From 7481dad24e98b4d9efd0a272350e40d10b471cba Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 21 Oct 2026 00:04:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ignore):=20=E9=9D=A2=E6=9D=BF=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=8C=E5=BF=BD=E7=95=A5=E3=80=8D=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=EF=BC=88=E5=9D=A6=E5=85=8B=E6=8C=89=E9=98=B5=E8=90=A5=20/=20?= =?UTF-8?q?=E5=8F=8D=E5=BC=B9=20/=20=E6=A0=85=E6=A0=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hornet_noclip 改为通用忽略模块:配对过滤支持 关闭/仅敌方/仅友方/全部 的坦克忽略,并新增忽略栅栏(只跳过 |法线z|<0.5 的静态三角形,地面与缓坡保留);面板新增可折叠「忽略」区域,接管忽略坦克下拉、忽略反弹(NoKnockback)、忽略栅栏,各自 localStorage 持久化;schema 补 Shape.Type/Vertex0-2。 --- .../play/static/js/unpacked/hornet_bundle.js | 333 ++++++++++++++---- 3dtank_bundle.js | 333 ++++++++++++++---- tools/hornet_noclip.js | 257 ++++++++++---- tools/hornet_panel.js | 72 +++- tools/hornet_schema.js | 4 + 5 files changed, 783 insertions(+), 216 deletions(-) diff --git a/3dtank.com/play/static/js/unpacked/hornet_bundle.js b/3dtank.com/play/static/js/unpacked/hornet_bundle.js index 441edc4..858d42f 100644 --- a/3dtank.com/play/static/js/unpacked/hornet_bundle.js +++ b/3dtank.com/play/static/js/unpacked/hornet_bundle.js @@ -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()); } // 底部状态 diff --git a/3dtank_bundle.js b/3dtank_bundle.js index 441edc4..858d42f 100644 --- a/3dtank_bundle.js +++ b/3dtank_bundle.js @@ -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()); } // 底部状态 diff --git a/tools/hornet_noclip.js b/tools/hornet_noclip.js index e4638a2..bf7f746 100644 --- a/tools/hornet_noclip.js +++ b/tools/hornet_noclip.js @@ -1,33 +1,26 @@ -// 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; } @@ -46,25 +39,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; @@ -72,14 +77,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__; @@ -102,25 +200,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; @@ -129,36 +226,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__ || {}; @@ -166,10 +285,14 @@ 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] 忽略模块已加载(忽略坦克 / 忽略栅栏)'); })(); diff --git a/tools/hornet_panel.js b/tools/hornet_panel.js index 101fed4..5d7b084 100644 --- a/tools/hornet_panel.js +++ b/tools/hornet_panel.js @@ -149,16 +149,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') || '{}'); @@ -572,6 +571,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__ || {}; @@ -599,6 +656,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()); } // 底部状态 diff --git a/tools/hornet_schema.js b/tools/hornet_schema.js index 2b41dba..8e45b44 100644 --- a/tools/hornet_schema.js +++ b/tools/hornet_schema.js @@ -75,6 +75,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)----