refactor(schema): 全部工具改为经字段抽象层取值
hornet_lib / hook_gamemode / radar / 透视 / 后视镜 / 瞄准警告 / 自由视角 / 战争沙盘 全部改为 Fld/Mth 常量取值;schema 补齐渲染器、网格、射线、武器、皮肤槽位等 88 项字段与别名;SelfCheck 改为只校验核心字段,避免别名误报。
This commit is contained in:
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
File diff suppressed because it is too large.
Load diff
+14
-5
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user