refactor(schema): 全部工具改为经字段抽象层取值

hornet_lib / hook_gamemode / radar / 透视 / 后视镜 / 瞄准警告 / 自由视角 / 战争沙盘 全部改为 Fld/Mth 常量取值;schema 补齐渲染器、网格、射线、武器、皮肤槽位等 88 项字段与别名;SelfCheck 改为只校验核心字段,避免别名误报。
This commit is contained in:
TermiNexus committed 2026-10-20 22:04:07 +08:00
1 parent 5a3440229e
commit 66a74df6e3
11 files changed
+2073 -1011

No files matched your search

File diff suppressed because it is too large. Load diff
+691 -337
View File
File diff suppressed because it is too large. Load diff
+14 -5
View File
@@ -13,6 +13,15 @@
// 坦克列表 = dct(scene).m10b_1.j11y()
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// ===== 语义字段常量(取不到抽象层时用 fallback 字面名) =====
var FGameModeTankManager = Fld('GameMode.TankManager', 'm10b_1'); // 坦克列表管理器
var MTankList = Mth('GameMode.TankList', 'j11y'); // 坦克列表
console.log('===== GameMode Hook v2 开始 =====');
// 1. 检查 webpack 内部对象
@@ -47,7 +56,7 @@
// 尝试获取坦克列表(失败也不影响 gameMode 设置)
var tankCount = -1;
try {
var list = gm.m10b_1.j11y();
var list = gm[FGameModeTankManager][MTankList]();
tankCount = list.e1();
window.__tankList__ = list;
} catch(e) {
@@ -103,7 +112,7 @@
if (window.__gameMode__) {
// 验证当前引用是否有效
try {
var list = window.__gameMode__.m10b_1.j11y();
var list = window.__gameMode__[FGameModeTankManager][MTankList]();
window.__tankList__ = list;
} catch(e) {
// 引用失效(新战斗/退出战斗),重置等待重新捕获
@@ -142,7 +151,7 @@
console.log('ght 实例未捕获,请等待');
return;
}
var list = window.__gameMode__.m10b_1.j11y();
var list = window.__gameMode__[FGameModeTankManager][MTankList]();
var count = list.e1();
console.log('=== 坦克列表 (' + count + " 个) ===");
for (var i = 0; i < count; i++) {
@@ -157,7 +166,7 @@
console.log('ght 实例未捕获');
return;
}
var list = window.__gameMode__.m10b_1.j11y();
var list = window.__gameMode__[FGameModeTankManager][MTankList]();
var tank = list.g1(idx || 0);
console.log('坦克[' + (idx || 0) + ']:', tank);
console.log('字段:', Object.keys(tank));
@@ -181,7 +190,7 @@
window.__refreshGameMode__ = function() {
if (window.__gameMode__) {
try {
var list = window.__gameMode__.m10b_1.j11y();
var list = window.__gameMode__[FGameModeTankManager][MTankList]();
console.log('%c[刷新] ght 引用有效,坦克数:' + list.e1(), 'color:#0f0;font-weight:bold');
return window.__gameMode__;
} catch(e) {
+78 -45
View File
@@ -38,6 +38,39 @@
var FSkinHullRoot = Fld('Skin.HullRoot', 'h159_1'); // 车身渲染树根
var FSkinParts = Fld('Skin.Parts', 'g17r_1'); // 部件表
var FSkinPartNode = Fld('Skin.PartNode', 'j17r_1'); // 部件 → BP 节点
var FNodeMatrixOld = Fld('Node.MatrixOld', 'n35_1'); // 旧版世界矩阵(兜底)
// 实体 / 向量 / 战斗实例
var FEntityId = Fld('Entity.Id', 'iz1_1'); // 实体 ID
var FEntityRoot = Fld('Entity.Root', 'qz1_1'); // 组件宿主根对象
var FEntityComponentMap = Fld('Entity.ComponentMap', 's2a_1'); // 宿主上的组件 Map
var FVecX = Fld('Vec.X', 'f20_1'); // 向量 X
var FVecY = Fld('Vec.Y', 'g20_1'); // 向量 Y
var FVecZ = Fld('Vec.Z', 'h20_1'); // 向量 Z
var FGameModeTankManager = Fld('GameMode.TankManager', 'm10b_1'); // 坦克管理器
var FTeamId = Fld('Team.Id', 'k3_1'); // 队伍编号
// 炮塔皮肤(LUt / CUt 包装)
var FTurretWrapper = Fld('Turret.Wrapper', 'a11p_1'); // 炮塔组件包装对象
var FTurretInnerWrapper = Fld('Turret.InnerWrapper', 'e11p_1'); // 包装内的 CUt 包装
var FTurretRootNode = Fld('Turret.RootNode', 'a17q_1'); // 炮塔 BP 根节点
// mesh 资源(Tp / J$ / Y$)
var FMeshVertexGroups = Fld('Mesh.VertexGroups', 'x4w_1'); // 顶点组列表
var FMeshVertexBuffer = Fld('Mesh.VertexBuffer', 'h3x_1'); // 顶点缓冲资源
var FMeshGpuBuffer = Fld('Mesh.GpuBuffer', 'v3x_1'); // GPU 缓冲包装
var FMeshVertexCount = Fld('Mesh.VertexCount', 'w3x_1'); // 顶点数
var FMeshCpuVertexData = Fld('Mesh.CpuVertexData', 'x3x_1'); // CPU 端顶点原始数据
var FMeshIndexGroups = Fld('Mesh.IndexGroups', 'y4w_1'); // 索引组列表
var FMeshIndexBuffer = Fld('Mesh.IndexBuffer', 'q4w_1'); // 索引缓冲资源
var FMeshIndexBufferOld = Fld('Mesh.IndexBufferOld', 'u3p_1'); // 旧版索引缓冲字段
var FMeshCpuIndexData = Fld('Mesh.CpuIndexData', 'p3x_1'); // CPU 端索引原始数据
var FMeshIndexGpuBuffer = Fld('Mesh.IndexGpuBuffer', 'n3x_1'); // 索引 GPU 缓冲包装
// 渲染设备 / 原生缓冲
var FRenderDevice = Fld('Render.Device', 's3h_1'); // GL 包装器
var FRenderDeviceNative = Fld('Render.DeviceNative', 'f75_1'); // 原生 WebGL2RenderingContext
var FRenderDrawBuffer = Fld('Render.DrawBuffer', 'd75_1'); // 原生 WebGLBuffer
// ===== 当前模式 =====
var Mode = 'outline'; // 'wireframe' | 'outline'
@@ -250,11 +283,11 @@
var Cached = SkinCompCache.get(entity);
if (Cached) return Cached;
var comp = FindComponentByField(entity, FSkinHullSkin);
if (!comp) comp = FindComponentByField(entity, 'a17q_1');
if (!comp) comp = FindComponentByField(entity, FTurretRootNode);
// 兜底:按结构找 —— 组件字段值是"皮肤对象"(含 g17r_1 部件表)
if (!comp) {
try {
entity.qz1_1.s2a_1.forEach(function (VL) {
entity[FEntityRoot][FEntityComponentMap].forEach(function (VL) {
if (comp) return;
var C = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!C) return;
@@ -281,13 +314,13 @@
// 炮塔 BP 根节点
// 新版:weaponSkin 组件(类 LUt,构造首字段 a11p_1)里 e11p_1 = CUt 包装,其 a17q_1 = 炮塔节点
function GetTurretNode(entity) {
var wp = FindComponentByField(entity, 'a11p_1');
var wp = FindComponentByField(entity, FTurretWrapper);
if (wp) {
if (wp.e11p_1 && wp.e11p_1.a17q_1) return wp.e11p_1.a17q_1;
if (wp[FTurretInnerWrapper] && wp[FTurretInnerWrapper][FTurretRootNode]) return wp[FTurretInnerWrapper][FTurretRootNode];
if (typeof wp.u11p === 'function') { try { return wp.u11p(); } catch (e) {} }
}
var comp = GetSkinComponent(entity);
if (comp && comp.a17q_1) return comp.a17q_1;
if (comp && comp[FTurretRootNode]) return comp[FTurretRootNode];
return null;
}
@@ -314,8 +347,8 @@
// ⚠ 旧字段 d3x_1 / s3h_1.b74_1 / v3x_1.z73_1 在新版分别是"渲染参数 / 雾色 uniform / uniform 槽位",全错
function GetGlFromResource(Res) {
if (!Res) return null;
var Dev = Res.s3h_1;
return (Dev && Dev.f75_1) ? Dev.f75_1 : null;
var Dev = Res[FRenderDevice];
return (Dev && Dev[FRenderDeviceNative]) ? Dev[FRenderDeviceNative] : null;
}
// 把引擎的 CPU 端缓冲区包装转成 TypedArray
@@ -348,17 +381,17 @@
}
function ReadVertexPositionsFromTp(tp) {
if (!tp || !tp.x4w_1 || typeof tp.x4w_1.e1 !== 'function' || tp.x4w_1.e1() === 0) return null;
var group = tp.x4w_1.g1(0);
if (!tp || !tp[FMeshVertexGroups] || typeof tp[FMeshVertexGroups].e1 !== 'function' || tp[FMeshVertexGroups].e1() === 0) return null;
var group = tp[FMeshVertexGroups].g1(0);
if (!group) return null;
var Buf = group.h3x_1; // J$
var Buf = group[FMeshVertexBuffer]; // J$
if (!Buf) return null;
// ① 优先 CPU 端原始数据:未上传 GPU 的 mesh 也能读到(v3x_1.d75_1 此时为 null)
var Cpu = ToTypedArray(Buf.x3x_1, 'f32');
var Cpu = ToTypedArray(Buf[FMeshCpuVertexData], 'f32');
if (Cpu && Cpu.length) return Cpu;
// ② GPU 回读(引擎已上传并释放 CPU 数据时)
var gl = GetGlFromResource(Buf);
var webglBuf = Buf.v3x_1 && Buf.v3x_1.d75_1;
var webglBuf = Buf[FMeshGpuBuffer] && Buf[FMeshGpuBuffer][FRenderDrawBuffer];
if (!gl || !webglBuf) return null;
gl.bindBuffer(gl.ARRAY_BUFFER, webglBuf);
var bufSize = gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE);
@@ -371,21 +404,21 @@
// ===== 从GPU读取索引数据 =====
// y4w_1 = 索引列表,每项 { q4w_1: 索引缓冲资源 Y$ }(旧版是 il.u3p_1)
function ReadIndicesFromTp(tp) {
if (!tp.y4w_1 || typeof tp.y4w_1.e1 !== 'function') return null;
var meshCount = tp.y4w_1.e1();
if (!tp[FMeshIndexGroups] || typeof tp[FMeshIndexGroups].e1 !== 'function') return null;
var meshCount = tp[FMeshIndexGroups].e1();
if (meshCount === 0) return null;
var allIndices = [];
for (var m = 0; m < meshCount; m++) {
var il = tp.y4w_1.g1(m);
var il = tp[FMeshIndexGroups].g1(m);
if (!il) continue;
var IdxBuf = il.q4w_1 || il.u3p_1;
var IdxBuf = il[FMeshIndexBuffer] || il[FMeshIndexBufferOld];
if (!IdxBuf) continue;
// ① CPU 端索引数据(Y$.p3x_1),未上传时也能用
var Cpu = ToTypedArray(IdxBuf.p3x_1, 'u16');
var Cpu = ToTypedArray(IdxBuf[FMeshCpuIndexData], 'u16');
if (Cpu && Cpu.length) { allIndices.push(Cpu); continue; }
// ② GPU 回读
var gl = GetGlFromResource(IdxBuf);
var webglBuf = IdxBuf.n3x_1 && IdxBuf.n3x_1.d75_1;
var webglBuf = IdxBuf[FMeshIndexGpuBuffer] && IdxBuf[FMeshIndexGpuBuffer][FRenderDrawBuffer];
if (!gl || !webglBuf) continue;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webglBuf);
var bufSize = gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE);
@@ -421,8 +454,8 @@
var stride = 0;
// 优先用引擎记录的顶点数反推 stride(J$.w3x_1 = 顶点数)
try {
var G0 = tp.x4w_1.g1(0);
var Vc = G0 && G0.h3x_1 && G0.h3x_1.w3x_1;
var G0 = tp[FMeshVertexGroups].g1(0);
var Vc = G0 && G0[FMeshVertexBuffer] && G0[FMeshVertexBuffer][FMeshVertexCount];
if (Vc > 0 && positions.length % Vc === 0) stride = Math.round(positions.length / Vc);
} catch (e) {}
if (!stride || stride < 3 || stride > 20) {
@@ -450,23 +483,23 @@
GpuFailLogged.add(tp);
var D = {};
try {
var G = tp.x4w_1 && typeof tp.x4w_1.g1 === 'function' ? tp.x4w_1.g1(0) : null;
var B = G && G.h3x_1;
var Dev = B && B.s3h_1;
var I = tp.y4w_1 && typeof tp.y4w_1.e1 === 'function' && tp.y4w_1.e1() > 0 ? tp.y4w_1.g1(0) : null;
var IB = I && (I.q4w_1 || I.u3p_1);
var G = tp[FMeshVertexGroups] && typeof tp[FMeshVertexGroups].g1 === 'function' ? tp[FMeshVertexGroups].g1(0) : null;
var B = G && G[FMeshVertexBuffer];
var Dev = B && B[FRenderDevice];
var I = tp[FMeshIndexGroups] && typeof tp[FMeshIndexGroups].e1 === 'function' && tp[FMeshIndexGroups].e1() > 0 ? tp[FMeshIndexGroups].g1(0) : null;
var IB = I && (I[FMeshIndexBuffer] || I[FMeshIndexBufferOld]);
D = {
顶点组: tp.x4w_1 && tp.x4w_1.e1 ? tp.x4w_1.e1() : -1,
顶点组: tp[FMeshVertexGroups] && tp[FMeshVertexGroups].e1 ? tp[FMeshVertexGroups].e1() : -1,
顶点缓冲资源: !!B,
缓冲包装: !!(B && B.v3x_1),
原生Buffer: !!(B && B.v3x_1 && B.v3x_1.d75_1),
缓冲包装: !!(B && B[FMeshGpuBuffer]),
原生Buffer: !!(B && B[FMeshGpuBuffer] && B[FMeshGpuBuffer][FRenderDrawBuffer]),
设备: !!Dev,
原生GL: !!(Dev && Dev.f75_1),
顶点数: B ? B.w3x_1 : -1,
原生GL: !!(Dev && Dev[FRenderDeviceNative]),
顶点数: B ? B[FMeshVertexCount] : -1,
位置长度: positions ? positions.length : -1,
索引组: tp.y4w_1 && tp.y4w_1.e1 ? tp.y4w_1.e1() : -1,
索引组: tp[FMeshIndexGroups] && tp[FMeshIndexGroups].e1 ? tp[FMeshIndexGroups].e1() : -1,
索引资源: !!IB,
索引Buffer: !!(IB && IB.n3x_1 && IB.n3x_1.d75_1),
索引Buffer: !!(IB && IB[FMeshIndexGpuBuffer] && IB[FMeshIndexGpuBuffer][FRenderDrawBuffer]),
索引个数: indices ? indices.length : -1
};
} catch (e) {}
@@ -555,7 +588,7 @@
if (!Node) return null;
var M = Node[FNodeMatrix];
if (!M || typeof M.length !== 'number' || M.length < 16) {
M = Node.n35_1; // 旧版矩阵字段
M = Node[FNodeMatrixOld]; // 旧版矩阵字段
if (!M || typeof M.length !== 'number' || M.length < 16) return null;
}
return { x: M[12], y: M[13], z: M[14] };
@@ -622,7 +655,7 @@
Srcs.push({ Comp: Comp, Path: Path });
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
tank[FEntityRoot][FEntityComponentMap].forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!IsPlainObj(comp)) return;
for (var K in comp) {
@@ -730,9 +763,9 @@
function IsNodeValid(bpNode, pos, tankIsMoving, diagStats) {
// 世界矩阵:新版是 p35_1(zl 基类构造里 = hl(),s3k/t3k/u3k 读 p35_1[12..14])
// 旧版是 n35_1;新版 n35_1 已变成布尔标志
var M = bpNode.p35_1;
var M = bpNode[FNodeMatrix];
if (!M || typeof M.length !== 'number' || M.length < 16) {
var OldM = bpNode.n35_1;
var OldM = bpNode[FNodeMatrixOld];
M = (OldM && typeof OldM.length === 'number' && OldM.length >= 16) ? OldM : null;
}
if (!M) return null;
@@ -1084,7 +1117,7 @@
for (var K in Obj) {
try {
var V = Obj[K];
if (V && typeof V === 'object' && typeof V.k3_1 === 'number' && typeof V.equals === 'function') return V;
if (V && typeof V === 'object' && typeof V[FTeamId] === 'number' && typeof V.equals === 'function') return V;
} catch (e) {}
}
return null;
@@ -1094,7 +1127,7 @@
function IsGoldBoxEntity(Entity) {
if (!Entity || !Entity.j10q_1) return false;
var Type = FindEnumOf(Entity.j10q_1);
return !!(Type && Type.k3_1 === GOLD_BOX_TYPE);
return !!(Type && Type[FTeamId] === GOLD_BOX_TYPE);
}
function RegisterGoldBox(Entity) {
@@ -1132,12 +1165,12 @@
try {
var V = Bonus[K];
if (!V || typeof V !== 'object') continue;
if (V.n35_1 && V.n35_1.length >= 15) {
var M = V.n35_1;
if (V[FNodeMatrixOld] && V[FNodeMatrixOld].length >= 15) {
var M = V[FNodeMatrixOld];
return { x: M[12], y: M[13], z: M[14] };
}
if (typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') {
return { x: V.f20_1, y: V.g20_1, z: V.h20_1 };
if (typeof V[FVecX] === 'number' && typeof V[FVecY] === 'number' && typeof V[FVecZ] === 'number') {
return { x: V[FVecX], y: V[FVecY], z: V[FVecZ] };
}
} catch (e) {}
}
@@ -1252,7 +1285,7 @@
UpdateCam(camInfo, screenW, screenH); // 每帧只算一次相机基(需在相机位置兜底之后),供顶点变换/投影复用
var tankList = gm.m10b_1.j11y();
var tankList = gm[FGameModeTankManager].j11y();
var count = tankList.e1();
var drawnCount = 0, botDrawn = 0;
@@ -1279,7 +1312,7 @@
// 本帧该坦克是否在移动(供「残留节点」检测:坦克在动而节点矩阵不动 = 旧位置遗留)
// 用位移的指数滑动平均,而不是「单帧位移 > 10 单位」——后者对慢速移动几乎永远为假,
// 会让残留检测形同虚设(残留漏掉的根因就在这里)
var tankId = tank.iz1_1;
var tankId = tank[FEntityId];
var prevPos = TankPrevPos.get(tankId);
var tankMove = 0;
if (prevPos) {
@@ -1297,7 +1330,7 @@
if (distance > 50000) continue;
var friendly = IsFriendly(tank, playerTank);
var tankName = GetTankName(tank) || ('ID:' + tank.iz1_1);
var tankName = GetTankName(tank) || ('ID:' + tank[FEntityId]);
// 根据 HighlightMode 判断是否渲染该坦克
if (!ShouldRender(tankName)) continue;
// 颜色按队伍:友方=蓝,敌方=红
+49 -27
View File
@@ -4,6 +4,28 @@
// 独立轻量级脚本,不依赖3D透视渲染
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// ===== 匿名字段常量 =====
var FEntityId = Fld('Entity.Id', 'iz1_1');
var FEntityRoot = Fld('Entity.Root', 'qz1_1');
var FEntityComponentMap = Fld('Entity.ComponentMap', 's2a_1');
var MGameModeScene = Mth('GameMode.Scene', 'lz4');
var MCollisionRaycast = Mth('CollisionWorld.Raycast', 'a17p');
var FWeaponComponent = Fld('Weapon.Component', 'n11c_1');
var FWeaponComponentAlt = Fld('Weapon.ComponentAlt', 'q112_1');
var FWeaponTurretRoot = Fld('Weapon.TurretRoot', 'm17a_1');
var FNodeMatrix = Fld('Node.MatrixOld', 'n35_1');
var FRayHitPoint = Fld('Ray.HitPoint', 'y178_1');
var FRayHitNormal = Fld('Ray.HitNormal', 'z178_1');
var FRayDistance = Fld('Ray.Distance', 'a179_1');
var FRayHitShape = Fld('Ray.HitShape', 'q17o_1');
var FRayHitBody = Fld('Ray.HitBody', 'b179_1');
var FRayFilterQuery = Fld('RayFilter.Query', 'l1da_1');
function _start() {
console.log('===== 3DTank瞄准警告启动 =====');
@@ -70,9 +92,9 @@
// ===== 获取 TDt 武器皮肤组件(炮塔) =====
function GetTDtComponent(entity) {
var comp = FindComponentByField(entity, 'n11c_1');
var comp = FindComponentByField(entity, FWeaponComponent);
if (comp) return comp;
comp = FindComponentByField(entity, 'q112_1');
comp = FindComponentByField(entity, FWeaponComponentAlt);
return comp;
}
@@ -85,9 +107,9 @@
// 返回 { angle: 炮塔朝向与"敌方→玩家"方向的夹角, distance: 距离, turretAngle: 炮塔朝向角度, turretDir: {x,y} }
function GetTurretAimAngle(tank, enemyPos, playerPos) {
var tdt = GetTDtComponent(tank);
if (!tdt || !tdt.n11c_1 || !tdt.n11c_1.m17a_1) return null;
var bpNode = tdt.n11c_1.m17a_1;
var m = bpNode.n35_1;
if (!tdt || !tdt[FWeaponComponent] || !tdt[FWeaponComponent][FWeaponTurretRoot]) return null;
var bpNode = tdt[FWeaponComponent][FWeaponTurretRoot];
var m = bpNode[FNodeMatrix];
if (!m) return null;
// 敌方→玩家方向(x,y 水平面)
@@ -127,13 +149,13 @@
function GetUMtComponent() {
if (UMtComponent) return UMtComponent;
try {
var gmEntity = gm.lz4();
if (!gmEntity || !gmEntity.qz1_1 || !gmEntity.qz1_1.s2a_1) return null;
gmEntity.qz1_1.s2a_1.forEach(function(valueList, key) {
var gmEntity = gm[MGameModeScene]();
if (!gmEntity || !gmEntity[FEntityRoot] || !gmEntity[FEntityRoot][FEntityComponentMap]) return null;
gmEntity[FEntityRoot][FEntityComponentMap].forEach(function(valueList, key) {
if (UMtComponent) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.a17p === 'function') UMtComponent = comp;
if (comp && typeof comp[MCollisionRaycast] === 'function') UMtComponent = comp;
} catch(e) {}
});
if (UMtComponent) console.log('[瞄准] uMt 碰撞检测器已获取');
@@ -175,30 +197,30 @@
var maxDist = dist - 300;
var result = {
y178_1: MkVec(0,0,0),
z178_1: MkVec(0,0,0),
a179_1: maxDist,
q17o_1: null,
b179_1: null,
[FRayHitPoint]: MkVec(0,0,0),
[FRayHitNormal]: MkVec(0,0,0),
[FRayDistance]: maxDist,
[FRayHitShape]: null,
[FRayHitBody]: null,
r17o: function(t, n, i, r, e) {
this.y178_1.k26(t);
this.z178_1.k26(n);
this.a179_1 = i;
this.q17o_1 = r;
this.b179_1 = e;
this[FRayHitPoint].k26(t);
this[FRayHitNormal].k26(n);
this[FRayDistance] = i;
this[FRayHitShape] = r;
this[FRayHitBody] = e;
},
d3: function() { this.b179_1 = null; }
d3: function() { this[FRayHitBody] = null; }
};
var filter = {
l1da_1: { h1: function() { return false; }, d3: function() {} },
[FRayFilterQuery]: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() { this.l1da_1.d3(); },
d3: function() { this[FRayFilterQuery].d3(); },
m1da: function() {}
};
var hit = uMt.a17p(startVec, dirVec, maxDist, result, filter);
blocked = !!hit && result.a179_1 < (maxDist - 50);
var hit = uMt[MCollisionRaycast](startVec, dirVec, maxDist, result, filter);
blocked = !!hit && result[FRayDistance] < (maxDist - 50);
RaycastFailCount = 0;
} catch(e) {
RaycastFailCount++;
@@ -310,19 +332,19 @@
if (aimInfo.angle >= aimThreshold) continue;
// 墙体阻隔判断
var blocked = IsLineOfSightBlocked(pos, playerPos, tank.iz1_1, frameCount);
var blocked = IsLineOfSightBlocked(pos, playerPos, tank[FEntityId], frameCount);
if (blocked) continue;
// 记录威胁
var proj = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var tankName = GetTankName(tank) || ('ID:' + tank.iz1_1);
var tankName = GetTankName(tank) || ('ID:' + tank[FEntityId]);
threats.push({
pos: pos,
distance: aimInfo.distance,
angle: aimInfo.angle,
turretDir: aimInfo.turretDir,
screenProj: proj,
tankId: tank.iz1_1,
tankId: tank[FEntityId],
tankName: tankName
});
}
+40 -23
View File
@@ -4,6 +4,23 @@
// 原理:Hook mainCamera.h37,在相机矩阵更新前覆盖l35_1/k35_1参数
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// ===== 匿名字段常量 =====
var FVecX = Fld('Vec.X', 'f20_1');
var FVecY = Fld('Vec.Y', 'g20_1');
var FVecZ = Fld('Vec.Z', 'h20_1');
var FCameraSceneRef = Fld('Camera.SceneRef', 'jz1_1');
var FCameraObject = Fld('Camera.Object', 'iyx_1');
var FCameraEuler = Fld('Camera.Euler', 'u35_1');
var FCameraPosition = Fld('Node.Children', 'r35_1');
var FViewMatrix = Fld('Camera.Fov', 'b37_1');
var FCameraSceneFx = Fld('Camera.SceneFx', 'd75_1');
var MGameModeScene = Mth('GameMode.Scene', 'lz4');
function _start() {
console.log('===== 3DTank自由视角启动 =====');
@@ -33,15 +50,15 @@
var initError = null;
try {
scene = gm.lz4().jz1_1;
mainCamera = scene.iyx_1;
ff = scene.gyw_1.d75_1;
if (!mainCamera || !mainCamera.u35_1 || !mainCamera.r35_1 || !mainCamera.o37) {
scene = gm[MGameModeScene]()[FCameraSceneRef];
mainCamera = scene[FCameraObject];
ff = scene.gyw_1[FCameraSceneFx];
if (!mainCamera || !mainCamera[FCameraEuler] || !mainCamera[FCameraPosition] || !mainCamera.o37) {
throw new Error('相机字段缺失');
}
console.log('[自由视角] 主相机:', mainCamera.constructor.name,
'l35=(' + mainCamera.u35_1.f20_1.toFixed(2) + ',' + mainCamera.u35_1.g20_1.toFixed(2) + ',' + mainCamera.u35_1.h20_1.toFixed(2) + ')',
'k35=(' + mainCamera.r35_1.f20_1.toFixed(0) + ',' + mainCamera.r35_1.g20_1.toFixed(0) + ',' + mainCamera.r35_1.h20_1.toFixed(0) + ')');
'l35=(' + mainCamera[FCameraEuler][FVecX].toFixed(2) + ',' + mainCamera[FCameraEuler][FVecY].toFixed(2) + ',' + mainCamera[FCameraEuler][FVecZ].toFixed(2) + ')',
'k35=(' + mainCamera[FCameraPosition][FVecX].toFixed(0) + ',' + mainCamera[FCameraPosition][FVecY].toFixed(0) + ',' + mainCamera[FCameraPosition][FVecZ].toFixed(0) + ')');
} catch(e) {
initError = e;
console.error('[自由视角] 初始化失败:', e.message);
@@ -67,12 +84,12 @@
var origH37 = mainCamera.o37;
function applyFreeLookParams() {
mainCamera.u35_1.f20_1 = freePitch; // pitch (f)
mainCamera.u35_1.g20_1 = freeRoll; // roll (g) 保持初始值
mainCamera.u35_1.h20_1 = freeYaw; // yaw (h) 鼠标左右控制
mainCamera.r35_1.f20_1 = freePosX;
mainCamera.r35_1.g20_1 = freePosY;
mainCamera.r35_1.h20_1 = freePosZ;
mainCamera[FCameraEuler][FVecX] = freePitch; // pitch (f)
mainCamera[FCameraEuler][FVecY] = freeRoll; // roll (g) 保持初始值
mainCamera[FCameraEuler][FVecZ] = freeYaw; // yaw (h) 鼠标左右控制
mainCamera[FCameraPosition][FVecX] = freePosX;
mainCamera[FCameraPosition][FVecY] = freePosY;
mainCamera[FCameraPosition][FVecZ] = freePosZ;
}
// 缓存从 viewMatrix 提取的前方向/右方向(h37 更新后设置)
@@ -89,11 +106,11 @@
applyFreeLookParams();
origH37.call(this);
// 从更新后的 viewMatrix (u36_1) 提取前方向和右方向(水平分量 x,y)
if (this.b37_1) {
cachedFwdX = -this.b37_1[2]; // forward.x
cachedFwdY = -this.b37_1[6]; // forward.y
cachedRightX = this.b37_1[0]; // right.x
cachedRightY = this.b37_1[4]; // right.y
if (this[FViewMatrix]) {
cachedFwdX = -this[FViewMatrix][2]; // forward.x
cachedFwdY = -this[FViewMatrix][6]; // forward.y
cachedRightX = this[FViewMatrix][0]; // right.x
cachedRightY = this[FViewMatrix][4]; // right.y
cachedInitialized = true;
}
} else {
@@ -106,12 +123,12 @@
function enableFreeLook() {
// 从当前相机参数初始化(平滑过渡)
// l35_1: f=pitch, g=roll, h=yaw
freePitch = mainCamera.u35_1.f20_1;
freeRoll = mainCamera.u35_1.g20_1; // roll 保持初始值
freeYaw = mainCamera.u35_1.h20_1; // yaw 鼠标控制
freePosX = mainCamera.r35_1.f20_1;
freePosY = mainCamera.r35_1.g20_1;
freePosZ = mainCamera.r35_1.h20_1;
freePitch = mainCamera[FCameraEuler][FVecX];
freeRoll = mainCamera[FCameraEuler][FVecY]; // roll 保持初始值
freeYaw = mainCamera[FCameraEuler][FVecZ]; // yaw 鼠标控制
freePosX = mainCamera[FCameraPosition][FVecX];
freePosY = mainCamera[FCameraPosition][FVecY];
freePosZ = mainCamera[FCameraPosition][FVecZ];
freeLookActive = true;
console.log('[自由视角] 已开启 — WASD移动 | 鼠标旋转 | 空格上升/Shift下降 | 滚轮调速 | Alt+F关闭');
updateHud();
+126 -80
View File
@@ -4,6 +4,11 @@
// 用法:各工具文件通过 var lib = window.__hornetLib__ 访问
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
console.log('===== 3DTank公共库加载 =====');
if (!window.__gameMode__) {
@@ -13,17 +18,58 @@
// ===== 缓存队伍判断函数(ZCt/m2Fn) =====
var ZCt = null, m2Fn = null;
// ===== 字段抽象层引用(字段名统一在 hornet_schema.js 里维护) =====
// 取不到抽象层时退回本地常量,保证单独加载本文件也能工作
var Schema = window.__hornetSchema__ || null;
function FieldOf(Key, Fallback) {
if (!Schema) return Fallback;
var P = Schema.PathOf(Key);
return (P && P.indexOf('.') < 0 && P.indexOf('(') < 0) ? P : Fallback;
}
var TEAM_FIELD = FieldOf('Team.Field', 'b135_1'); // 队伍组件上的队伍对象字段
var TEAM_ID_FIELD = FieldOf('Team.Id', 'k3_1'); // 队伍编号:0/1/2(2=中立)
// ===== 语义字段常量(取不到抽象层时用 fallback 字面名,保证单独加载也能工作) =====
// 实体
var FEntityRoot = Fld('Entity.Root', 'qz1_1'); // 组件宿主根对象
var FEntityComponentMap = Fld('Entity.ComponentMap', 's2a_1'); // 宿主上的组件 Map
var FEntityId = Fld('Entity.Id', 'iz1_1'); // 实体 ID
// 向量
var FVecX = Fld('Vec.X', 'f20_1');
var FVecY = Fld('Vec.Y', 'g20_1');
var FVecZ = Fld('Vec.Z', 'h20_1');
// 队伍
var FTeamField = Fld('Team.Field', 'b135_1'); // 队伍组件上的队伍对象字段
var FTeamId = Fld('Team.Id', 'k3_1'); // 队伍编号:0/1/2(2=中立)
var NEUTRAL_TEAM_ID = 2;
// 物理
var FPhysicsPosition = Fld('Physics.Position', 'bzr_1'); // 世界位置向量
var FPhysicsPositionOld1 = Fld('Physics.PositionOld1', 'gzh_1'); // 旧版位置字段名(兜底候选)
var FPhysicsPositionOld2 = Fld('Physics.PositionOld2', 'pz7_1'); // 更旧位置字段名(兜底候选)
// 坦克状态
var FTankFsm = Fld('Tank.Fsm', 'p10c_1'); // 状态机字段(组件识别用)
var FTankState = Fld('Tank.State', 'k10c_1'); // 状态枚举对象
var FTankStateValue = Fld('Tank.StateValue', 'k3_1'); // 状态数值
// 用户信息 / 血量 / 装备分
var FUserName = Fld('User.Name', 'c12n_1');
var FUserClan = Fld('User.Clan', 'e12n_1');
var FHealthMax = Fld('Health.Max', 'p12o_1');
var FHealthCurrent = Fld('Health.Current', 'q12o_1');
var FGearScore = Fld('Gear.Score', 'cjf_1');
// GameMode
var FGameModeTankManager = Fld('GameMode.TankManager', 'm10b_1');
var FGameModeOwnedTank = Fld('GameMode.OwnedTank', 'u10b_1');
var FGameModeObservedTank = Fld('GameMode.ObservedTank', 't10b_1');
var FGameModeLocalTeam = Fld('GameMode.LocalTeam', 'v10b_1');
// 相机
var FCameraSceneRef = Fld('Camera.SceneRef', 'jz1_1');
var FCameraObject = Fld('Camera.Object', 'iyx_1');
var FCameraPosition = Fld('Camera.Position', 't35_1');
var FCameraViewMatrix = Fld('Camera.ViewMatrix', 'd37_1');
var FCameraClipMatrix = Fld('Camera.ClipMatrix', 'g37_1');
var FCameraViewport = Fld('Camera.Viewport', 'w36_1');
var FCameraFov = Fld('Camera.Fov', 'b37_1');
// 射线检测结果 / 过滤器
var FRayStartPoint = Fld('Ray.StartPoint', 'y178_1');
var FRayEndPoint = Fld('Ray.EndPoint', 'z178_1');
var FRayDistance = Fld('Ray.Distance', 'a179_1');
var FRayHitEntity = Fld('Ray.HitEntity', 'q17o_1');
var FRayHitShape = Fld('Ray.HitShape', 'b179_1');
var FRayFilterContext = Fld('RayFilter.Context', 'l1da_1');
// 方法
var MGameModeScene = Mth('GameMode.Scene', 'lz4'); // 场景实体
var MTankList = Mth('GameMode.TankList', 'j11y'); // 坦克列表
var MIsTankAlive = Mth('Tank.IsAlive', 's14j'); // 引擎可见性判定
var MRaycast = Mth('Ray.Cast', 'a17p'); // 全局射线检测
// 加载队伍判断函数(从 module_87072 导出)
// 新旧差异(main.7cfa4058):
@@ -57,9 +103,9 @@
// 遍历 entity.wyj_1.r2a_1(Map<组件标识, 组件列表>),value 是列表类(s_),g1(0) 获取实际组件
// 注意:用 !== undefined 检查字段存在性,避免 falsy 值(0/""/null/false)漏判
function FindComponentByField(entity, fieldName) {
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) return null;
if (!entity || !entity[FEntityRoot] || !entity[FEntityRoot][FEntityComponentMap]) return null;
var result = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
entity[FEntityRoot][FEntityComponentMap].forEach(function(valueList, key) {
if (result) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
@@ -77,18 +123,18 @@
// njt() 里 `bzr_1.l27(上一物理态位置, 当前物理态位置, 插值)` —— bzr_1 就是世界位置。
// ⚠ xzq_1 是车体半尺寸(用于包围盒/出生点:`-xzq_1.h20_1/2 + 10`),绝不能当位置用。
// 返回 {x, y, z},x/y 水平面,z 高度
var POS_FIELDS = ['bzr_1', 'gzh_1', 'pz7_1'];
var POS_FIELDS = [FPhysicsPosition, FPhysicsPositionOld1, FPhysicsPositionOld2];
function GetTankPosition(tank) {
for (var i = 0; i < POS_FIELDS.length; i++) {
var Key = POS_FIELDS[i];
var Comp = null;
try { Comp = FindComponentByField(tank, Key); } catch (e) { Comp = null; }
var V = Comp && Comp[Key];
if (V && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') {
if (V && typeof V[FVecX] === 'number' && typeof V[FVecY] === 'number' && typeof V[FVecZ] === 'number') {
// 数值有效性校验:NaN/超大值一律视为读取失败(避免雷达画到画布外)
if (isFinite(V.f20_1) && isFinite(V.g20_1) && isFinite(V.h20_1) &&
Math.abs(V.f20_1) < 1e7 && Math.abs(V.g20_1) < 1e7 && Math.abs(V.h20_1) < 1e7) {
return { x: V.f20_1, y: V.g20_1, z: V.h20_1 };
if (isFinite(V[FVecX]) && isFinite(V[FVecY]) && isFinite(V[FVecZ]) &&
Math.abs(V[FVecX]) < 1e7 && Math.abs(V[FVecY]) < 1e7 && Math.abs(V[FVecZ]) < 1e7) {
return { x: V[FVecX], y: V[FVecY], z: V[FVecZ] };
}
}
}
@@ -98,13 +144,13 @@
// ===== 坦克组件(lxt / TankComponent)=====
// 特征:有 k10c_1(状态枚举)与 p10c_1(fsm)
function GetTankComponent(tank) {
if (!tank || !tank.qz1_1 || !tank.qz1_1.s2a_1) return null;
if (!tank || !tank[FEntityRoot] || !tank[FEntityRoot][FEntityComponentMap]) return null;
var Found = null;
tank.qz1_1.s2a_1.forEach(function(valueList, key) {
tank[FEntityRoot][FEntityComponentMap].forEach(function(valueList, key) {
if (Found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && comp.p10c_1 && comp.k10c_1 !== undefined) Found = comp;
if (comp && comp[FTankFsm] && comp[FTankState] !== undefined) Found = comp;
} catch(e) {}
});
return Found;
@@ -114,7 +160,7 @@
// 状态枚举:0=DEAD, 1=DEAD_PHANTOM, 2=SEMI_ACTIVE, 3=ACTIVE, 4=DISABLED, 5=TELEPORT
function GetTankState(tank) {
var C = GetTankComponent(tank);
return (C && C.k10c_1) ? C.k10c_1.k3_1 : -1;
return (C && C[FTankState]) ? C[FTankState][FTankStateValue] : -1;
}
// 可见/战斗中的坦克:**直接调用引擎自己的判定 s14j()**
@@ -122,7 +168,7 @@
// 旧实现只认 2/3,会漏掉传送态;死亡态 0/1/4 会被正确排除
function IsTankAlive(tank) {
var C = GetTankComponent(tank);
try { if (C && typeof C.s14j === 'function') return !!C.s14j(); } catch (e) {}
try { if (C && typeof C[MIsTankAlive] === 'function') return !!C[MIsTankAlive](); } catch (e) {}
var s = GetTankState(tank);
return s === 2 || s === 3 || s === 5;
}
@@ -130,20 +176,20 @@
// ===== 获取坦克名称(yot 用户信息组件) =====
// s120_1 = userName, u120_1 = clanTag(可为null), t120_1 = userRank
function GetTankName(tank) {
var userComp = FindComponentByField(tank, 'c12n_1');
var userComp = FindComponentByField(tank, FUserName);
if (!userComp) return null;
var name = userComp.c12n_1 || '';
var clanTag = userComp.e12n_1 || '';
var name = userComp[FUserName] || '';
var clanTag = userComp[FUserClan] || '';
return clanTag ? (clanTag + ' ' + name) : name;
}
// ===== 获取坦克血量(Vkt 血量组件) =====
// f122_1 = 最大血量(max), g122_1 = 当前血量(current, 死亡时=0)
function GetTankHealth(tank) {
var healthComp = FindComponentByField(tank, 'q12o_1');
var healthComp = FindComponentByField(tank, FHealthCurrent);
if (!healthComp) return null;
var max = healthComp.p12o_1 || 0;
var cur = healthComp.q12o_1 || 0;
var max = healthComp[FHealthMax] || 0;
var cur = healthComp[FHealthCurrent] || 0;
if (max <= 0) return null;
return { current: cur, max: max, percent: cur / max };
}
@@ -151,9 +197,9 @@
// ===== 装备分数(战斗 GS:BattleGearScoreCC.score 落在组件的 cjf_1 上) =====
// 来源:module_85183 的 BattleGearScoreCC(score = cjf_1);战斗内每辆坦克各有一份
function GetGearScore(tank) {
var c = FindComponentByField(tank, 'cjf_1');
if (!c || typeof c.cjf_1 !== 'number') return null;
return c.cjf_1;
var c = FindComponentByField(tank, FGearScore);
if (!c || typeof c[FGearScore] !== 'number') return null;
return c[FGearScore];
}
// ===== 人机识别 =====
@@ -166,10 +212,10 @@
var MaxScore = typeof opt.maxScore === 'number' ? opt.maxScore : 8000;
BotSet = new Set();
var gm = window.__gameMode__;
if (!gm || !gm.m10b_1) return BotSet;
if (!gm || !gm[FGameModeTankManager]) return BotSet;
var playerTank = GetPlayerTank(gm);
var list, n;
try { list = gm.m10b_1.j11y(); n = list.e1(); } catch (e) { return BotSet; }
try { list = gm[FGameModeTankManager][MTankList](); n = list.e1(); } catch (e) { return BotSet; }
var Groups = { ally: {}, enemy: {} }; // 组 → { 分数 → [坦克...] }
for (var i = 0; i < n; i++) {
var tank = list.g1(i);
@@ -197,9 +243,9 @@
var g = window.__gameMode__;
R.gm = !!g;
if (!g) return R;
try { R.scene = !!g.lz4().jz1_1; } catch (e) { R.errors.push('scene: ' + e.message); }
try { R.scene = !!g[MGameModeScene]()[FCameraSceneRef]; } catch (e) { R.errors.push('scene: ' + e.message); }
var list = null;
try { list = g.m10b_1.j11y(); R.tanks = list.e1(); } catch (e) { R.errors.push('tankList: ' + e.message); }
try { list = g[FGameModeTankManager][MTankList](); R.tanks = list.e1(); } catch (e) { R.errors.push('tankList: ' + e.message); }
if (list) {
var N = Math.min(list.e1(), 8);
for (var i = 0; i < N; i++) {
@@ -213,7 +259,7 @@
for (var j = 0; j < POS_FIELDS.length; j++) {
var K = POS_FIELDS[j], C = null;
try { C = FindComponentByField(T, K); } catch (e) { C = null; }
if (C && C[K] && typeof C[K].f20_1 === 'number') { Src = K; break; }
if (C && C[K] && typeof C[K][FVecX] === 'number') { Src = K; break; }
}
R.posHit[Src] = (R.posHit[Src] || 0) + 1;
} else {
@@ -221,13 +267,13 @@
// 失败时把该坦克组件的字段名与"候选向量"打印出来,便于定位
try {
var Fields = [];
T.qz1_1.s2a_1.forEach(function (VL) {
T[FEntityRoot][FEntityComponentMap].forEach(function (VL) {
var C = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!C) return;
for (var K2 in C) {
var V2 = C[K2];
if (V2 && typeof V2 === 'object') {
var Kind = (typeof V2.f20_1 === 'number') ? 'O$' : (typeof V2.length === 'number' && V2.length >= 15 ? 'M' : '');
var Kind = (typeof V2[FVecX] === 'number') ? 'O$' : (typeof V2.length === 'number' && V2.length >= 15 ? 'M' : '');
if (Kind) Fields.push(K2 + ':' + Kind);
}
}
@@ -248,12 +294,12 @@
// checkPosition: 是否检查位置有效性(默认 true)
function GetPlayerTank(gm, checkPosition) {
if (checkPosition === undefined) checkPosition = true;
var t = gm.u10b_1;
var t = gm[FGameModeOwnedTank];
if (t && (!checkPosition || GetTankPosition(t))) return t;
t = gm.t10b_1;
t = gm[FGameModeObservedTank];
if (t && (!checkPosition || GetTankPosition(t))) return t;
try {
var tl = gm.m10b_1.j11y();
var tl = gm[FGameModeTankManager][MTankList]();
if (tl.e1() > 0) {
t = tl.g1(0);
if (t && (!checkPosition || GetTankPosition(t))) return t;
@@ -278,8 +324,8 @@
if (ZCt) {
try { var T = ZCt(tank); if (T) return T; } catch (e) {}
}
var Comp = FindComponentByField(tank, TEAM_FIELD);
return Comp ? Comp[TEAM_FIELD] : null;
var Comp = FindComponentByField(tank, FTeamField);
return Comp ? Comp[FTeamField] : null;
}
// 读队伍编号:0 / 1 = 两个阵营,2 = 中立(q$());读不到返回 -1
@@ -287,7 +333,7 @@
// (module_87072 里 P7()/$ct() 都按 k3_1 分派)
function GetTeamId(Team) {
if (!Team) return -1;
var Id = Team[TEAM_ID_FIELD];
var Id = Team[FTeamId];
return (typeof Id === 'number') ? Id : -1;
}
@@ -296,9 +342,9 @@
function RememberLocalTeam(gm) {
var t = null;
try { t = gm.u10b_1; } catch (e) { t = null; }
try { t = gm[FGameModeOwnedTank]; } catch (e) { t = null; }
if (t) {
var Id = t.iz1_1;
var Id = t[FEntityId];
if (LocalTeam && LocalTeamTankId === Id) return;
var Team = ReadTeamRaw(t);
var TeamId = GetTeamId(Team);
@@ -308,7 +354,7 @@
// u10b_1 为空(死亡/观战):保持已有缓存;若还没缓存过,再试 GameMode 上的本地队伍字段
if (LocalTeam) return;
try {
var G = gm.v10b_1;
var G = gm[FGameModeLocalTeam];
var GId = GetTeamId(G);
if (GId >= 0 && GId !== NEUTRAL_TEAM_ID) LocalTeam = G;
} catch (e) {}
@@ -334,7 +380,7 @@
// 可靠基准:本地拥有坦克的队伍(缓存)→ GameMode 上的本地队伍字段
// 观战目标(t10b_1)可能是敌人,绝不作为基准
var BaseTeam = LocalTeam;
if (!BaseTeam && gm) { try { BaseTeam = gm.v10b_1; } catch (e) {} }
if (!BaseTeam && gm) { try { BaseTeam = gm[FGameModeLocalTeam]; } catch (e) {} }
var BaseId = GetTeamId(BaseTeam);
if (BaseId < 0 || BaseId === NEUTRAL_TEAM_ID) {
var Last2 = TankFriendCache.get(tank); // 基准不可靠(极早期):沿用上次
@@ -346,7 +392,7 @@
if (!TeamJudgeLogged.has(tank)) {
TeamJudgeLogged.add(tank);
try {
console.log('[敌我] id=' + tank.iz1_1 + ' 队伍=' + TankId + ' 本地=' + BaseId +
console.log('[敌我] id=' + tank[FEntityId] + ' 队伍=' + TankId + ' 本地=' + BaseId +
' → ' + (Friendly ? '友方' : '敌方') + ' name=' + (GetTankName(tank) || '?'));
} catch (e) {}
}
@@ -367,12 +413,12 @@
// heading 供雷达使用(forward = (sin(heading), cos(heading)),水平面)
function GetCameraInfo(gm) {
try {
var scene = gm.lz4().jz1_1;
var cam = scene && scene.iyx_1;
if (!cam || !cam.t35_1) return null;
var P = cam.t35_1;
var pos = { x: P.f20_1, y: P.g20_1, z: P.h20_1 };
var V = cam.d37_1;
var scene = gm[MGameModeScene]()[FCameraSceneRef];
var cam = scene && scene[FCameraObject];
if (!cam || !cam[FCameraPosition]) return null;
var P = cam[FCameraPosition];
var pos = { x: P[FVecX], y: P[FVecY], z: P[FVecZ] };
var V = cam[FCameraViewMatrix];
var fx = 0, fy = 1, fz = 0;
if (V && typeof V.length === 'number' && V.length >= 11) {
fx = -V[2]; fy = -V[6]; fz = -V[10];
@@ -380,14 +426,14 @@
fx /= len; fy /= len; fz /= len;
}
var viewW = 0, viewH = 0;
try { viewW = cam.w36_1.h29(); viewH = cam.w36_1.g29(); } catch (e) {}
try { viewW = cam[FCameraViewport].h29(); viewH = cam[FCameraViewport].g29(); } catch (e) {}
return {
pos: pos,
yaw: Math.atan2(-fx, fy),
pitch: Math.asin(Math.max(-1, Math.min(1, fz))),
heading: Math.atan2(fx, fy),
fov: cam.b37_1 || 1.0471976,
matrix: cam.g37_1 || null,
fov: cam[FCameraFov] || 1.0471976,
matrix: cam[FCameraClipMatrix] || null,
viewW: viewW,
viewH: viewH
};
@@ -400,14 +446,14 @@
// g16x(startVec, dirVec, maxDist, resultObj, filterObj) = 全局射线检测
function GetUMtComponent(gm) {
try {
var gmEntity = gm.lz4();
if (!gmEntity || !gmEntity.qz1_1 || !gmEntity.qz1_1.s2a_1) return null;
var gmEntity = gm[MGameModeScene]();
if (!gmEntity || !gmEntity[FEntityRoot] || !gmEntity[FEntityRoot][FEntityComponentMap]) return null;
var uMt = null;
gmEntity.qz1_1.s2a_1.forEach(function(valueList, key) {
gmEntity[FEntityRoot][FEntityComponentMap].forEach(function(valueList, key) {
if (uMt) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.a17p === 'function') uMt = comp;
if (comp && typeof comp[MRaycast] === 'function') uMt = comp;
} catch(e) {}
});
return uMt;
@@ -419,9 +465,9 @@
// ===== 构造 mock O$ 向量(用于射线检测接口) =====
function MkVec(x, y, z) {
return {
f20_1: x || 0, g20_1: y || 0, h20_1: z || 0,
k26: function(t) { this.f20_1 = t.f20_1; this.g20_1 = t.g20_1; this.h20_1 = t.h20_1; },
n23: function(x, y, z) { this.f20_1 = x; this.g20_1 = y; this.h20_1 = z; return this; }
[FVecX]: x || 0, [FVecY]: y || 0, [FVecZ]: z || 0,
k26: function(t) { this[FVecX] = t[FVecX]; this[FVecY] = t[FVecY]; this[FVecZ] = t[FVecZ]; },
n23: function(x, y, z) { this[FVecX] = x; this[FVecY] = y; this[FVecZ] = z; return this; }
};
}
@@ -434,28 +480,28 @@
var startVec = MkVec(fromPos.x, fromPos.y, fromPos.z);
var dirVec = MkVec(dirX, dirY, dirZ);
var result = {
y178_1: MkVec(0, 0, 0),
z178_1: MkVec(0, 0, 0),
a179_1: maxDist,
q17o_1: null,
b179_1: null,
[FRayStartPoint]: MkVec(0, 0, 0),
[FRayEndPoint]: MkVec(0, 0, 0),
[FRayDistance]: maxDist,
[FRayHitEntity]: null,
[FRayHitShape]: null,
r17o: function(t, n, i, r, e) {
this.y178_1.k26(t);
this.z178_1.k26(n);
this.a179_1 = i;
this.q17o_1 = r;
this.b179_1 = e;
this[FRayStartPoint].k26(t);
this[FRayEndPoint].k26(n);
this[FRayDistance] = i;
this[FRayHitEntity] = r;
this[FRayHitShape] = e;
},
d3: function() { this.b179_1 = null; }
d3: function() { this[FRayHitShape] = null; }
};
var filter = {
l1da_1: { h1: function() { return false; }, d3: function() {} },
[FRayFilterContext]: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() { this.l1da_1.d3(); },
d3: function() { this[FRayFilterContext].d3(); },
m1da: function() {}
};
var hit = uMt.a17p(startVec, dirVec, maxDist, result, filter);
return (hit && result.a179_1 < maxDist) ? result.a179_1 : -1;
var hit = uMt[MRaycast](startVec, dirVec, maxDist, result, filter);
return (hit && result[FRayDistance] < maxDist) ? result[FRayDistance] : -1;
} catch(e) {
return -1;
}
+41 -18
View File
@@ -3,6 +3,29 @@
// 功能:在屏幕右上角显示2D雷达,显示所有坦克的相对位置
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// ===== 匿名字段常量 =====
var FVecX = Fld('Vec.X', 'f20_1');
var FVecY = Fld('Vec.Y', 'g20_1');
var FVecZ = Fld('Vec.Z', 'h20_1');
var FEntityId = Fld('Entity.Id', 'iz1_1');
var FEntityRoot = Fld('Entity.Root', 'qz1_1');
var FEntityComponentMap = Fld('Entity.ComponentMap', 's2a_1');
var FTankHullComponent = Fld('Tank.HullComponent', 'qzv_1');
var FTankHullDirection = Fld('Tank.HullDirection', 'wzl_1');
var MGameModeScene = Mth('GameMode.Scene', 'lz4');
var MCollisionRaycast = Mth('CollisionWorld.Raycast', 'a17p');
var FRayHitPoint = Fld('Ray.HitPoint', 'y178_1');
var FRayHitNormal = Fld('Ray.HitNormal', 'z178_1');
var FRayDistance = Fld('Ray.Distance', 'a179_1');
var FRayHitShape = Fld('Ray.HitShape', 'q17o_1');
var FRayHitBody = Fld('Ray.HitBody', 'b179_1');
var FRayFilterQuery = Fld('RayFilter.Query', 'l1da_1');
function _start() {
console.log('===== 3DTank雷达启动 =====');
@@ -38,12 +61,12 @@
// ===== 获取坦克方向(Kkt 组件的 dzc_1) =====
function getTankDirection(tank) {
var hullComp = FindComponentByField(tank, 'qzv_1');
if (hullComp && hullComp.wzl_1) {
var hullComp = FindComponentByField(tank, FTankHullComponent);
if (hullComp && hullComp[FTankHullDirection]) {
return {
x: hullComp.wzl_1.f20_1,
y: hullComp.wzl_1.g20_1,
z: hullComp.wzl_1.h20_1
x: hullComp[FTankHullDirection][FVecX],
y: hullComp[FTankHullDirection][FVecY],
z: hullComp[FTankHullDirection][FVecZ]
};
}
return null;
@@ -82,13 +105,13 @@
function getUMtComponent() {
if (UMtComponent) return UMtComponent;
try {
var gmEntity = gm.lz4();
if (!gmEntity || !gmEntity.qz1_1 || !gmEntity.qz1_1.s2a_1) return null;
gmEntity.qz1_1.s2a_1.forEach(function(valueList, key) {
var gmEntity = gm[MGameModeScene]();
if (!gmEntity || !gmEntity[FEntityRoot] || !gmEntity[FEntityRoot][FEntityComponentMap]) return null;
gmEntity[FEntityRoot][FEntityComponentMap].forEach(function(valueList, key) {
if (UMtComponent) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.a17p === 'function') UMtComponent = comp;
if (comp && typeof comp[MCollisionRaycast] === 'function') UMtComponent = comp;
} catch(e) {}
});
if (UMtComponent) console.log('[雷达] uMt 碰撞检测器已获取');
@@ -110,21 +133,21 @@
var startVec = MkVec(fromPos.x, fromPos.y, fromPos.z);
var dirVec = MkVec(dirX, dirY, dirZ);
var result = {
y178_1: MkVec(0,0,0), z178_1: MkVec(0,0,0),
a179_1: maxDist, q17o_1: null, b179_1: null,
[FRayHitPoint]: MkVec(0,0,0), [FRayHitNormal]: MkVec(0,0,0),
[FRayDistance]: maxDist, [FRayHitShape]: null, [FRayHitBody]: null,
r17o: function(t, n, i, r, e) {
this.y178_1.k26(t); this.z178_1.k26(n);
this.a179_1 = i; this.q17o_1 = r; this.b179_1 = e;
this[FRayHitPoint].k26(t); this[FRayHitNormal].k26(n);
this[FRayDistance] = i; this[FRayHitShape] = r; this[FRayHitBody] = e;
},
d3: function() { this.b179_1 = null; }
d3: function() { this[FRayHitBody] = null; }
};
var filter = {
l1da_1: { h1: function() { return false; }, d3: function() {} },
[FRayFilterQuery]: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() {}, m1da: function() {}
};
var hit = uMt.a17p(startVec, dirVec, maxDist, result, filter);
return (hit && result.a179_1 < maxDist) ? result.a179_1 : -1;
var hit = uMt[MCollisionRaycast](startVec, dirVec, maxDist, result, filter);
return (hit && result[FRayDistance] < maxDist) ? result[FRayDistance] : -1;
} catch(e) {
return -1;
}
@@ -689,7 +712,7 @@
ctx.textAlign = 'left';
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ' Bot:' + botCount + ')', 5, 12);
if (radar.playerFound) {
ctx.fillText('Player: ' + (playerTank ? playerTank.iz1_1 : '?'), 5, 24);
ctx.fillText('Player: ' + (playerTank ? playerTank[FEntityId] : '?'), 5, 24);
} else {
ctx.fillText('Player: NOT FOUND', 5, 24);
}
+104 -66
View File
@@ -4,6 +4,44 @@
// 原理:Hook gl.bindFramebuffer 重定向到自定义 FBO,用后视相机参数调用游戏渲染器,readback 显示
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// ===== 场景 / 渲染器 =====
var FSceneRef = Fld('Camera.SceneRef', 'jz1_1'); // 场景实体上的相机引用
var FRenderer = Fld('Render.Renderer', 'gyw_1'); // 渲染器实例
var FDrawer = Fld('Render.Drawer', 'd75_1'); // Ff 绘制器实例
var FRootNode = Fld('Scene.RootNode', 'fyx_1'); // 场景根节点
var FCameraObject = Fld('Camera.Object', 'iyx_1'); // 主相机对象
var FGl = Fld('Render.Gl', 'w39_1'); // GL 包装对象(o3c/n3c)
var FGlNative = Fld('Render.GlNative', 'b74_1'); // 原生 WebGL 上下文
// ===== 相机 =====
var FAngles = Fld('Camera.Angles', 'u35_1'); // 朝向角(pitch/roll/yaw)
var FPosition = Fld('Camera.Position', 'r35_1'); // 位置向量
var FFov = Fld('Camera.Fov', 'b37_1'); // 视图矩阵数据
var FViewportWidth = Fld('Camera.ViewportWidth', 'u36_1'); // 渲染宽
var FViewportHeight = Fld('Camera.ViewportHeight', 'v36_1');// 渲染高
var FFovValue = Fld('Camera.FovValue', 'z36_1'); // 视场角
var FNear = Fld('Camera.Near', 'x36_1'); // 近裁剪面
var FFar = Fld('Camera.Far', 'y36_1'); // 远裁剪面
var MCameraUpdate = Mth('Camera.Update', 'o37'); // 重算相机矩阵
var FX = Fld('Vec.X', 'f20_1');
var FY = Fld('Vec.Y', 'g20_1');
var FZ = Fld('Vec.Z', 'h20_1');
// ===== 渲染开关 / 队列 =====
var FSettings = Fld('Render.Settings', 'y38_1'); // 渲染开关集合
var FShadowEnabled = Fld('Render.ShadowEnabled', 'e3o_1'); // 阴影开关
var FPostProcessEnabled = Fld('Render.PostProcessEnabled', 'f3o_1'); // 后处理开关
var FExtraPassEnabled = Fld('Render.ExtraPassEnabled', 'k3o_1'); // 额外 pass 开关
var FLayerQueue = Fld('Render.LayerQueue', 'e39_1'); // 渲染层队列
var MDrawScene = Mth('Render.Draw', 'f3t'); // 渲染主场景
var MGetDrawingWidth = Mth('Render.DrawingWidth', 'o3c'); // drawingBufferWidth
var MGetDrawingHeight = Mth('Render.DrawingHeight', 'n3c'); // drawingBufferHeight
function _start() {
console.log('===== 3DTank后视镜(原生渲染版)启动 =====');
@@ -81,27 +119,27 @@
var initError = null;
try {
scene = gm.lz4().jz1_1;
scene = gm.lz4()[FSceneRef];
console.log('[后视] 场景对象:', scene);
// hC 渲染器实例(jr.lye_1)
var renderer = scene.gyw_1;
var renderer = scene[FRenderer];
console.log('[后视] 渲染器:', renderer);
// Ff 绘制器实例(hC.r74_1)
ff = renderer.d75_1;
ff = renderer[FDrawer];
console.log('[后视] Ff绘制器:', ff);
// 场景根节点(jr.kyf_1)
rootNode = scene.fyx_1;
rootNode = scene[FRootNode];
console.log('[后视] 根节点:', rootNode);
// 主相机 Ha 实例(jr.nyf_1)
mainCamera = scene.iyx_1;
mainCamera = scene[FCameraObject];
console.log('[后视] 主相机:', mainCamera);
// 游戏 gl context(Ff.p39_1.p73_1 = BS.p73_1)
gl = ff.w39_1.b74_1;
gl = ff[FGl][FGlNative];
console.log('[后视] GL上下文:', gl);
if (!ff || !rootNode || !mainCamera || !gl) {
@@ -109,11 +147,11 @@
}
// 探测相机字段
console.log('[后视] 相机u35_1:', mainCamera.u35_1);
console.log('[后视] 相机r35_1:', mainCamera.r35_1);
console.log('[后视] 相机b37_1:', mainCamera.b37_1);
console.log('[后视] 相机u35_1:', mainCamera[FAngles]);
console.log('[后视] 相机r35_1:', mainCamera[FPosition]);
console.log('[后视] 相机b37_1:', mainCamera[FFov]);
if (!mainCamera.u35_1 || !mainCamera.r35_1) {
if (!mainCamera[FAngles] || !mainCamera[FPosition]) {
throw new Error('相机字段缺失');
}
@@ -245,10 +283,10 @@
// Hook BS.h3c/g3c:后视渲染时返回 FBO 尺寸(修复 screenSize/aspect ratio)
// y3s 会调用 BS.h3c()/g3c() 获取 drawingBufferWidth/Height 设置相机 l36_1/m36_1
// 不 Hook 的话 aspect ratio 会用游戏 canvas 尺寸(如1920x1080),导致画面比例错误
origH3c = ff.w39_1.o3c.bind(ff.w39_1);
origG3c = ff.w39_1.n3c.bind(ff.w39_1);
ff.w39_1.o3c = function() { return rearviewRendering ? rw : origH3c(); };
ff.w39_1.n3c = function() { return rearviewRendering ? rh : origG3c(); };
origH3c = ff[FGl][MGetDrawingWidth].bind(ff[FGl]);
origG3c = ff[FGl][MGetDrawingHeight].bind(ff[FGl]);
ff[FGl][MGetDrawingWidth] = function() { return rearviewRendering ? rw : origH3c(); };
ff[FGl][MGetDrawingHeight] = function() { return rearviewRendering ? rh : origG3c(); };
console.log('[后视] BS.o3c/n3c 已 Hook');
}
@@ -309,17 +347,17 @@
var camPosZ = playerPos.z + Config.cameraHeight;
// ===== 保存主相机参数 =====
var savedPitch = mainCamera.u35_1.f20_1;
var savedYaw = mainCamera.u35_1.g20_1;
var savedRoll = mainCamera.u35_1.h20_1;
var savedX = mainCamera.r35_1.f20_1;
var savedY = mainCamera.r35_1.g20_1;
var savedZ = mainCamera.r35_1.h20_1;
var savedL36 = mainCamera.u36_1;
var savedM36 = mainCamera.v36_1;
var savedS36 = mainCamera.z36_1;
var savedQ36 = mainCamera.x36_1;
var savedR36 = mainCamera.y36_1;
var savedPitch = mainCamera[FAngles][FX];
var savedYaw = mainCamera[FAngles][FY];
var savedRoll = mainCamera[FAngles][FZ];
var savedX = mainCamera[FPosition][FX];
var savedY = mainCamera[FPosition][FY];
var savedZ = mainCamera[FPosition][FZ];
var savedL36 = mainCamera[FViewportWidth];
var savedM36 = mainCamera[FViewportHeight];
var savedS36 = mainCamera[FFovValue];
var savedQ36 = mainCamera[FNear];
var savedR36 = mainCamera[FFar];
// 临时暂停自由视角的 h37 hook,避免覆盖后视镜相机参数
var fl = window.__hornetFreeLook__;
@@ -330,41 +368,41 @@
// g=π 使水平方向反向(朝后看),h=yaw+π 保持水平视角正确
// f=-pitch 使垂直方向与主视一致(低头看后方地形)
// up 朝下 → FBO 画面颠倒 → GL readback Y 翻转修正
mainCamera.u35_1.f20_1 = rearPitch;
mainCamera.u35_1.g20_1 = Math.PI;
mainCamera.u35_1.h20_1 = camInfo.yaw + Math.PI;
mainCamera.r35_1.f20_1 = camPosX;
mainCamera.r35_1.g20_1 = camPosY;
mainCamera.r35_1.h20_1 = camPosZ;
mainCamera.u36_1 = rw;
mainCamera.v36_1 = rh;
mainCamera.z36_1 = Config.fov;
mainCamera.x36_1 = Config.near;
mainCamera.y36_1 = Config.far;
mainCamera[FAngles][FX] = rearPitch;
mainCamera[FAngles][FY] = Math.PI;
mainCamera[FAngles][FZ] = camInfo.yaw + Math.PI;
mainCamera[FPosition][FX] = camPosX;
mainCamera[FPosition][FY] = camPosY;
mainCamera[FPosition][FZ] = camPosZ;
mainCamera[FViewportWidth] = rw;
mainCamera[FViewportHeight] = rh;
mainCamera[FFovValue] = Config.fov;
mainCamera[FNear] = Config.near;
mainCamera[FFar] = Config.far;
// 更新相机矩阵(viewMatrix, projectionMatrix, viewProjectionMatrix)
mainCamera.o37();
mainCamera[MCameraUpdate]();
// 保存后视视图矩阵副本(用于 UI 标记投影,finally 会恢复相机所以需提前保存)
var rearViewMatrix = new Float32Array(mainCamera.b37_1);
var rearViewMatrix = new Float32Array(mainCamera[FFov]);
// ===== 保存并禁用阴影/后处理(避免它们切换 FBO 覆盖 rearFbo)=====
// p38_1.b3o_1 控制 z39_1(阴影),c3o_1 控制 c3a_1/d3a_1(后处理),z3n_1 控制 f3a_1
// 禁用后 y3s 只执行主场景渲染 z3k,跳过所有 FBO 切换的 pass
var savedX3n = ff.y38_1.e3o_1;
var savedY3n = ff.y38_1.f3o_1;
var savedZ3n = ff.y38_1.k3o_1;
ff.y38_1.e3o_1 = false;
ff.y38_1.f3o_1 = false;
ff.y38_1.k3o_1 = false;
var savedX3n = ff[FSettings][FShadowEnabled];
var savedY3n = ff[FSettings][FPostProcessEnabled];
var savedZ3n = ff[FSettings][FExtraPassEnabled];
ff[FSettings][FShadowEnabled] = false;
ff[FSettings][FPostProcessEnabled] = false;
ff[FSettings][FExtraPassEnabled] = false;
// ===== 禁用 UI 渲染层(防止技能条等 UI 混入后视 FBO)=====
// 保存 UI 层队列,替换为空假对象,y3s 后恢复
var savedUiQueues = {};
for (var ui = 0; ui < uiLayerIndices.length; ui++) {
var li = uiLayerIndices[ui];
savedUiQueues[li] = ff.e39_1[li];
ff.e39_1[li] = fakeQueue;
savedUiQueues[li] = ff[FLayerQueue][li];
ff[FLayerQueue][li] = fakeQueue;
}
// ===== 渲染到 FBO =====
@@ -377,7 +415,7 @@
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 调用游戏原生渲染器(已禁用阴影/后处理/UI层,只渲染 3D 场景)
ff.f3t(rootNode, mainCamera);
ff[MDrawScene](rootNode, mainCamera);
rearviewRendering = false;
@@ -430,9 +468,9 @@
// ===== 恢复阴影/后处理(异常时也要恢复)=====
try {
if (typeof savedX3n !== 'undefined') {
ff.y38_1.e3o_1 = savedX3n;
ff.y38_1.f3o_1 = savedY3n;
ff.y38_1.k3o_1 = savedZ3n;
ff[FSettings][FShadowEnabled] = savedX3n;
ff[FSettings][FPostProcessEnabled] = savedY3n;
ff[FSettings][FExtraPassEnabled] = savedZ3n;
}
} catch(e) {
console.error('[后视] 恢复阴影/后处理失败:', e.message);
@@ -443,7 +481,7 @@
if (savedUiQueues) {
for (var ui2 = 0; ui2 < uiLayerIndices.length; ui2++) {
var li2 = uiLayerIndices[ui2];
ff.e39_1[li2] = savedUiQueues[li2];
ff[FLayerQueue][li2] = savedUiQueues[li2];
}
}
} catch(e) {
@@ -452,18 +490,18 @@
// ===== 恢复主相机参数 =====
try {
mainCamera.u35_1.f20_1 = savedPitch;
mainCamera.u35_1.g20_1 = savedYaw;
mainCamera.u35_1.h20_1 = savedRoll;
mainCamera.r35_1.f20_1 = savedX;
mainCamera.r35_1.g20_1 = savedY;
mainCamera.r35_1.h20_1 = savedZ;
mainCamera.u36_1 = savedL36;
mainCamera.v36_1 = savedM36;
mainCamera.z36_1 = savedS36;
mainCamera.x36_1 = savedQ36;
mainCamera.y36_1 = savedR36;
mainCamera.o37();
mainCamera[FAngles][FX] = savedPitch;
mainCamera[FAngles][FY] = savedYaw;
mainCamera[FAngles][FZ] = savedRoll;
mainCamera[FPosition][FX] = savedX;
mainCamera[FPosition][FY] = savedY;
mainCamera[FPosition][FZ] = savedZ;
mainCamera[FViewportWidth] = savedL36;
mainCamera[FViewportHeight] = savedM36;
mainCamera[FFovValue] = savedS36;
mainCamera[FNear] = savedQ36;
mainCamera[FFar] = savedR36;
mainCamera[MCameraUpdate]();
} catch(e) {
console.error('[后视] 恢复相机失败:', e.message);
}
@@ -591,9 +629,9 @@
}
// 恢复 BS.h3c/g3c
if (origH3c && ff && ff.w39_1) {
ff.w39_1.o3c = origH3c;
ff.w39_1.n3c = origG3c;
if (origH3c && ff && ff[FGl]) {
ff[FGl][MGetDrawingWidth] = origH3c;
ff[FGl][MGetDrawingHeight] = origG3c;
console.log('[后视] BS.o3c/n3c 已恢复');
}
+132 -10
View File
@@ -27,12 +27,20 @@
// ===== 字段映射总表 =====
// 命名规则:<对象类型>.<语义名>
var Fields = {
// ---- 向量(O$)----
'Vec.X': 'f20_1',
'Vec.Y': 'g20_1',
'Vec.Z': 'h20_1',
// ---- 战斗实体基类(fe / BattleEntity)----
'Entity.Id': 'iz1_1', // 实体 ID(坦克/战场唯一)
'Entity.Root': 'qz1_1', // 组件宿主根对象
'Entity.ComponentMap': 's2a_1', // 宿主上的组件 Map
'Entity.Components': 'qz1_1.s2a_1', // Map<组件标识, 组件列表>,取值要 valueList.g1(0)
// ---- GameMode(ght,window.__gameMode__)----
'GameMode.Scene': 'lz4()', // 场景实体
'GameMode.TankManager': 'm10b_1', // 坦克管理器
'GameMode.TankList': 'm10b_1.j11y()', // 坦克列表(e1() 数量 / g1(i) 取)
'GameMode.OwnedTank': 'u10b_1', // 本地玩家拥有的坦克(onTankPossessed)
'GameMode.ObservedTank': 't10b_1', // 当前观察目标(死亡/观战时可能指向敌人)
@@ -52,6 +60,8 @@
// ---- 坦克物理组件(ejt,组件名 tankPhysics)----
'Physics.Position': 'bzr_1', // 世界位置向量(f20_1/g20_1/h20_1)
'Physics.PositionOld1': 'gzh_1', // 旧版位置字段名(兜底候选)
'Physics.PositionOld2': 'pz7_1', // 更旧位置字段名(兜底候选)
'Physics.Size': 'xzq_1', // 车体半尺寸(**不是位置**)
'Physics.Hull': 'zzq_1', // 车体主碰撞形状(x9j_1 === 'hull-main')
'Physics.Body': 'pbk()', // 物理刚体(jM)
@@ -101,17 +111,107 @@
// ---- BP 场景节点(zl)----
'Node.Name': 'g35_1',
'Node.Matrix': 'p35_1', // 世界矩阵(列主序,[12][13][14] = 平移)
'Node.MatrixOld': 'n35_1', // 旧版矩阵字段(兜底候选)
'Node.Children': 'r35_1',
'Node.Mesh': 'v3m_1',
'Node.Lod': 'u4x_1',
// ---- 相机(场景实体 .jz1_1.iyx_1)----
'Camera.SceneRef': 'jz1_1',
'Camera.Object': 'iyx_1', // 主相机对象(挂在场景实体的 jz1_1 上)
'Camera.Viewport': 'w36_1', // 视口(h29() 宽 / g29() 高)
'Camera.Main': 'jz1_1.iyx_1',
'Camera.Position': 'jz1_1.iyx_1.t35_1',
'Camera.ViewMatrix': 'jz1_1.iyx_1.d37_1',
'Camera.ClipMatrix': 'jz1_1.iyx_1.g37_1',
'Camera.Fov': 'jz1_1.iyx_1.b37_1'
'Camera.Fov': 'jz1_1.iyx_1.b37_1',
// ---- 相机:单段叶子字段(FieldName 只认单段,这里给同一批字段补叶子别名)----
'Camera.PositionLeaf': 't35_1',
'Camera.ViewMatrixLeaf': 'd37_1',
'Camera.ClipMatrixLeaf': 'g37_1',
'Camera.FovLeaf': 'b37_1',
'Camera.PositionNode': 'r35_1', // 旧版相机位置字段(与 Node.Children 同名但不通用)
'Camera.Angles': 'u35_1', // 欧拉角(pitch/roll/yaw)
'Camera.Euler': 'u35_1', // 同上(自由视角叫法)
'Camera.Rotation': 'u35_1', // 同上(沙盘叫法)
'Camera.Update': 'o37()', // 相机矩阵刷新
'Camera.ViewportWidth': 'u36_1',
'Camera.ViewportHeight': 'v36_1',
'Camera.RenderWidth': 'u36_1', // 别名
'Camera.RenderHeight': 'v36_1', // 别名
'Camera.FovValue': 'z36_1',
'Camera.Near': 'x36_1',
'Camera.Far': 'y36_1',
'Camera.SceneFx': 'd75_1',
// ---- 渲染器 / 绘制流程(module_92980)----
'Render.Renderer': 'gyw_1', // 场景渲染器
'Render.Drawer': 'd75_1', // Ff 绘制器
'Render.DrawBuffer': 'd75_1', // 别名(透视里按"绘制缓冲"称呼)
'Renderer.Object': 'd75_1', // 别名(沙盘里按"渲染器对象"称呼)
'Render.Gl': 'w39_1', // GL 包装器
'Renderer.Device': 'w39_1', // 别名
'Render.GlNative': 'b74_1', // 原生 WebGL2RenderingContext
'Renderer.Gl': 'b74_1', // 别名
'Render.Device': 's3h_1', // 资源侧设备包装
'Render.DeviceNative': 'f75_1', // 设备里的原生 GL
'Render.Settings': 'y38_1', // 渲染开关组
'Renderer.Flags': 'y38_1', // 别名
'Render.ShadowEnabled': 'e3o_1',
'Renderer.Shadow': 'e3o_1', // 别名
'Render.PostProcessEnabled': 'f3o_1',
'Renderer.PostFx': 'f3o_1', // 别名
'Render.ExtraPassEnabled': 'k3o_1',
'Renderer.FlagAux': 'k3o_1', // 别名
'Render.LayerQueue': 'e39_1', // 渲染层队列(20=Hud 21-24=GUI)
'Renderer.QueueList': 'e39_1', // 别名
'Render.Draw': 'f3t()', // y3s → f3t 主渲染
'Render.DrawingWidth': 'o3c()',
'Render.DrawingHeight': 'n3c()',
'Scene.RootNode': 'fyx_1', // 场景根节点
// ---- 网格资源(Tp / sm)----
'Mesh.VertexGroups': 'x4w_1',
'Mesh.VertexBuffer': 'h3x_1',
'Mesh.VertexCount': 'w3x_1',
'Mesh.CpuVertexData': 'x3x_1',
'Mesh.GpuBuffer': 'v3x_1',
'Mesh.IndexGroups': 'y4w_1',
'Mesh.IndexBuffer': 'q4w_1',
'Mesh.IndexBufferOld': 'u3p_1',
'Mesh.CpuIndexData': 'p3x_1',
'Mesh.IndexGpuBuffer': 'n3x_1',
// ---- 射线检测(碰撞世界的射线接口)----
'CollisionWorld.Raycast': 'a17p()',
'Ray.Cast': 'a17p()', // 别名
'Ray.StartPoint': 'y178_1',
'Ray.HitPoint': 'y178_1', // 别名
'Ray.EndPoint': 'z178_1',
'Ray.HitNormal': 'z178_1', // 别名
'Ray.Distance': 'a179_1',
'Ray.HitEntity': 'q17o_1',
'Ray.HitShape': 'q17o_1', // 别名(雷达/瞄准把第 4 参数叫 HitShape)
'Ray.HitBody': 'b179_1', // 第 5 参数
'RayFilter.Context': 'l1da_1',
'RayFilter.Query': 'l1da_1', // 别名
// ---- 车体朝向 / 武器(雷达、瞄准警告)----
'Tank.HullComponent': 'qzv_1', // 车体朝向组件识别字段
'Tank.HullDirection': 'wzl_1', // 车体朝向向量
'Tank.StateId': 'k3_1', // 状态枚举数值(与 Team.Id 同名字段,按用途分开)
'Weapon.Component': 'n11c_1',
'Weapon.ComponentAlt': 'q112_1',
'Weapon.TurretRoot': 'm17a_1',
// ---- 炮塔 / 皮肤槽位 ----
'Turret.InnerWrapper': 'e11p_1',
'Turret.RootNode': 'a17q_1',
'Skin.SlotHull': 'w14w_1',
'Skin.SlotHullLegacy': 't14l_1',
'Skin.SlotHullAlt': 'w14x_1',
'Skin.SlotHullOld': 'p14l_1'
};
// ===== module_87072 的导出键($_.xx),每版都可能平移 =====
@@ -186,6 +286,21 @@
return Out;
}
// 自检项(只查核心字段:别名/扩展项不在样本对象上,查了会误报)
var CoreKeys = {
Entity: ['Entity.Id', 'Entity.Root', 'Entity.ComponentMap', 'Entity.Components'],
GameMode: ['GameMode.Scene', 'GameMode.TankManager', 'GameMode.TankList',
'GameMode.OwnedTank', 'GameMode.ObservedTank', 'GameMode.LocalTeam'],
Tank: ['Tank.Fsm', 'Tank.State', 'Tank.StateValue', 'Tank.IsAlive', 'Tank.TeamSource'],
Physics: ['Physics.Position', 'Physics.Size', 'Physics.Hull', 'Physics.Body', 'Physics.BodyShapes'],
Shape: ['Shape.Name', 'Shape.Category', 'Shape.Mask', 'Shape.Body', 'Body.Owner'],
Skin: ['Skin.HullSkin', 'Skin.HullRoot', 'Skin.Parts', 'Skin.PartNode'],
Turret: ['Turret.Wrapper', 'Turret.GetRoot'],
Node: ['Node.Name', 'Node.Matrix', 'Node.Children', 'Node.Mesh'],
Camera: ['Camera.SceneRef', 'Camera.Main', 'Camera.Position', 'Camera.ViewMatrix',
'Camera.ClipMatrix', 'Camera.Fov']
};
// 用当前战局对象做一次全量自检(控制台调用 __hornetSchema__.SelfCheck())
function SelfCheck() {
var Gm = window.__gameMode__;
@@ -200,29 +315,36 @@
if (!Tank && TankList && typeof TankList.e1 === 'function' && TankList.e1() > 0) Tank = TankList.g1(0);
} catch (e) {}
var Physics = Tank ? FindPhysics(Tank) : null;
var Targets = {
Entity: Tank || Scene,
GameMode: Gm,
Tank: Tank,
Physics: Tank ? FindPhysics(Tank) : null,
Shape: null,
Physics: Physics,
Shape: Physics ? Get(Physics, 'Physics.Hull') : null,
Skin: Tank,
Turret: Tank,
Node: null,
Node: Tank ? Get(Tank, 'Skin.HullRoot') : null,
Camera: Scene
};
Object.keys(Targets).forEach(function (Prefix) {
if (!Targets[Prefix]) { Report.Groups[Prefix] = '无样本对象'; return; }
var R = Check(Targets[Prefix], Prefix);
Report.Groups[Prefix] = R;
R.Missing.forEach(function (M) { Report.Missing.push(Prefix + '.' + M); });
Object.keys(CoreKeys).forEach(function (Prefix) {
var Target = Targets[Prefix];
if (!Target) { Report.Groups[Prefix] = '无样本对象'; return; }
var Ok = [], Missing = [];
CoreKeys[Prefix].forEach(function (Key) {
var V = Get(Target, Key);
if (V === undefined) Missing.push(Key + ' → ' + PathOf(Key));
else Ok.push(Key);
});
Report.Groups[Prefix] = { Ok: Ok, Missing: Missing };
Missing.forEach(function (M) { Report.Missing.push(Prefix + '.' + M); });
});
if (Report.Missing.length) {
console.warn('[字段抽象] 失配 ' + Report.Missing.length + ' 项:', Report.Missing);
} else {
console.log('[字段抽象] 全部语义字段解析正常');
console.log('[字段抽象] 全部核心字段解析正常');
}
return Report;
}
+107 -63
View File
@@ -5,6 +5,50 @@
// 操作:拖拽标题移动窗口 | 拖拽地图平移 | 滚轮缩放 | 双击居中 | Alt+M 开关
(function() {
// ===== 字段抽象层(语义名 → 匿名字段,总表在 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; }
// 向量
var FVecX = Fld('Vec.X', 'f20_1');
var FVecY = Fld('Vec.Y', 'g20_1');
var FVecZ = Fld('Vec.Z', 'h20_1');
// 战局 / 场景
var MScene = Mth('GameMode.Scene', 'lz4');
var FCameraSceneRef = Fld('Camera.SceneRef', 'jz1_1');
var FTankManager = Fld('GameMode.TankManager', 'm10b_1');
var MTankList = Mth('GameMode.TankList', 'j11y');
// 渲染器
var FRenderer = Fld('Renderer.Object', 'd75_1');
var FRenderDevice = Fld('Renderer.Device', 'w39_1');
var FRenderGl = Fld('Renderer.Gl', 'b74_1');
var FRenderFlags = Fld('Renderer.Flags', 'y38_1');
var FRenderShadow = Fld('Renderer.Shadow', 'e3o_1');
var FRenderPostFx = Fld('Renderer.PostFx', 'f3o_1');
var FRenderFlagAux = Fld('Renderer.FlagAux', 'k3o_1');
var FRenderQueueList = Fld('Renderer.QueueList', 'e39_1');
// 相机
var FCameraRotation = Fld('Camera.Rotation', 'u35_1');
var FNodeChildren = Fld('Node.Children', 'r35_1'); // 本文件里该字段是相机位置向量(与节点子列表同名复用)
var FCameraRenderWidth = Fld('Camera.RenderWidth', 'u36_1');
var FCameraRenderHeight = Fld('Camera.RenderHeight', 'v36_1');
var FCameraFov = Fld('Camera.Fov', 'z36_1');
var FCameraNear = Fld('Camera.Near', 'x36_1');
var FCameraFar = Fld('Camera.Far', 'y36_1');
// 皮肤 / 节点
var FSkinSlotHull = Fld('Skin.SlotHull', 'w14w_1');
var FSkinSlotHullLegacy = Fld('Skin.SlotHullLegacy', 't14l_1');
var FSkinSlotHullAlt = Fld('Skin.SlotHullAlt', 'w14x_1');
var FSkinSlotHullOld = Fld('Skin.SlotHullOld', 'p14l_1');
var FSkinParts = Fld('Skin.Parts', 'g17r_1');
var FSkinPartNode = Fld('Skin.PartNode', 'j17r_1');
var FNodeMatrixOld = Fld('Node.MatrixOld', 'n35_1');
function _start() {
console.log('===== 3DTank 战争沙盘(3D俯瞰版)启动 =====');
@@ -37,27 +81,27 @@
// 从车体皮肤组件(KMt)的Bp节点世界矩阵提取前向方向
// dzc_1 是局部空间向量且可能未设置,不可靠
// 皮肤字段:新 build 是 w14w_1;旧 build 是 t14l_1(新版 t14l_1 已改成悬停车体状态 Qmt)
var hullComp = FindComponentByField(tank, 'w14w_1') ||
FindComponentByField(tank, 't14l_1') ||
FindComponentByField(tank, 'w14x_1') ||
FindComponentByField(tank, 'p14l_1');
var hullComp = FindComponentByField(tank, FSkinSlotHull) ||
FindComponentByField(tank, FSkinSlotHullLegacy) ||
FindComponentByField(tank, FSkinSlotHullAlt) ||
FindComponentByField(tank, FSkinSlotHullOld);
if (!hullComp) return null;
var skin = (hullComp.w14w_1 && hullComp.w14w_1.g17r_1) ? hullComp.w14w_1 :
(hullComp.t14l_1 && hullComp.t14l_1.g17r_1) ? hullComp.t14l_1 :
(hullComp.p14l_1 && hullComp.p14l_1.g17r_1) ? hullComp.p14l_1 : null;
var skin = (hullComp[FSkinSlotHull] && hullComp[FSkinSlotHull][FSkinParts]) ? hullComp[FSkinSlotHull] :
(hullComp[FSkinSlotHullLegacy] && hullComp[FSkinSlotHullLegacy][FSkinParts]) ? hullComp[FSkinSlotHullLegacy] :
(hullComp[FSkinSlotHullOld] && hullComp[FSkinSlotHullOld][FSkinParts]) ? hullComp[FSkinSlotHullOld] : null;
if (!skin) return null;
try {
var partCount = skin.g17r_1.e1();
var partCount = skin[FSkinParts].e1();
for (var p = 0; p < partCount; p++) {
var part = skin.g17r_1.g1(p);
if (!part || !part.j17r_1) continue;
var nodeCount = part.j17r_1.e1();
var part = skin[FSkinParts].g1(p);
if (!part || !part[FSkinPartNode]) continue;
var nodeCount = part[FSkinPartNode].e1();
for (var n = 0; n < nodeCount; n++) {
var bpNode = part.j17r_1.g1(n);
if (!bpNode || !bpNode.n35_1) continue;
var bpNode = part[FSkinPartNode].g1(n);
if (!bpNode || !bpNode[FNodeMatrixOld]) continue;
// 不按 g35_1 过滤:它是引擎的细节/淡出权重(0~1),不是幽灵标志,
// 过滤会漏掉低细节节点、导致取不到朝向
var m = bpNode.n35_1;
var m = bpNode[FNodeMatrixOld];
// 列主序世界矩阵:Col0=(m[0],m[1],m[2]) Col1=(m[4],m[5],m[6]) Col2=(m[8],m[9],m[10])
// 游戏坐标:x,y 水平面; z(h20_1)=高度。找"上"列(|z|最大),其余为水平方向
var az0 = Math.abs(m[2]), az1 = Math.abs(m[6]), az2 = Math.abs(m[10]);
@@ -87,11 +131,11 @@
var scene = null, ff = null, rootNode = null, mainCamera = null, gl = null;
var initError = null;
try {
scene = gm.lz4().jz1_1;
ff = scene.gyw_1.d75_1;
scene = gm[MScene]()[FCameraSceneRef];
ff = scene.gyw_1[FRenderer];
rootNode = scene.fyx_1;
mainCamera = scene.iyx_1;
gl = ff.w39_1.b74_1;
gl = ff[FRenderDevice][FRenderGl];
if (!ff || !rootNode || !mainCamera || !gl) throw new Error('关键字段缺失');
} catch(e) {
initError = e;
@@ -278,10 +322,10 @@
}
return origBindFramebuffer(target, fbo);
};
var origH3c = ff.w39_1.o3c.bind(ff.w39_1);
var origG3c = ff.w39_1.n3c.bind(ff.w39_1);
ff.w39_1.o3c = function() { return sandboxRendering ? FBO_W : origH3c(); };
ff.w39_1.n3c = function() { return sandboxRendering ? FBO_H : origG3c(); };
var origH3c = ff[FRenderDevice].o3c.bind(ff[FRenderDevice]);
var origG3c = ff[FRenderDevice].n3c.bind(ff[FRenderDevice]);
ff[FRenderDevice].o3c = function() { return sandboxRendering ? FBO_W : origH3c(); };
ff[FRenderDevice].n3c = function() { return sandboxRendering ? FBO_H : origG3c(); };
// UI 层禁用
var uiLayerIndices = [20, 21, 22, 23, 24];
@@ -481,7 +525,7 @@
// 坦克标记
var tanks = [];
try {
var list = gm.m10b_1.j11y();
var list = gm[FTankManager][MTankList]();
var n = list.e1();
for (var i = 0; i < n; i++) { var t = list.g1(i); if (t) tanks.push(t); }
} catch(e) {}
@@ -661,44 +705,44 @@
try {
// 保存主相机参数
var sPitch = mainCamera.u35_1.f20_1;
var sRoll = mainCamera.u35_1.g20_1;
var sYaw = mainCamera.u35_1.h20_1;
var sX = mainCamera.r35_1.f20_1;
var sY = mainCamera.r35_1.g20_1;
var sZ = mainCamera.r35_1.h20_1;
var sL36 = mainCamera.u36_1, sM36 = mainCamera.v36_1;
var sS36 = mainCamera.z36_1, sQ36 = mainCamera.x36_1, sR36 = mainCamera.y36_1;
var sPitch = mainCamera[FCameraRotation][FVecX];
var sRoll = mainCamera[FCameraRotation][FVecY];
var sYaw = mainCamera[FCameraRotation][FVecZ];
var sX = mainCamera[FNodeChildren][FVecX];
var sY = mainCamera[FNodeChildren][FVecY];
var sZ = mainCamera[FNodeChildren][FVecZ];
var sL36 = mainCamera[FCameraRenderWidth], sM36 = mainCamera[FCameraRenderHeight];
var sS36 = mainCamera[FCameraFov], sQ36 = mainCamera[FCameraNear], sR36 = mainCamera[FCameraFar];
// 设置俯瞰相机参数
// pitch=-π/2 朝下看,roll=0 不翻转,yaw=0 北朝上
mainCamera.u35_1.f20_1 = -Math.PI / 2;
mainCamera.u35_1.g20_1 = 0;
mainCamera.u35_1.h20_1 = 0;
mainCamera.r35_1.f20_1 = viewCenterX;
mainCamera.r35_1.g20_1 = viewCenterY;
mainCamera.r35_1.h20_1 = camHeight;
mainCamera.u36_1 = FBO_W;
mainCamera.v36_1 = FBO_H;
mainCamera.z36_1 = Config.fov;
mainCamera.x36_1 = Config.near;
mainCamera.y36_1 = Config.far;
mainCamera[FCameraRotation][FVecX] = -Math.PI / 2;
mainCamera[FCameraRotation][FVecY] = 0;
mainCamera[FCameraRotation][FVecZ] = 0;
mainCamera[FNodeChildren][FVecX] = viewCenterX;
mainCamera[FNodeChildren][FVecY] = viewCenterY;
mainCamera[FNodeChildren][FVecZ] = camHeight;
mainCamera[FCameraRenderWidth] = FBO_W;
mainCamera[FCameraRenderHeight] = FBO_H;
mainCamera[FCameraFov] = Config.fov;
mainCamera[FCameraNear] = Config.near;
mainCamera[FCameraFar] = Config.far;
mainCamera.o37();
// 禁用阴影/后处理
var savedX3n = ff.y38_1.e3o_1;
var savedY3n = ff.y38_1.f3o_1;
var savedZ3n = ff.y38_1.k3o_1;
ff.y38_1.e3o_1 = false;
ff.y38_1.f3o_1 = false;
ff.y38_1.k3o_1 = false;
var savedX3n = ff[FRenderFlags][FRenderShadow];
var savedY3n = ff[FRenderFlags][FRenderPostFx];
var savedZ3n = ff[FRenderFlags][FRenderFlagAux];
ff[FRenderFlags][FRenderShadow] = false;
ff[FRenderFlags][FRenderPostFx] = false;
ff[FRenderFlags][FRenderFlagAux] = false;
// 禁用 UI 层
var savedUiQueues = {};
for (var ui = 0; ui < uiLayerIndices.length; ui++) {
var li = uiLayerIndices[ui];
savedUiQueues[li] = ff.e39_1[li];
ff.e39_1[li] = fakeQueue;
savedUiQueues[li] = ff[FRenderQueueList][li];
ff[FRenderQueueList][li] = fakeQueue;
}
// 渲染到 FBO
@@ -744,22 +788,22 @@
origBindFramebuffer(gl.FRAMEBUFFER, mainFbo);
// 恢复渲染状态
ff.y38_1.e3o_1 = savedX3n;
ff.y38_1.f3o_1 = savedY3n;
ff.y38_1.k3o_1 = savedZ3n;
ff[FRenderFlags][FRenderShadow] = savedX3n;
ff[FRenderFlags][FRenderPostFx] = savedY3n;
ff[FRenderFlags][FRenderFlagAux] = savedZ3n;
for (var ui2 = 0; ui2 < uiLayerIndices.length; ui2++) {
ff.e39_1[uiLayerIndices[ui2]] = savedUiQueues[uiLayerIndices[ui2]];
ff[FRenderQueueList][uiLayerIndices[ui2]] = savedUiQueues[uiLayerIndices[ui2]];
}
// 恢复主相机
mainCamera.u35_1.f20_1 = sPitch;
mainCamera.u35_1.g20_1 = sRoll;
mainCamera.u35_1.h20_1 = sYaw;
mainCamera.r35_1.f20_1 = sX;
mainCamera.r35_1.g20_1 = sY;
mainCamera.r35_1.h20_1 = sZ;
mainCamera.u36_1 = sL36; mainCamera.v36_1 = sM36;
mainCamera.z36_1 = sS36; mainCamera.x36_1 = sQ36; mainCamera.y36_1 = sR36;
mainCamera[FCameraRotation][FVecX] = sPitch;
mainCamera[FCameraRotation][FVecY] = sRoll;
mainCamera[FCameraRotation][FVecZ] = sYaw;
mainCamera[FNodeChildren][FVecX] = sX;
mainCamera[FNodeChildren][FVecY] = sY;
mainCamera[FNodeChildren][FVecZ] = sZ;
mainCamera[FCameraRenderWidth] = sL36; mainCamera[FCameraRenderHeight] = sM36;
mainCamera[FCameraFov] = sS36; mainCamera[FCameraNear] = sQ36; mainCamera[FCameraFar] = sR36;
mainCamera.o37();
// 绘制 2D 叠加层(坦克标记/网格/罗盘)
@@ -795,8 +839,8 @@
if (rafId) cancelAnimationFrame(rafId);
// 恢复 hook
gl.bindFramebuffer = origBindFramebuffer;
ff.w39_1.o3c = origH3c;
ff.w39_1.n3c = origG3c;
ff[FRenderDevice].o3c = origH3c;
ff[FRenderDevice].n3c = origG3c;
window.removeEventListener('resize', resizeCanvas);
if (container.parentNode) container.parentNode.removeChild(container);
window.__hornetWarSandbox__ = null;