Files
TankIC/tools/hornet_noclip.js
T
TermiNexus 2ba8df0245 fix(ignore): 只忽略立面三角形,type 1/4 静态代理不再跳过(修复掉地)
实测 byType={16/4:26530,16/1:4620} 表明被跳过的是非三角形地形代理,导致穿地。现判定只对 type=8 三角形生效:最高点高于车体中心 1m 且 |法线z|<0.6;非三角形静态一律保留。
2026-10-21 00:36:30 +08:00

451 lines
17 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 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 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,
byType: {}, // cat/type → 被忽略次数(诊断)
patched: 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(墙/栅栏/悬崖立面)
function IsTriangle(Shape) {
return !!(Shape && Shape[FShapeType] === TRIANGLE_TYPE);
}
// 返回法线 z 分量(-1..1);读不到返回 null
function TriangleNormalZ(Shape) {
if (!IsTriangle(Shape)) return null;
var A = Shape[FV0], B = Shape[FV1], C = Shape[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);
}
var FPhysicsPosition = Fld('Physics.Position', 'bzr_1'); // 本地坦克世界位置
function LocalTankZ() {
var Phys = GetLocalPhysics();
var P = Phys && Phys[FPhysicsPosition];
return (P && typeof P.h20_1 === 'number') ? P.h20_1 : null;
}
function ShapeMaxZ(Shape) {
if (IsTriangle(Shape)) {
var A = Shape[FV0], B = Shape[FV1], C = Shape[FV2];
if (!A || !B || !C) return null;
var Max = Math.max(A.h20_1, B.h20_1, C.h20_1);
return isFinite(Max) ? Max : null;
}
var Body = Shape[FShapeBody];
var State = Body && Body[FBodyState];
var P = State && State[FBodyPosVec];
return (P && typeof P.h20_1 === 'number') ? P.h20_1 : null;
}
// 「栅栏类」= 静态几何中的**立面三角形**:
// · 必须是三角形(type 8)—— 非三角形静态体(type 1/4 等)是地面/地形代理,跳过会导致掉出地图
// · 最高点高于车体中心 1m(地面/缓坡不满足)
// · |法线 z| < 0.6(竖直立面)
function IsFenceLike(Shape) {
if (!IsStaticShape(Shape)) return false;
if (!IsTriangle(Shape)) return false;
var TankZ = LocalTankZ();
if (TankZ === null) return false;
var MaxZ = ShapeMaxZ(Shape);
if (MaxZ === null) return false;
if (MaxZ < TankZ + 100) return false;
var Nz = TriangleNormalZ(Shape);
return Nz !== null && Math.abs(Nz) < 0.6;
}
// ===== 诊断:识别「正在和什么物体重叠」=====
// 碰撞形状本身没有名字(地图三角形是合并的三角汤),所以用两级信息:
// ① 形状自身: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;
if (IsTriangle(Shape)) {
var A = Shape[FV0], B = Shape[FV1], C = Shape[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;
}
// 非三角形:取所在刚体的世界位置
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 };
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 Key = Cat + '/' + Type + '/' + (Nz === null ? '-' : Nz.toFixed(2));
if (FenceSampleKeys[Key]) return;
FenceSampleKeys[Key] = 1;
var Pt = ShapePoint(Shape);
var Near = FindNearNodeNames(Pt, 700);
console.log('[忽略][重叠物体] cat=' + Cat + ' type=' + Type + ' 三角=' + Tri +
' nz=' + (Nz === null ? '-' : Nz.toFixed(2)) +
' 位置=' + (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;
if (FenceShape && IsFenceLike(FenceShape)) {
Stat.skippedFence++;
var Key = FenceShape[FShapeCategory] + '/' + FenceShape[FShapeType];
Stat.byType[Key] = (Stat.byType[Key] || 0) + 1;
return true;
}
// 诊断:本车与任意非坦克形状配对但没跳过 → 记录样本(含被挡住的栅栏)
if (TankA && OwnerOf(ShapeA) === Local && !TankB) RecordFenceSample(ShapeB);
if (TankB && OwnerOf(ShapeB) === Local && !TankA) RecordFenceSample(ShapeA);
}
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('[忽略] 已接管碰撞配对过滤(坦克按阵营 / 陡峭静态三角形)');
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,
byType: Stat.byType, patched: Stat.patched
};
}
};
console.log('[3DTank] 忽略模块已加载(忽略坦克 / 忽略栅栏)');
})();