feat(ignore): 面板新增「忽略」区域(坦克按阵营 / 反弹 / 栅栏)

hornet_noclip 改为通用忽略模块:配对过滤支持 关闭/仅敌方/仅友方/全部 的坦克忽略,并新增忽略栅栏(只跳过 |法线z|<0.5 的静态三角形,地面与缓坡保留);面板新增可折叠「忽略」区域,接管忽略坦克下拉、忽略反弹(NoKnockback)、忽略栅栏,各自 localStorage 持久化;schema 补 Shape.Type/Vertex0-2。
This commit is contained in:
TermiNexus committed 2026-10-21 00:04:38 +08:00
1 parent 5088524f22
commit 7481dad24e
5 files changed
+783 -216

No files matched your search

@@ -299,6 +299,10 @@
'Shape.Mask': 'z9j_1', // 愿意碰撞的分类位
'Shape.Ghost': 'g9k_1', // true = 只探测不产生碰撞响应
'Shape.Body': 'b9k_1', // 形状 → 所属刚体(Fn)
'Shape.Type': 'v9j_1', // 形状类型位组(1/2/4/8/16/32;8 = 三角形)
'Shape.Vertex0': 'u9x_1', // 三角形顶点 0
'Shape.Vertex1': 'v9x_1', // 三角形顶点 1
'Shape.Vertex2': 'w9x_1', // 三角形顶点 2
'Body.Owner': 'x9i_1', // 刚体 → 拥有者(坦克物理组件 ejt)
// ---- 碰撞世界(module_55088 的 ki)----
@@ -6516,36 +6520,29 @@
// ===== hornet_noclip.js =====
// hornet_noclip.js — 穿坦克(移动中忽略玩家坦克碰撞,强行行走)
// 放在控制面板「穿坦克」开关里,只影响**本地坦克**,不改服务端/其他玩家。
// hornet_noclip.js — 忽略模块(穿坦克 / 忽略栅栏)
// 面板「忽略」区域控制:
// · 忽略坦克:关闭 / 仅敌方 / 仅友方 / 全部
// · 忽略栅栏:穿墙(只忽略陡峭的静态三角形,地面与缓坡照常)
//
// ⚠ 关键修正(2026-10-20):**不再改碰撞 mask**。
// 之前清掉坦克形状 mask 的 bit0(1)会让拾取物失效 —— 坦克 category=1,
// 而拾取物的触发体(module_87072 的 Ilt:category=1、mask=1、半径触发)
// 判定需要「触发体.cat & 坦克.mask」成立,清掉 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 / category 一个字节都不改 → 地形、拾取物触发、地雷、子弹全部保持原样
// · 关闭开关不需要还原任何状态(wrapper 直接转发原函数)
// · 只影响坦克↔坦克这一种配对,不波及静态物体与触发体
// 原理(module_55088 碰撞世界 ki):
// q9q(a, b) 是全物理世界唯一的「接触配对过滤」:
// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.y9j_1)) return; // 不生成接触
// 我们包装它,在满足条件时直接返回(等价引擎自己的「不碰撞」分支):
// · 坦克↔坦克:一边是本车、另一边是目标阵营的坦克 → 跳过
// · 本车↔静态三角形:三角形法线接近水平(墙/栅栏/悬崖立面)→ 跳过;法线朝上(地面/缓坡)→ 保留
//
// 关键点:**不改 mask/category**,所以拾取物触发、地雷、子弹、地形判定都不受影响。
// 形状 → 刚体 → 拥有者:
// Shape.b9k_1 = 刚体(Fn,u9j 里 `t.b9k_1 = this`)
// Body.x9i_1 = 拥有者(坦克物理组件 ejt 在 y15j 里 `pbk().x9i_1 = this`)
// Shape.b9k_1 = 刚体(Fn);Body.x9i_1 = 拥有者(坦克物理组件 ejt)
// 静态三角形(类型 8):顶点 u9x_1 / v9x_1 / w9x_1;本引擎 x,y 为水平面,**z 是高度**。
(function () {
'use strict';
var Lib = window.__hornetLib__;
if (!Lib) {
console.error('[穿坦克] 请先加载 hornet_lib.js');
console.error('[忽略] 请先加载 hornet_lib.js');
return;
}
@@ -6564,25 +6561,37 @@
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 FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状
var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main')
var FShapeCategory = Fld('Shape.Category', 'y9j_1'); // category 位组
var FShapeType = Fld('Shape.Type', 'v9j_1'); // 形状类型位组(8 = 三角形)
var FShapeBody = Fld('Shape.Body', 'b9k_1'); // 形状 → 刚体
var FBodyOwner = Fld('Body.Owner', 'x9i_1'); // 刚体 → 拥有者
var FV0 = Fld('Shape.Vertex0', 'u9x_1'); // 三角形顶点 0
var FV1 = Fld('Shape.Vertex1', 'v9x_1'); // 三角形顶点 1
var FV2 = Fld('Shape.Vertex2', 'w9x_1'); // 三角形顶点 2
var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); // 坦克物理组件识别方法
var MCollide = Mth('CollisionWorld.ShouldCollide', 'q9q');
var WorldKeys = ExportKeys('Export.PhysicsWorld', ['h']);
var PHYSICS_MODULE = 55088; // 碰撞世界所在模块
var PHYSICS_MODULE = 55088;
var STATIC_CATEGORY_BIT = 16; // 地图静态几何 category 位(s6() 造地形:y9j_1 = 16)
var TRIANGLE_TYPE = 8; // yi 子类 wu(三角形)的 v9j_1
var FENCE_NORMAL_Z = 0.5; // |法线 z| 小于它 → 视为墙/栅栏(地面与缓坡保留)
var Stat = {
running: false,
skipped: 0, // 累计跳过的坦克↔坦克配对次数
mode: 'all', // 'off' | 'enemy' | 'friendly' | 'all'
ignoreFence: false,
skippedTank: 0,
skippedFence: 0,
patched: false
};
var WorldProto = null; // 碰撞世界类原型(ki.prototype)
var OrigCollide = null; // 原始配对过滤
var WorldProto = null;
var OrigCollide = null;
// 拥有者是否是坦克物理组件(ejt):带 m155,且 zzq_1.x9j_1 === 'hull-main'
// 拥有者是否是坦克物理组件(ejt)
function IsTankOwner(Owner) {
if (!Owner || typeof Owner !== 'object') return false;
if (typeof Owner[MRemoveMask] !== 'function') return false;
@@ -6590,14 +6599,107 @@
return !!(Hull && Hull[FShapeName] === 'hull-main');
}
function IsTankShape(Shape) {
if (!Shape || typeof Shape !== 'object') return false;
function OwnerOf(Shape) {
if (!Shape || typeof Shape !== 'object') return null;
var Body = Shape[FShapeBody];
if (!Body) return false;
return IsTankOwner(Body[FBodyOwner]);
return Body ? (Body[FBodyOwner] || null) : null;
}
// 取碰撞世界类原型:module 55088 的导出键 → 校验原型上确实有配对过滤方法
function IsTankShape(Shape) {
return IsTankOwner(OwnerOf(Shape));
}
// 本地坦克物理组件(100ms 节流缓存;重生/换车后最多 100ms 生效)
var LocalPhys = null;
var LocalPhysAt = 0;
function GetLocalPhysics() {
var Now = Date.now();
if (Now - LocalPhysAt < 100) return LocalPhys;
LocalPhysAt = Now;
LocalPhys = null;
var Gm = window.__gameMode__;
if (!Gm) return null;
var Tank = null;
try { Tank = Gm.u10b_1 || Gm.t10b_1; } catch (e) { Tank = null; }
if (!Tank || !Tank.qz1_1 || !Tank.qz1_1.s2a_1) return null;
try {
Tank.qz1_1.s2a_1.forEach(function (ValueList) {
if (LocalPhys) return;
var Comp = (ValueList && typeof ValueList.g1 === 'function') ? ValueList.g1(0) : ValueList;
if (IsTankOwner(Comp)) LocalPhys = Comp;
});
} catch (e) {}
return LocalPhys;
}
function EntityOf(Owner) {
if (!Owner || typeof Owner.lz4 !== 'function') return null;
try { return Owner.lz4(); } catch (e) { return null; }
}
// 三角形是否「陡」:法线 z 分量接近 0(墙/栅栏/悬崖立面)
function IsSteepTriangle(Shape) {
if (!Shape || Shape[FShapeType] !== TRIANGLE_TYPE) return false;
var A = Shape[FV0], B = Shape[FV1], C = Shape[FV2];
if (!A || !B || !C) return false;
if (typeof A.f20_1 !== 'number' || typeof B.f20_1 !== 'number' || typeof C.f20_1 !== 'number') return false;
var ABx = B.f20_1 - A.f20_1, ABy = B.g20_1 - A.g20_1, ABz = B.h20_1 - A.h20_1;
var ACx = C.f20_1 - A.f20_1, ACy = C.g20_1 - A.g20_1, ACz = C.h20_1 - A.h20_1;
var Nx = ABy * ACz - ABz * ACy;
var Ny = ABz * ACx - ABx * ACz;
var Nz = ABx * ACy - ABy * ACx;
var Len = Math.sqrt(Nx * Nx + Ny * Ny + Nz * Nz);
if (!(Len > 1e-6)) return false;
return Math.abs(Nz / Len) < FENCE_NORMAL_Z;
}
// 静态几何:category 含 bit4,且不是坦克部件
function IsStaticShape(Shape) {
if (!Shape || typeof Shape[FShapeCategory] !== 'number') return false;
if ((Shape[FShapeCategory] & STATIC_CATEGORY_BIT) === 0) return false;
return !IsTankShape(Shape);
}
// ===== 判定:这一对形状要不要跳过 =====
function ShouldSkip(ShapeA, ShapeB) {
var Local = GetLocalPhysics();
var TankA = IsTankShape(ShapeA);
var TankB = IsTankShape(ShapeB);
// ① 忽略坦克(按阵营)
if (Stat.mode !== 'off' && TankA && TankB) {
var OA = OwnerOf(ShapeA);
var OB = OwnerOf(ShapeB);
if (Local && (OA === Local || OB === Local)) {
var OtherOwner = (OA === Local) ? OB : OA;
if (Stat.mode === 'all') {
Stat.skippedTank++;
return true;
}
var OtherEntity = EntityOf(OtherOwner);
if (OtherEntity) {
var Friendly = Lib.IsFriendly(OtherEntity, null, window.__gameMode__);
if ((Stat.mode === 'enemy' && !Friendly) || (Stat.mode === 'friendly' && Friendly)) {
Stat.skippedTank++;
return true;
}
}
}
}
// ② 忽略栅栏(只跳过陡峭静态三角形,保留地面/缓坡)
if (Stat.ignoreFence && Local) {
if ((TankA && OwnerOf(ShapeA) === Local && IsStaticShape(ShapeB) && IsSteepTriangle(ShapeB)) ||
(TankB && OwnerOf(ShapeB) === Local && IsStaticShape(ShapeA) && IsSteepTriangle(ShapeA))) {
Stat.skippedFence++;
return true;
}
}
return false;
}
// ===== 安装/卸载配对过滤 =====
function GetWorldProto() {
if (WorldProto) return WorldProto;
var Req = window.__wp_require__;
@@ -6620,25 +6722,24 @@
if (Stat.patched) return true;
var Proto = GetWorldProto();
if (!Proto) {
console.warn('[穿坦克] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效');
console.warn('[忽略] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效');
return false;
}
OrigCollide = Proto[MCollide];
var Wrapper = function (ShapeA, ShapeB) {
// 开关打开且两边都是坦克 → 直接当作"不碰撞"返回(不生成接触点)
if (Stat.running && IsTankShape(ShapeA) && IsTankShape(ShapeB)) {
Stat.skipped++;
return undefined;
Proto[MCollide] = function (ShapeA, ShapeB) {
if (Stat.running) {
try {
if (ShouldSkip(ShapeA, ShapeB)) return undefined; // 等价引擎的「不碰撞」分支:不生成接触
} catch (e) {}
}
return OrigCollide.apply(this, arguments);
};
Proto[MCollide] = Wrapper;
Stat.patched = true;
console.log('[穿坦克] 已接管碰撞配对过滤(只拦坦克↔坦克,拾取物/地形/子弹不受影响)');
console.log('[忽略] 已接管碰撞配对过滤(坦克按阵营 / 陡峭静态三角形)');
return true;
}
// 碰撞世界可能还没加载(在车库里提前开开关):每 0.5s 重试一次,加载后自动接管
// 碰撞世界可能还没加载(车库里提前开):每 0.5s 重试
var RetryTimer = null;
function ScheduleRetry() {
if (RetryTimer) return;
@@ -6647,36 +6748,58 @@
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.skipped = 0;
if (!InstallPatch()) {
console.warn('[穿坦克] 碰撞世界尚未加载,进入等待(加载后自动接管)');
ScheduleRetry();
function Apply() {
var NeedRun = Stat.mode !== 'off' || Stat.ignoreFence;
if (NeedRun && !Stat.running) {
Stat.running = true;
if (!InstallPatch()) {
console.warn('[忽略] 碰撞世界尚未加载,进入等待(加载后自动接管)');
ScheduleRetry();
}
} else if (!NeedRun && Stat.running) {
Stat.running = false;
if (RetryTimer) { clearInterval(RetryTimer); RetryTimer = null; }
}
console.log('[穿坦克] 已启动');
}
function _stop() {
if (!Stat.running) return;
Stat.running = false;
CancelRetry();
console.log('[穿坦克] 已停止(碰撞恢复,无需还原任何状态)');
// ===== 对外接口 =====
function SetTankMode(Mode) {
if (['off', 'enemy', 'friendly', 'all'].indexOf(Mode) < 0) Mode = 'off';
Stat.mode = Mode;
Apply();
try { localStorage.setItem('hornet_ignore_tanks', Mode); } catch (e) {}
console.log('[忽略] 忽略坦克 = ' + Mode);
}
function GetTankMode() { return Stat.mode; }
function SetIgnoreFence(On) {
Stat.ignoreFence = !!On;
Apply();
try { localStorage.setItem('hornet_ignore_fence', On ? '1' : '0'); } catch (e) {}
console.log('[忽略] 忽略栅栏 = ' + (On ? '开' : '关'));
}
function GetIgnoreFence() { return Stat.ignoreFence; }
// 兼容旧接口(面板旧版/热键可能还在调用)
function _start() { SetTankMode(Stat.mode === 'off' ? 'all' : Stat.mode); }
function _stop() { Stat.mode = 'off'; Stat.ignoreFence = false; Apply(); }
var Tool = {
Start: function () { _start(); },
Stop: function () { _stop(); },
Start: _start,
Stop: _stop,
GetRunning: function () { return Stat.running; },
Status: function () {
if (!Stat.running) return '已停止';
return Stat.patched ? ('拦截 ' + Stat.skipped + ' 对') : '等待碰撞世界';
}
var Parts = [];
if (Stat.mode !== 'off') Parts.push('坦克:' + Stat.mode);
if (Stat.ignoreFence) Parts.push('栅栏');
return Parts.length ? Parts.join(' + ') : '已停止';
},
SetTankMode: SetTankMode,
GetTankMode: GetTankMode,
SetIgnoreFence: SetIgnoreFence,
GetIgnoreFence: GetIgnoreFence
};
window.__hornetTools__ = window.__hornetTools__ || {};
@@ -6684,12 +6807,16 @@
window.__hornetNoClip__ = {
IsTankOwner: IsTankOwner,
IsTankShape: IsTankShape,
IsSteepTriangle: IsSteepTriangle,
GetStat: function () {
return { running: Stat.running, skipped: Stat.skipped, patched: Stat.patched };
return {
running: Stat.running, mode: Stat.mode, ignoreFence: Stat.ignoreFence,
skippedTank: Stat.skippedTank, skippedFence: Stat.skippedFence, patched: Stat.patched
};
}
};
console.log('[3DTank] 穿坦克模块已加载(配对过滤模式)');
console.log('[3DTank] 忽略模块已加载(忽略坦克 / 忽略栅栏)');
})();
@@ -6995,16 +7122,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: 'NoClip', label: '穿坦克', obj: tools.NoClip },
{ key: 'NoKnockback', label: '拒绝击退', obj: tools.NoKnockback }
{ key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse }
];
}
// ===== 工具启用状态(持久化 + 默认启用)=====
// 默认除「自由视角」外全部开启:自由视角会直接接管相机,默认开启过于突兀
var ToolKeys = ['Radar', 'Wireframe', 'Rearview', 'AimWarning', 'FreeLook', 'WarSandbox', 'Mouse', 'NoClip', 'NoKnockback'];
var ToolDefaults = { Radar: true, Wireframe: true, Rearview: true, AimWarning: true, FreeLook: false, WarSandbox: true, Mouse: true, NoClip: false, NoKnockback: false };
// 忽略模块(NoClip / NoKnockback)不在这里:由「忽略」区域单独管理,自带 localStorage 持久化
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 ToolEnabled = {};
try {
var __savedEnabled = JSON.parse(localStorage.getItem('hornet_tool_enabled') || '{}');
@@ -7418,6 +7544,64 @@
} catch (e) {}
})();
// ===== 忽略(坦克 / 反弹 / 栅栏)=====
// 忽略坦克:关闭 / 仅敌方 / 仅友方 / 全部(由 hornet_noclip.js 的配对过滤实现)
// 忽略反弹:对应 hornet_noknockback.js(中弹不再被推开)
// 忽略栅栏:只忽略陡峭的静态三角形(墙/栅栏立面),地面与缓坡照常
var igSec = MakeSettingsSection('忽略');
var igTankRow = igSec.row('忽略坦克');
var igTankSel = document.createElement('select');
igTankSel.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;';
[
{ v: 'off', t: '关闭' },
{ v: 'enemy', t: '仅敌方' },
{ v: 'friendly', t: '仅友方' },
{ v: 'all', t: '全部' }
].forEach(function (o) {
var opt = document.createElement('option');
opt.value = o.v; opt.textContent = o.t;
igTankSel.appendChild(opt);
});
igTankRow.ctrl.appendChild(igTankSel);
igTankSel.onchange = function () {
var tools = window.__hornetTools__ || {};
if (tools.NoClip) tools.NoClip.SetTankMode(igTankSel.value);
};
var igKnockCtl = igSec.checkbox('忽略反弹', function (on) {
var tools = window.__hornetTools__ || {};
if (tools.NoKnockback) { on ? tools.NoKnockback.Start() : tools.NoKnockback.Stop(); }
try { localStorage.setItem('hornet_ignore_knockback', on ? '1' : '0'); } catch (e) {}
});
var igFenceCtl = igSec.checkbox('忽略栅栏', function (on) {
var tools = window.__hornetTools__ || {};
if (tools.NoClip) tools.NoClip.SetIgnoreFence(on);
});
// 启动时按上次保存的状态恢复
(function () {
var tools = window.__hornetTools__ || {};
var Mode = 'off';
try { Mode = localStorage.getItem('hornet_ignore_tanks') || 'off'; } catch (e) {}
if (['off', 'enemy', 'friendly', 'all'].indexOf(Mode) < 0) Mode = 'off';
igTankSel.value = Mode;
if (tools.NoClip) tools.NoClip.SetTankMode(Mode);
var Fence = false;
try { Fence = localStorage.getItem('hornet_ignore_fence') === '1'; } catch (e) {}
igFenceCtl.set(Fence);
if (tools.NoClip) tools.NoClip.SetIgnoreFence(Fence);
var Knock = false;
try { Knock = localStorage.getItem('hornet_ignore_knockback') === '1'; } catch (e) {}
igKnockCtl.set(Knock);
if (tools.NoKnockback && Knock) tools.NoKnockback.Start();
})();
// 从工具读取当前设置(打开面板时同步)
function SyncWfSettings() {
var tools = window.__hornetTools__ || {};
@@ -7445,6 +7629,11 @@
wsGridCtl.set(tools.WarSandbox.GetShowGrid());
wsMarkerCtl.set(tools.WarSandbox.GetMarkerSize());
}
if (tools.NoClip) {
igTankSel.value = tools.NoClip.GetTankMode();
igFenceCtl.set(tools.NoClip.GetIgnoreFence());
}
if (tools.NoKnockback) igKnockCtl.set(tools.NoKnockback.GetRunning());
}
// 底部状态