Files
TankIC/tools/hornet_noclip.js
T
TermiNexus bfc3821dc7 fix(ignore): 恢复非三角形静态碰撞的忽略,栅栏恢复可穿过
上一版把判定收窄为仅陡峭三角形,导致非三角形静态代理(栅栏碰撞体)仍会挡住本地坦克。现判定:静态几何中非三角形一律忽略;三角形按 |法线z|<0.5 忽略,地面/缓坡保留。
2026-10-21 00:30:00 +08:00

417 lines
16 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,
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);
}
// 「栅栏类」= 静态几何里除地面/缓坡以外的部分:
// · 三角形:法线接近水平(墙/栅栏/悬崖立面)→ 忽略;法线朝上(地面/缓坡)→ 保留
// · 非三角形静态碰撞代理(盒/柱等):地图里这类基本都不是可站立地面 → 忽略
function IsFenceLike(Shape) {
if (!IsStaticShape(Shape)) return false;
if (!IsTriangle(Shape)) return true;
return IsSteepTriangle(Shape);
}
// ===== 诊断:识别「正在和什么物体重叠」=====
// 碰撞形状本身没有名字(地图三角形是合并的三角汤),所以用两级信息:
// ① 形状自身: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) {
if ((TankA && OwnerOf(ShapeA) === Local && IsFenceLike(ShapeB)) ||
(TankB && OwnerOf(ShapeB) === Local && IsFenceLike(ShapeA))) {
Stat.skippedFence++;
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, patched: Stat.patched
};
}
};
console.log('[3DTank] 忽略模块已加载(忽略坦克 / 忽略栅栏)');
})();