Files
TankIC/tools/hornet_noclip.js
T
TermiNexus ee2cba3273 fix(ignore): 改用局部坐标的竖直跨度判定栅栏,修正世界/局部坐标混用
三角形顶点是刚体局部坐标(诊断里位置恒为 0,0,0),之前拿局部 z 与车体世界 z 比较导致条件恒不成立。现判据:|法线z|<0.6 且本地竖直跨度>100(地面跨度≈0、栅栏≈200+);诊断输出补 解包type/跨度,位置改用刚体世界坐标以便识别近邻节点。
2026-10-21 01:06:20 +08:00

513 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// hornet_noclip.js — 忽略模块(穿坦克 / 忽略栅栏)
// 面板「忽略」区域控制:
// · 忽略坦克:关闭 / 仅敌方 / 仅友方 / 全部
// · 忽略栅栏:穿墙(只忽略陡峭的静态三角形,地面与缓坡照常)
//
// 原理(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);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');
return;
}
// ===== 字段抽象层(语义名 → 匿名字段,总表在 hornet_schema.js)=====
var Schema = window.__hornetSchema__ || null;
function Fld(Key, Fallback) {
var N = Schema ? Schema.FieldName(Key) : null;
return N || Fallback;
}
function Mth(Key, Fallback) {
var N = Schema ? Schema.MethodName(Key) : null;
return N || Fallback;
}
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 FShapeCategory = Fld('Shape.Category', 'y9j_1'); // category 位组
var FShapeType = Fld('Shape.Type', 'v9j_1'); // 形状类型位组(8 = 三角形)
var FShapeClone = Fld('Shape.Clone', 'c9w_1'); // 类型 4 包装里的真实形状
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 MOverlap = Mth('CollisionWorld.Overlap', 'x9o'); // 形状相交查询(真形状,不是包围盒)
var WorldKeys = ExportKeys('Export.PhysicsWorld', ['h']);
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,
mode: 'all', // 'off' | 'enemy' | 'friendly' | 'all'
ignoreFence: false,
skippedTank: 0,
skippedFence: 0,
skippedOverlap: 0, // x9o 形状相交查询被拦下的次数
byType: {}, // cat/type → 被忽略次数(诊断)
seen: {}, // cat/type → 本车接触到的非坦克形状次数(诊断)
patched: false,
overlapPatched: false
};
var WorldProto = null;
var OrigCollide = null;
// 拥有者是否是坦克物理组件(ejt)
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');
}
function OwnerOf(Shape) {
if (!Shape || typeof Shape !== 'object') return null;
var Body = Shape[FShapeBody];
return Body ? (Body[FBodyOwner] || null) : null;
}
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(墙/栅栏/悬崖立面)
// type 4 是「克隆包装」(hu: this.c9w_1 = 原形状.d21()),真实几何在 c9w_1 里,必须先解包
function UnwrapShape(Shape) {
var S = Shape, Guard = 0;
while (S && S[FShapeType] === 4 && S[FShapeClone] && Guard++ < 4) S = S[FShapeClone];
return S;
}
function IsTriangle(Shape) {
var S = UnwrapShape(Shape);
return !!(S && S[FShapeType] === TRIANGLE_TYPE);
}
// 返回法线 z 分量(-1..1);读不到返回 null
function TriangleNormalZ(Shape) {
var S = UnwrapShape(Shape);
if (!S || S[FShapeType] !== TRIANGLE_TYPE) return null;
var A = S[FV0], B = S[FV1], C = S[FV2];
if (!A || !B || !C) return null;
if (typeof A.f20_1 !== 'number' || typeof B.f20_1 !== 'number' || typeof C.f20_1 !== 'number') return null;
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 null;
return Nz / Len;
}
function IsSteepTriangle(Shape) {
var Nz = TriangleNormalZ(Shape);
return Nz !== null && Math.abs(Nz) < 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);
}
// 三角形竖直跨度(局部坐标):顶点 z 的 max-min
// 地面/缓坡:三个顶点几乎同高 → 跨度≈0
// 栅栏/墙:竖直面 → 跨度大(≥1m)
function TriangleZRange(Shape) {
var S = UnwrapShape(Shape);
if (!S || S[FShapeType] !== TRIANGLE_TYPE) return null;
var A = S[FV0], B = S[FV1], C = S[FV2];
if (!A || !B || !C) return null;
if (typeof A.h20_1 !== 'number' || typeof B.h20_1 !== 'number' || typeof C.h20_1 !== 'number') return null;
var Max = Math.max(A.h20_1, B.h20_1, C.h20_1);
var Min = Math.min(A.h20_1, B.h20_1, C.h20_1);
return Max - Min;
}
// 「栅栏类」= 静态几何中的**立面三角形**(全部用局部坐标判定,不依赖世界坐标):
// · type 4 先解包到真实形状,必须是三角形
// · |法线 z| < 0.6 → 竖直立面(地面法线≈1)
// · 竖直跨度 > 100 单位 → 有高度的墙/栅栏(地面/缓坡跨度≈0)
function IsFenceLike(Shape) {
if (!IsStaticShape(Shape)) return false;
if (!IsTriangle(Shape)) return false;
var Nz = TriangleNormalZ(Shape);
if (Nz === null || Math.abs(Nz) >= 0.6) return false;
var Range = TriangleZRange(Shape);
return Range !== null && Range > 100;
}
// 本车形状 / 本车与栅栏形状的配对(q9q 与 x9o 共用同一套判定)
function IsLocalTankShape(Shape) {
var Local = GetLocalPhysics();
return !!(Local && IsTankShape(Shape) && OwnerOf(Shape) === Local);
}
// 「本车 ↔ 任意静态形状」:用于形状相交查询(x9o)这条路径。
// 扫掠/查询被挡时返回 false 即可穿过;地面支撑走的是射线(l9m),不经过 x9o。
function IsLocalStaticPair(ShapeA, ShapeB) {
return (IsLocalTankShape(ShapeA) && IsStaticShape(ShapeB)) ||
(IsLocalTankShape(ShapeB) && IsStaticShape(ShapeA));
}
// ===== 诊断:识别「正在和什么物体重叠」=====
// 碰撞形状本身没有名字(地图三角形是合并的三角汤),所以用两级信息:
// ① 形状自身:category / mask / type / 法线
// ② 形状位置附近(默认 7m)场景树里的节点名(如 fence_s / wall_new_3w)
var FSceneRoot = Fld('Scene.RootNode', 'fyx_1');
var FNodeName = Fld('Node.Name', 'g35_1');
var FNodeMatrix = Fld('Node.Matrix', 'p35_1');
var FNodeChildren = Fld('Node.Children', 'r35_1');
var FBodyState = Fld('Body.State', 'f9j_1');
var FBodyPosVec = Fld('Body.PositionVec', 's9j_1');
function ShapePoint(Shape) {
if (!Shape) return null;
// 顶点是刚体局部坐标(常见 (0,0,0) 附近),诊断要用刚体的世界位置
var Body = Shape[FShapeBody];
var State = Body && Body[FBodyState];
var P = State && State[FBodyPosVec];
if (P && typeof P.f20_1 === 'number') return { x: P.f20_1, y: P.g20_1, z: P.h20_1 };
// 兜底:局部顶点重心
var S = UnwrapShape(Shape);
if (S && S[FShapeType] === TRIANGLE_TYPE) {
var A = S[FV0], B = S[FV1], C = S[FV2];
if (A && B && C) {
return {
x: (A.f20_1 + B.f20_1 + C.f20_1) / 3,
y: (A.g20_1 + B.g20_1 + C.g20_1) / 3,
z: (A.h20_1 + B.h20_1 + C.h20_1) / 3
};
}
}
return null;
}
function FindNearNodeNames(Point, Radius) {
if (!Point) return [];
var Gm = window.__gameMode__;
if (!Gm) return [];
var Scene = null;
try { Scene = Gm.lz4()[Fld('Camera.SceneRef', 'jz1_1')]; } catch (e) { return []; }
var Root = Scene && Scene[FSceneRoot];
if (!Root) return [];
var Names = [];
var Seen = typeof Set !== 'undefined' ? new Set() : null;
var Visited = 0;
var R = Radius || 700;
var R2 = R * R;
(function Walk(Node, Depth) {
if (!Node || Depth > 12 || Visited > 4000) return;
if (Seen) { if (Seen.has(Node)) return; Seen.add(Node); }
Visited++;
var M = Node[FNodeMatrix];
if (M && typeof M.length === 'number' && M.length >= 16 && typeof M[12] === 'number') {
var Dx = M[12] - Point.x, Dy = M[13] - Point.y, Dz = M[14] - Point.z;
if (Dx * Dx + Dy * Dy + Dz * Dz <= R2) {
var Nm = Node[FNodeName];
if (Nm && Names.indexOf(Nm) < 0 && Names.length < 8) Names.push(Nm);
}
}
var Children = Node[FNodeChildren];
if (Children && typeof Children.e1 === 'function') {
var N = Children.e1();
for (var i = 0; i < N; i++) {
try { Walk(Children.g1(i), Depth + 1); } catch (e) {}
}
}
})(Root, 0);
return Names;
}
// 每个「未跳过」的静态形状只打印一次(按签名去重)
var FenceSampleKeys = {};
function RecordFenceSample(Shape) {
if (window.__hornetFenceDebug__ !== true) return;
var Cat = Shape[FShapeCategory];
var Type = Shape[FShapeType];
var Tri = IsTriangle(Shape);
var Nz = TriangleNormalZ(Shape);
var Base = UnwrapShape(Shape);
var BaseType = Base ? Base[FShapeType] : '-';
var Range = TriangleZRange(Shape);
var Key = Cat + '/' + Type + '/' + BaseType + '/' +
(Nz === null ? '-' : Nz.toFixed(1)) + '/' +
(Range === null ? '-' : Math.round(Range / 50));
if (FenceSampleKeys[Key]) return;
FenceSampleKeys[Key] = 1;
var Pt = ShapePoint(Shape);
var Near = FindNearNodeNames(Pt, 700);
console.log('[忽略][重叠物体] cat=' + Cat + ' type=' + Type + ' 解包type=' + BaseType +
' 三角=' + Tri + ' nz=' + (Nz === null ? '-' : Nz.toFixed(2)) +
' 跨度=' + (Range === null ? '-' : Range.toFixed(0)) +
' 位置=' + (Pt ? ('(' + Pt.x.toFixed(0) + ',' + Pt.y.toFixed(0) + ',' + Pt.z.toFixed(0) + ')') : '-') +
' 近邻节点=[' + Near.join(', ') + ']' +
' 当前判定=' + (IsFenceLike(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) {
var FenceShape = null;
if (TankA && OwnerOf(ShapeA) === Local) FenceShape = ShapeB;
else if (TankB && OwnerOf(ShapeB) === Local) FenceShape = ShapeA;
// 统计:本车接触到的所有非坦克形状(不论是否跳过)→ cat/type 直方图
if (FenceShape) {
var SeenKey = FenceShape[FShapeCategory] + '/' + FenceShape[FShapeType];
Stat.seen[SeenKey] = (Stat.seen[SeenKey] || 0) + 1;
}
if (FenceShape && IsFenceLike(FenceShape)) {
Stat.skippedFence++;
var Key = FenceShape[FShapeCategory] + '/' + FenceShape[FShapeType];
Stat.byType[Key] = (Stat.byType[Key] || 0) + 1;
return true;
}
// 诊断:本车与任意非坦克形状配对但没跳过 → 记录样本(含被挡住的栅栏)
if (FenceShape) RecordFenceSample(FenceShape);
}
return false;
}
// ===== 安装/卸载配对过滤 =====
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;
}
function InstallPatch() {
if (Stat.patched) return true;
var Proto = GetWorldProto();
if (!Proto) {
console.warn('[忽略] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效');
return false;
}
OrigCollide = Proto[MCollide];
Proto[MCollide] = function (ShapeA, ShapeB) {
if (Stat.running) {
try {
if (ShouldSkip(ShapeA, ShapeB)) return undefined; // 等价引擎的「不碰撞」分支:不生成接触
} catch (e) {}
}
return OrigCollide.apply(this, arguments);
};
Stat.patched = true;
console.log('[忽略] 已接管碰撞配对过滤(坦克按阵营 / 陡峭静态三角形)');
// 同时接管「形状相交查询」x9o:真形状检测(不是包围盒),用于扫掠/穿墙判定
if (typeof Proto[MOverlap] === 'function') {
var OrigOverlap = Proto[MOverlap];
Proto[MOverlap] = function (ShapeA, ShapeB) {
if (Stat.running && Stat.ignoreFence) {
try {
if (IsLocalStaticPair(ShapeA, ShapeB)) {
Stat.skippedOverlap++;
return false; // 查询结果:不相交(穿栅栏走这条路径)
}
} catch (e) {}
}
return OrigOverlap.apply(this, arguments);
};
Stat.overlapPatched = true;
console.log('[忽略] 已接管形状相交查询 ' + MOverlap + '(真形状检测)');
} else {
console.warn('[忽略] 未找到形状相交查询 ' + MOverlap + ',扫掠路径未接管');
}
return true;
}
// 碰撞世界可能还没加载(车库里提前开):每 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 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; }
}
}
// ===== 对外接口 =====
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: _start,
Stop: _stop,
GetRunning: function () { return Stat.running; },
Status: function () {
if (!Stat.running) return '已停止';
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__ || {};
window.__hornetTools__.NoClip = Tool;
window.__hornetNoClip__ = {
IsTankOwner: IsTankOwner,
IsTankShape: IsTankShape,
IsSteepTriangle: IsSteepTriangle,
IsFenceLike: IsFenceLike,
// 诊断:__hornetNoClip__.DebugFence(true) 后撞一次墙,控制台会打印静态形状样本
DebugFence: function (On) {
window.__hornetFenceDebug__ = On !== false;
FenceSampleKeys = {};
console.log('[忽略] 栅栏诊断 = ' + (window.__hornetFenceDebug__ ? '开(撞栅栏后看 [忽略][重叠物体])' : '关'));
},
GetStat: function () {
return {
running: Stat.running, mode: Stat.mode, ignoreFence: Stat.ignoreFence,
skippedTank: Stat.skippedTank, skippedFence: Stat.skippedFence,
skippedOverlap: Stat.skippedOverlap,
byType: Stat.byType, seen: Stat.seen,
patched: Stat.patched, overlapPatched: Stat.overlapPatched
};
},
// 清零统计:便于「平地 1 秒」与「顶住栅栏 1 秒」两次对比 seen 里的 cat/type
ResetStat: function () {
Stat.skippedTank = 0;
Stat.skippedFence = 0;
Stat.skippedOverlap = 0;
Stat.byType = {};
Stat.seen = {};
FenceSampleKeys = {};
console.log('[忽略] 统计已清零');
}
};
console.log('[3DTank] 忽略模块已加载(忽略坦克 / 忽略栅栏)');
})();