fix(tools): 按新旧差异修复位置与相机链路(雷达/透视恢复绘制)

This commit is contained in:
TermiNexus committed 2026-10-17 16:50:54 +08:00
1 parent 9506648877
commit a34cae5474
5 files changed
+538 -544

No files matched your search

@@ -263,60 +263,29 @@
}
// ===== 获取坦克位置(物理组件里的位置向量) =====
// 物理组件的字段名每个构建都可能平移,这里按"含 f20_1/g20_1/h20_1 的 O$ 向量"做多级探测:
// ejt(tankPhysics)里可能的候选字段都试一遍,命中即用。
// 新旧差异(main.7cfa4058 vs 旧版):
// 物理组件类 ejt(组件名 "tankPhysics")的字段整体平移,构造函数字段序列一一对应:
// 旧 vjt: zzg_1 azh_1 czh_1 dzh_1 ezh_1 fzh_1 **gzh_1** hzh_1 izh_1 ...
// 新 ejt: uzq_1 vzq_1 xzq_1 yzq_1 zzq_1 azr_1 **bzr_1** czr_1 dzr_1 ...
// njt() 里 `bzr_1.l27(上一物理态位置, 当前物理态位置, 插值)` —— bzr_1 就是世界位置。
// ⚠ xzq_1 是车体半尺寸(用于包围盒/出生点:`-xzq_1.h20_1/2 + 10`),绝不能当位置用。
// 返回 {x, y, z},x/y 水平面,z 高度
var POS_FIELDS = ['xzq_1', 'uzq_1', 'azr_1', 'bzr_1', 'dzr_1', 'izr_1', 'jzr_1', 'rzf_1', 'gzh_1', 'pz7_1'];
var POS_FIELDS = ['bzr_1', 'gzh_1', 'pz7_1'];
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; }
if (Comp && Comp[Key] && typeof Comp[Key].f20_1 === 'number') {
return { x: Comp[Key].f20_1, y: Comp[Key].g20_1, z: Comp[Key].h20_1 };
var V = Comp && Comp[Key];
if (V && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === '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 };
}
}
}
// 兜底 1:遍历坦克全部组件,找任意"带 O$ 向量"的组件
var Found = null;
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var V = comp[K];
if (V && typeof V === 'object' && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') {
if (Math.abs(V.f20_1) < 1e6 && Math.abs(V.g20_1) < 1e6 && Math.abs(V.h20_1) < 1e6) {
Found = { x: V.f20_1, y: V.g20_1, z: V.h20_1 };
return;
}
}
}
});
} catch (e) {}
if (Found) return Found;
// 兜底 2:矩阵式位置 —— 引擎的世界矩阵通常是长度 ≥16 的数组,[12],[13],[14] 是平移分量
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var M = comp[K];
if (M && typeof M === 'object' && typeof M.length === 'number' && M.length >= 15) {
var X = M[12], Y = M[13], Z = M[14];
if (typeof X === 'number' && typeof Y === 'number' && typeof Z === 'number' &&
Math.abs(X) < 1e6 && Math.abs(Y) < 1e6 && Math.abs(Z) < 1e6 &&
(X !== 0 || Y !== 0 || Z !== 0)) {
Found = { x: X, y: Y, z: Z };
return;
}
}
}
});
} catch (e) {}
return Found;
return null;
}
// ===== 获取坦克状态值(Yjt 组件的 vzr_1.k3_1) =====
@@ -408,6 +377,56 @@
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 自动诊断:一次性打印关键链路状态(供新版字段平移后快速定位) =====
function SelfDiagnose() {
var R = { gm: false, tanks: -1, posHit: {}, posFail: 0, camera: null, scene: false, errors: [] };
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); }
var list = null;
try { list = g.m10b_1.j11y(); 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++) {
var T = null;
try { T = list.g1(i); } catch (e) { continue; }
if (!T) continue;
var P = GetTankPosition(T);
if (P) {
// 记录命中来源(候选字段名 或 fallback)
var Src = 'fallback';
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; }
}
R.posHit[Src] = (R.posHit[Src] || 0) + 1;
} else {
R.posFail++;
// 失败时把该坦克组件的字段名与"候选向量"打印出来,便于定位
try {
var Fields = [];
T.qz1_1.s2a_1.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' : '');
if (Kind) Fields.push(K2 + ':' + Kind);
}
}
});
R.errors.push('tank' + i + ' 组件字段向量: ' + Fields.slice(0, 40).join(','));
} catch (e2) { R.errors.push('tank' + i + ' dump err: ' + e2.message); }
}
}
}
try { R.camera = GetCameraInfo(g); } catch (e) { R.errors.push('camera: ' + e.message); }
return R;
}
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -450,70 +469,46 @@
return false;
}
// ===== 获取相机信息($r/gq 相机组件) =====
// 遍历 GameMode entity 和所有坦克 entity 查找活跃相机组件
// 策略1:查找 $r/gq 相机组件(有 kym_1 位置 和 lym_1 方向)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1),从 jzh_1 获取相机组件
// 返回 {yaw, pitch, fov, pos:{x,y,z}}
// 注意:lym_1.h20_1 是 roll(绕Z),lym_1.f20_1 是 pitch(绕X)
// ===== 获取相机信息(场景主相机 scene.iyx_1) =====
// 新旧差异:旧版是找挂在实体上的相机组件(kym_1/lym_1),新版这些字段已被别的类占用;
// 新版直接暴露主相机对象:gm.lz4().jz1_1(场景).iyx_1(相机,类 Jf/lc)
// 相机位置 = t35_1(O$ 向量)
// 视图旋转 = d37_1(列主序 4x4,行·点)
// 视投影矩阵 = g37_1(world → clip,行·点,w>0 为可见)
// 视口 = w36_1.h29()/g29(),FOV = b37_1
// 朝向:视图矩阵第 2 列 = 相机 +Z 轴,相机看向 -Z → forward = -(d37_1[2],[6],[10])
// 返回 {pos, yaw, pitch, fov, heading, matrix, viewW, viewH}
// yaw/pitch 供 Project3D 使用(forward = (-sin(yaw), cos(yaw), sin(pitch)))
// heading 供雷达使用(forward = (sin(heading), cos(heading)),水平面)
function GetCameraInfo(gm) {
var entities = [];
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:$r/gq 相机组件
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:k7 相机控制器
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
return {
yaw: found.dyw_1.h20_1,
pitch: found.dyw_1.f20_1,
fov: found.byw_1 || 1.0471976,
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
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 fx = 0, fy = 1, fz = 0;
if (V && typeof V.length === 'number' && V.length >= 11) {
fx = -V[2]; fy = -V[6]; fz = -V[10];
var len = Math.sqrt(fx * fx + fy * fy + fz * fz) || 1;
fx /= len; fy /= len; fz /= len;
}
var viewW = 0, viewH = 0;
try { viewW = cam.w36_1.h29(); viewH = cam.w36_1.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,
viewW: viewW,
viewH: viewH
};
} catch (e) {
return null;
}
return null;
}
// ===== 获取全局碰撞检测器(uMt 组件,挂在 GameMode entity 上) =====
@@ -581,6 +576,24 @@
}
}
// ===== 3D 到 2D 投影(矩阵版,最可靠) =====
// 直接用相机自带的 world→clip 矩阵(scene.iyx_1.g37_1),不依赖任何欧拉角约定。
// 矩阵按列主序存放、按"行·点"使用(与引擎 z37/a38 一致)。
function Project3DMatrix(camInfo, worldPos, screenW, screenH) {
var M = camInfo && camInfo.matrix;
if (!M || typeof M.length !== 'number' || M.length < 16) return null;
var px = worldPos.x, py = worldPos.y, pz = worldPos.z;
var cx = M[0] * px + M[4] * py + M[8] * pz + M[12];
var cy = M[1] * px + M[5] * py + M[9] * pz + M[13];
var cw = M[3] * px + M[7] * py + M[11] * pz + M[15];
if (!(cw > 0.0001)) return null;
return {
x: (cx / cw * 0.5 + 0.5) * screenW,
y: (1 - (cy / cw * 0.5 + 0.5)) * screenH,
depth: cw
};
}
// ===== 3D 到 2D 投影(用于 UI 标记) =====
// 标准透视投影:forward/right/up 三轴正交基
function Project3D(worldPos, camPos, yaw, pitch, fov, screenW, screenH) {
@@ -735,6 +748,7 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
SelfDiagnose: SelfDiagnose,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
@@ -746,6 +760,7 @@
MkVec: MkVec,
RaycastDistance: RaycastDistance,
Project3D: Project3D,
Project3DMatrix: Project3DMatrix,
TransformVertex: TransformVertex,
MakeDraggable: MakeDraggable
};
@@ -804,79 +819,13 @@
return null;
}
// ===== 获取相机方向(gq/$r 组件的 lym_1.h20_1 = 偏航角) =====
// 相机组件可能在 GameMode entity、玩家坦克或任何坦克上
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段),从 jzh_1 获取相机组件
// ===== 获取相机朝向(新版:直接用场景主相机 scene.iyx_1 的视图矩阵算水平朝向) =====
// 雷达约定:forward = (sin(angle), cos(angle)),x/y 为水平面
function getCameraHeading() {
var entities = [];
// 1. GameMode entity
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
// 2. 所有坦克列表中的 entity
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
// 在每个 entity 上查找相机组件
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
// 检查是否是活跃的相机(lym_1 不全为0)
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段)
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
// lym_1.h20_1 = -atan2(t, n) = 偏航角
// lym_1.f20_1 = atan2(i, e) = 俯仰角
return {
yaw: -found.dyw_1.h20_1, // 偏航角(转为正向)
pitch: found.dyw_1.f20_1, // 俯仰角
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
}
}
return null;
var ci = null;
try { ci = lib.GetCameraInfo(gm); } catch (e) { ci = null; }
if (!ci) return null;
return { yaw: ci.heading, pitch: ci.pitch, pos: ci.pos };
}
// 玩家坦克:使用公共库
@@ -1312,6 +1261,12 @@
// ght 尚未捕获 / 不在战局:仍画静态雷达框,内容留空(等进入战局自动出)
var Ready = !!gm;
// 进战局后打印一次自诊断(位置/相机链路),便于新版字段平移时快速定位
if (Ready && !radar._diagPrinted) {
radar._diagPrinted = true;
try { console.log('[雷达] 自诊断:', lib.SelfDiagnose()); } catch (e) {}
}
try {
var tankList = null, count = 0;
if (Ready) {
@@ -1667,6 +1622,15 @@
return lib.GetCameraInfo(gm);
}
var Project3D = lib.Project3D;
var Project3DMatrix = lib.Project3DMatrix;
// 优先用相机矩阵投影(新版唯一可靠路径);自由视角虚拟相机没有矩阵时退回欧拉角投影
function ProjectPoint(pos, camInfo, screenW, screenH) {
if (camInfo && camInfo.matrix) {
var r = Project3DMatrix(camInfo, pos, screenW, screenH);
if (r) return r;
}
return Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
}
// 人机标记:同队内装备分数相同且 < 8000(lib.ScanBots 定期扫描 → IsBot 查询)
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
var LastBotScanAt = 0; // 人机扫描节流(约每 0.3 秒一次)
@@ -1677,6 +1641,13 @@
focal: 1, halfW: 0, halfH: 0, px: 0, py: 0, pz: 0
};
function UpdateCam(camInfo, screenW, screenH) {
// 新 build:直接用相机自带的 world→clip 矩阵(g37_1),不依赖欧拉角约定
var M = camInfo.matrix;
if (M && typeof M.length === 'number' && M.length >= 16) {
Cam.M = M; Cam.hasM = true;
} else {
Cam.M = null; Cam.hasM = false;
}
var cp = Math.cos(camInfo.pitch), sp = Math.sin(camInfo.pitch);
var cy = Math.cos(camInfo.yaw), sy = Math.sin(camInfo.yaw);
Cam.fx = cp * (-sy); Cam.fy = cp * cy; Cam.fz = sp; // forward
@@ -1686,6 +1657,7 @@
Cam.uz = Cam.rx * Cam.fy - Cam.ry * Cam.fx;
Cam.focal = screenH / (2 * Math.tan(camInfo.fov / 2));
Cam.halfW = screenW / 2; Cam.halfH = screenH / 2;
Cam.screenW = screenW; Cam.screenH = screenH;
Cam.px = camInfo.pos.x; Cam.py = camInfo.pos.y; Cam.pz = camInfo.pos.z;
}
@@ -1712,6 +1684,32 @@
m8 = worldMatrix[8], m9 = worldMatrix[9], m10 = worldMatrix[10],
m12 = worldMatrix[12], m13 = worldMatrix[13], m14 = worldMatrix[14];
var px = pos.x, py = pos.y, pz = pos.z;
// 新 build:矩阵投影(world→clip),完全跟随引擎自己的相机矩阵
if (Cam.hasM) {
var M = Cam.M;
var mm0 = M[0], mm1 = M[1], mm3 = M[3],
mm4 = M[4], mm5 = M[5], mm7 = M[7],
mm8 = M[8], mm9 = M[9], mm11 = M[11],
mm12 = M[12], mm13 = M[13], mm15 = M[15];
var sw = Cam.screenW, sh = Cam.screenH;
for (var v2 = 0; v2 < vc; v2++) {
var b2x = positions[v2 * stride], b2y = positions[v2 * stride + 1], b2z = positions[v2 * stride + 2];
var w2x = m0 * b2x + m4 * b2y + m8 * b2z + m12;
var w2y = m1 * b2x + m5 * b2y + m9 * b2z + m13;
var w2z = m2 * b2x + m6 * b2y + m10 * b2z + m14;
VB_Wx[v2] = w2x; VB_Wy[v2] = w2y; VB_Wz[v2] = w2z;
var dd2x = w2x - px, dd2y = w2y - py, dd2z = w2z - pz;
if (dd2x * dd2x + dd2y * dd2y + dd2z * dd2z > 6250000) { VB_Ok[v2] = 0; continue; }
var ccx = mm0 * w2x + mm4 * w2y + mm8 * w2z + mm12;
var ccy = mm1 * w2x + mm5 * w2y + mm9 * w2z + mm13;
var ccw = mm3 * w2x + mm7 * w2y + mm11 * w2z + mm15;
if (!(ccw > 0.0001)) { VB_Ok[v2] = 0; continue; }
VB_Sx[v2] = (ccx / ccw * 0.5 + 0.5) * sw;
VB_Sy[v2] = (1 - (ccy / ccw * 0.5 + 0.5)) * sh;
VB_Ok[v2] = 1;
}
return vc;
}
var cfx = Cam.fx, cfy = Cam.fy, cfz = Cam.fz;
var crx = Cam.rx, cry = Cam.ry, crz = Cam.rz;
var cux = Cam.ux, cuy = Cam.uy, cuz = Cam.uz;
@@ -2015,7 +2013,7 @@
// ===== 兜底标记点:模型采集不到/被过滤时,至少画一个点,避免坦克完全不可见 =====
function DrawTankMarker(ctx, pos, camInfo, screenW, screenH, color) {
var p = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var p = ProjectPoint(pos, camInfo, screenW, screenH);
if (!p) return;
ctx.fillStyle = color;
ctx.beginPath();
@@ -2563,8 +2561,8 @@
// 人机标记:坦克上方写「人机」(同队装备分数相同且 < 8000)
if (typeof IsBot === 'function' && IsBot(tank)) {
var botPt = Project3D({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var botPt = ProjectPoint({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo, screenW, screenH);
if (botPt) {
ctx.save();
ctx.font = 'bold 13px Consolas,monospace';
+179 -181
View File
@@ -263,60 +263,29 @@
}
// ===== 获取坦克位置(物理组件里的位置向量) =====
// 物理组件的字段名每个构建都可能平移,这里按"含 f20_1/g20_1/h20_1 的 O$ 向量"做多级探测:
// ejt(tankPhysics)里可能的候选字段都试一遍,命中即用。
// 新旧差异(main.7cfa4058 vs 旧版):
// 物理组件类 ejt(组件名 "tankPhysics")的字段整体平移,构造函数字段序列一一对应:
// 旧 vjt: zzg_1 azh_1 czh_1 dzh_1 ezh_1 fzh_1 **gzh_1** hzh_1 izh_1 ...
// 新 ejt: uzq_1 vzq_1 xzq_1 yzq_1 zzq_1 azr_1 **bzr_1** czr_1 dzr_1 ...
// njt() 里 `bzr_1.l27(上一物理态位置, 当前物理态位置, 插值)` —— bzr_1 就是世界位置。
// ⚠ xzq_1 是车体半尺寸(用于包围盒/出生点:`-xzq_1.h20_1/2 + 10`),绝不能当位置用。
// 返回 {x, y, z},x/y 水平面,z 高度
var POS_FIELDS = ['xzq_1', 'uzq_1', 'azr_1', 'bzr_1', 'dzr_1', 'izr_1', 'jzr_1', 'rzf_1', 'gzh_1', 'pz7_1'];
var POS_FIELDS = ['bzr_1', 'gzh_1', 'pz7_1'];
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; }
if (Comp && Comp[Key] && typeof Comp[Key].f20_1 === 'number') {
return { x: Comp[Key].f20_1, y: Comp[Key].g20_1, z: Comp[Key].h20_1 };
var V = Comp && Comp[Key];
if (V && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === '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 };
}
}
}
// 兜底 1:遍历坦克全部组件,找任意"带 O$ 向量"的组件
var Found = null;
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var V = comp[K];
if (V && typeof V === 'object' && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') {
if (Math.abs(V.f20_1) < 1e6 && Math.abs(V.g20_1) < 1e6 && Math.abs(V.h20_1) < 1e6) {
Found = { x: V.f20_1, y: V.g20_1, z: V.h20_1 };
return;
}
}
}
});
} catch (e) {}
if (Found) return Found;
// 兜底 2:矩阵式位置 —— 引擎的世界矩阵通常是长度 ≥16 的数组,[12],[13],[14] 是平移分量
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var M = comp[K];
if (M && typeof M === 'object' && typeof M.length === 'number' && M.length >= 15) {
var X = M[12], Y = M[13], Z = M[14];
if (typeof X === 'number' && typeof Y === 'number' && typeof Z === 'number' &&
Math.abs(X) < 1e6 && Math.abs(Y) < 1e6 && Math.abs(Z) < 1e6 &&
(X !== 0 || Y !== 0 || Z !== 0)) {
Found = { x: X, y: Y, z: Z };
return;
}
}
}
});
} catch (e) {}
return Found;
return null;
}
// ===== 获取坦克状态值(Yjt 组件的 vzr_1.k3_1) =====
@@ -408,6 +377,56 @@
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 自动诊断:一次性打印关键链路状态(供新版字段平移后快速定位) =====
function SelfDiagnose() {
var R = { gm: false, tanks: -1, posHit: {}, posFail: 0, camera: null, scene: false, errors: [] };
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); }
var list = null;
try { list = g.m10b_1.j11y(); 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++) {
var T = null;
try { T = list.g1(i); } catch (e) { continue; }
if (!T) continue;
var P = GetTankPosition(T);
if (P) {
// 记录命中来源(候选字段名 或 fallback)
var Src = 'fallback';
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; }
}
R.posHit[Src] = (R.posHit[Src] || 0) + 1;
} else {
R.posFail++;
// 失败时把该坦克组件的字段名与"候选向量"打印出来,便于定位
try {
var Fields = [];
T.qz1_1.s2a_1.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' : '');
if (Kind) Fields.push(K2 + ':' + Kind);
}
}
});
R.errors.push('tank' + i + ' 组件字段向量: ' + Fields.slice(0, 40).join(','));
} catch (e2) { R.errors.push('tank' + i + ' dump err: ' + e2.message); }
}
}
}
try { R.camera = GetCameraInfo(g); } catch (e) { R.errors.push('camera: ' + e.message); }
return R;
}
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -450,70 +469,46 @@
return false;
}
// ===== 获取相机信息($r/gq 相机组件) =====
// 遍历 GameMode entity 和所有坦克 entity 查找活跃相机组件
// 策略1:查找 $r/gq 相机组件(有 kym_1 位置 和 lym_1 方向)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1),从 jzh_1 获取相机组件
// 返回 {yaw, pitch, fov, pos:{x,y,z}}
// 注意:lym_1.h20_1 是 roll(绕Z),lym_1.f20_1 是 pitch(绕X)
// ===== 获取相机信息(场景主相机 scene.iyx_1) =====
// 新旧差异:旧版是找挂在实体上的相机组件(kym_1/lym_1),新版这些字段已被别的类占用;
// 新版直接暴露主相机对象:gm.lz4().jz1_1(场景).iyx_1(相机,类 Jf/lc)
// 相机位置 = t35_1(O$ 向量)
// 视图旋转 = d37_1(列主序 4x4,行·点)
// 视投影矩阵 = g37_1(world → clip,行·点,w>0 为可见)
// 视口 = w36_1.h29()/g29(),FOV = b37_1
// 朝向:视图矩阵第 2 列 = 相机 +Z 轴,相机看向 -Z → forward = -(d37_1[2],[6],[10])
// 返回 {pos, yaw, pitch, fov, heading, matrix, viewW, viewH}
// yaw/pitch 供 Project3D 使用(forward = (-sin(yaw), cos(yaw), sin(pitch)))
// heading 供雷达使用(forward = (sin(heading), cos(heading)),水平面)
function GetCameraInfo(gm) {
var entities = [];
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:$r/gq 相机组件
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:k7 相机控制器
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
return {
yaw: found.dyw_1.h20_1,
pitch: found.dyw_1.f20_1,
fov: found.byw_1 || 1.0471976,
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
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 fx = 0, fy = 1, fz = 0;
if (V && typeof V.length === 'number' && V.length >= 11) {
fx = -V[2]; fy = -V[6]; fz = -V[10];
var len = Math.sqrt(fx * fx + fy * fy + fz * fz) || 1;
fx /= len; fy /= len; fz /= len;
}
var viewW = 0, viewH = 0;
try { viewW = cam.w36_1.h29(); viewH = cam.w36_1.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,
viewW: viewW,
viewH: viewH
};
} catch (e) {
return null;
}
return null;
}
// ===== 获取全局碰撞检测器(uMt 组件,挂在 GameMode entity 上) =====
@@ -581,6 +576,24 @@
}
}
// ===== 3D 到 2D 投影(矩阵版,最可靠) =====
// 直接用相机自带的 world→clip 矩阵(scene.iyx_1.g37_1),不依赖任何欧拉角约定。
// 矩阵按列主序存放、按"行·点"使用(与引擎 z37/a38 一致)。
function Project3DMatrix(camInfo, worldPos, screenW, screenH) {
var M = camInfo && camInfo.matrix;
if (!M || typeof M.length !== 'number' || M.length < 16) return null;
var px = worldPos.x, py = worldPos.y, pz = worldPos.z;
var cx = M[0] * px + M[4] * py + M[8] * pz + M[12];
var cy = M[1] * px + M[5] * py + M[9] * pz + M[13];
var cw = M[3] * px + M[7] * py + M[11] * pz + M[15];
if (!(cw > 0.0001)) return null;
return {
x: (cx / cw * 0.5 + 0.5) * screenW,
y: (1 - (cy / cw * 0.5 + 0.5)) * screenH,
depth: cw
};
}
// ===== 3D 到 2D 投影(用于 UI 标记) =====
// 标准透视投影:forward/right/up 三轴正交基
function Project3D(worldPos, camPos, yaw, pitch, fov, screenW, screenH) {
@@ -735,6 +748,7 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
SelfDiagnose: SelfDiagnose,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
@@ -746,6 +760,7 @@
MkVec: MkVec,
RaycastDistance: RaycastDistance,
Project3D: Project3D,
Project3DMatrix: Project3DMatrix,
TransformVertex: TransformVertex,
MakeDraggable: MakeDraggable
};
@@ -804,79 +819,13 @@
return null;
}
// ===== 获取相机方向(gq/$r 组件的 lym_1.h20_1 = 偏航角) =====
// 相机组件可能在 GameMode entity、玩家坦克或任何坦克上
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段),从 jzh_1 获取相机组件
// ===== 获取相机朝向(新版:直接用场景主相机 scene.iyx_1 的视图矩阵算水平朝向) =====
// 雷达约定:forward = (sin(angle), cos(angle)),x/y 为水平面
function getCameraHeading() {
var entities = [];
// 1. GameMode entity
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
// 2. 所有坦克列表中的 entity
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
// 在每个 entity 上查找相机组件
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
// 检查是否是活跃的相机(lym_1 不全为0)
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段)
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
// lym_1.h20_1 = -atan2(t, n) = 偏航角
// lym_1.f20_1 = atan2(i, e) = 俯仰角
return {
yaw: -found.dyw_1.h20_1, // 偏航角(转为正向)
pitch: found.dyw_1.f20_1, // 俯仰角
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
}
}
return null;
var ci = null;
try { ci = lib.GetCameraInfo(gm); } catch (e) { ci = null; }
if (!ci) return null;
return { yaw: ci.heading, pitch: ci.pitch, pos: ci.pos };
}
// 玩家坦克:使用公共库
@@ -1312,6 +1261,12 @@
// ght 尚未捕获 / 不在战局:仍画静态雷达框,内容留空(等进入战局自动出)
var Ready = !!gm;
// 进战局后打印一次自诊断(位置/相机链路),便于新版字段平移时快速定位
if (Ready && !radar._diagPrinted) {
radar._diagPrinted = true;
try { console.log('[雷达] 自诊断:', lib.SelfDiagnose()); } catch (e) {}
}
try {
var tankList = null, count = 0;
if (Ready) {
@@ -1667,6 +1622,15 @@
return lib.GetCameraInfo(gm);
}
var Project3D = lib.Project3D;
var Project3DMatrix = lib.Project3DMatrix;
// 优先用相机矩阵投影(新版唯一可靠路径);自由视角虚拟相机没有矩阵时退回欧拉角投影
function ProjectPoint(pos, camInfo, screenW, screenH) {
if (camInfo && camInfo.matrix) {
var r = Project3DMatrix(camInfo, pos, screenW, screenH);
if (r) return r;
}
return Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
}
// 人机标记:同队内装备分数相同且 < 8000(lib.ScanBots 定期扫描 → IsBot 查询)
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
var LastBotScanAt = 0; // 人机扫描节流(约每 0.3 秒一次)
@@ -1677,6 +1641,13 @@
focal: 1, halfW: 0, halfH: 0, px: 0, py: 0, pz: 0
};
function UpdateCam(camInfo, screenW, screenH) {
// 新 build:直接用相机自带的 world→clip 矩阵(g37_1),不依赖欧拉角约定
var M = camInfo.matrix;
if (M && typeof M.length === 'number' && M.length >= 16) {
Cam.M = M; Cam.hasM = true;
} else {
Cam.M = null; Cam.hasM = false;
}
var cp = Math.cos(camInfo.pitch), sp = Math.sin(camInfo.pitch);
var cy = Math.cos(camInfo.yaw), sy = Math.sin(camInfo.yaw);
Cam.fx = cp * (-sy); Cam.fy = cp * cy; Cam.fz = sp; // forward
@@ -1686,6 +1657,7 @@
Cam.uz = Cam.rx * Cam.fy - Cam.ry * Cam.fx;
Cam.focal = screenH / (2 * Math.tan(camInfo.fov / 2));
Cam.halfW = screenW / 2; Cam.halfH = screenH / 2;
Cam.screenW = screenW; Cam.screenH = screenH;
Cam.px = camInfo.pos.x; Cam.py = camInfo.pos.y; Cam.pz = camInfo.pos.z;
}
@@ -1712,6 +1684,32 @@
m8 = worldMatrix[8], m9 = worldMatrix[9], m10 = worldMatrix[10],
m12 = worldMatrix[12], m13 = worldMatrix[13], m14 = worldMatrix[14];
var px = pos.x, py = pos.y, pz = pos.z;
// 新 build:矩阵投影(world→clip),完全跟随引擎自己的相机矩阵
if (Cam.hasM) {
var M = Cam.M;
var mm0 = M[0], mm1 = M[1], mm3 = M[3],
mm4 = M[4], mm5 = M[5], mm7 = M[7],
mm8 = M[8], mm9 = M[9], mm11 = M[11],
mm12 = M[12], mm13 = M[13], mm15 = M[15];
var sw = Cam.screenW, sh = Cam.screenH;
for (var v2 = 0; v2 < vc; v2++) {
var b2x = positions[v2 * stride], b2y = positions[v2 * stride + 1], b2z = positions[v2 * stride + 2];
var w2x = m0 * b2x + m4 * b2y + m8 * b2z + m12;
var w2y = m1 * b2x + m5 * b2y + m9 * b2z + m13;
var w2z = m2 * b2x + m6 * b2y + m10 * b2z + m14;
VB_Wx[v2] = w2x; VB_Wy[v2] = w2y; VB_Wz[v2] = w2z;
var dd2x = w2x - px, dd2y = w2y - py, dd2z = w2z - pz;
if (dd2x * dd2x + dd2y * dd2y + dd2z * dd2z > 6250000) { VB_Ok[v2] = 0; continue; }
var ccx = mm0 * w2x + mm4 * w2y + mm8 * w2z + mm12;
var ccy = mm1 * w2x + mm5 * w2y + mm9 * w2z + mm13;
var ccw = mm3 * w2x + mm7 * w2y + mm11 * w2z + mm15;
if (!(ccw > 0.0001)) { VB_Ok[v2] = 0; continue; }
VB_Sx[v2] = (ccx / ccw * 0.5 + 0.5) * sw;
VB_Sy[v2] = (1 - (ccy / ccw * 0.5 + 0.5)) * sh;
VB_Ok[v2] = 1;
}
return vc;
}
var cfx = Cam.fx, cfy = Cam.fy, cfz = Cam.fz;
var crx = Cam.rx, cry = Cam.ry, crz = Cam.rz;
var cux = Cam.ux, cuy = Cam.uy, cuz = Cam.uz;
@@ -2015,7 +2013,7 @@
// ===== 兜底标记点:模型采集不到/被过滤时,至少画一个点,避免坦克完全不可见 =====
function DrawTankMarker(ctx, pos, camInfo, screenW, screenH, color) {
var p = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var p = ProjectPoint(pos, camInfo, screenW, screenH);
if (!p) return;
ctx.fillStyle = color;
ctx.beginPath();
@@ -2563,8 +2561,8 @@
// 人机标记:坦克上方写「人机」(同队装备分数相同且 < 8000)
if (typeof IsBot === 'function' && IsBot(tank)) {
var botPt = Project3D({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var botPt = ProjectPoint({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo, screenW, screenH);
if (botPt) {
ctx.save();
ctx.font = 'bold 13px Consolas,monospace';
+46 -3
View File
@@ -107,6 +107,15 @@
return lib.GetCameraInfo(gm);
}
var Project3D = lib.Project3D;
var Project3DMatrix = lib.Project3DMatrix;
// 优先用相机矩阵投影(新版唯一可靠路径);自由视角虚拟相机没有矩阵时退回欧拉角投影
function ProjectPoint(pos, camInfo, screenW, screenH) {
if (camInfo && camInfo.matrix) {
var r = Project3DMatrix(camInfo, pos, screenW, screenH);
if (r) return r;
}
return Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
}
// 人机标记:同队内装备分数相同且 < 8000(lib.ScanBots 定期扫描 → IsBot 查询)
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
var LastBotScanAt = 0; // 人机扫描节流(约每 0.3 秒一次)
@@ -117,6 +126,13 @@
focal: 1, halfW: 0, halfH: 0, px: 0, py: 0, pz: 0
};
function UpdateCam(camInfo, screenW, screenH) {
// 新 build:直接用相机自带的 world→clip 矩阵(g37_1),不依赖欧拉角约定
var M = camInfo.matrix;
if (M && typeof M.length === 'number' && M.length >= 16) {
Cam.M = M; Cam.hasM = true;
} else {
Cam.M = null; Cam.hasM = false;
}
var cp = Math.cos(camInfo.pitch), sp = Math.sin(camInfo.pitch);
var cy = Math.cos(camInfo.yaw), sy = Math.sin(camInfo.yaw);
Cam.fx = cp * (-sy); Cam.fy = cp * cy; Cam.fz = sp; // forward
@@ -126,6 +142,7 @@
Cam.uz = Cam.rx * Cam.fy - Cam.ry * Cam.fx;
Cam.focal = screenH / (2 * Math.tan(camInfo.fov / 2));
Cam.halfW = screenW / 2; Cam.halfH = screenH / 2;
Cam.screenW = screenW; Cam.screenH = screenH;
Cam.px = camInfo.pos.x; Cam.py = camInfo.pos.y; Cam.pz = camInfo.pos.z;
}
@@ -152,6 +169,32 @@
m8 = worldMatrix[8], m9 = worldMatrix[9], m10 = worldMatrix[10],
m12 = worldMatrix[12], m13 = worldMatrix[13], m14 = worldMatrix[14];
var px = pos.x, py = pos.y, pz = pos.z;
// 新 build:矩阵投影(world→clip),完全跟随引擎自己的相机矩阵
if (Cam.hasM) {
var M = Cam.M;
var mm0 = M[0], mm1 = M[1], mm3 = M[3],
mm4 = M[4], mm5 = M[5], mm7 = M[7],
mm8 = M[8], mm9 = M[9], mm11 = M[11],
mm12 = M[12], mm13 = M[13], mm15 = M[15];
var sw = Cam.screenW, sh = Cam.screenH;
for (var v2 = 0; v2 < vc; v2++) {
var b2x = positions[v2 * stride], b2y = positions[v2 * stride + 1], b2z = positions[v2 * stride + 2];
var w2x = m0 * b2x + m4 * b2y + m8 * b2z + m12;
var w2y = m1 * b2x + m5 * b2y + m9 * b2z + m13;
var w2z = m2 * b2x + m6 * b2y + m10 * b2z + m14;
VB_Wx[v2] = w2x; VB_Wy[v2] = w2y; VB_Wz[v2] = w2z;
var dd2x = w2x - px, dd2y = w2y - py, dd2z = w2z - pz;
if (dd2x * dd2x + dd2y * dd2y + dd2z * dd2z > 6250000) { VB_Ok[v2] = 0; continue; }
var ccx = mm0 * w2x + mm4 * w2y + mm8 * w2z + mm12;
var ccy = mm1 * w2x + mm5 * w2y + mm9 * w2z + mm13;
var ccw = mm3 * w2x + mm7 * w2y + mm11 * w2z + mm15;
if (!(ccw > 0.0001)) { VB_Ok[v2] = 0; continue; }
VB_Sx[v2] = (ccx / ccw * 0.5 + 0.5) * sw;
VB_Sy[v2] = (1 - (ccy / ccw * 0.5 + 0.5)) * sh;
VB_Ok[v2] = 1;
}
return vc;
}
var cfx = Cam.fx, cfy = Cam.fy, cfz = Cam.fz;
var crx = Cam.rx, cry = Cam.ry, crz = Cam.rz;
var cux = Cam.ux, cuy = Cam.uy, cuz = Cam.uz;
@@ -455,7 +498,7 @@
// ===== 兜底标记点:模型采集不到/被过滤时,至少画一个点,避免坦克完全不可见 =====
function DrawTankMarker(ctx, pos, camInfo, screenW, screenH, color) {
var p = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var p = ProjectPoint(pos, camInfo, screenW, screenH);
if (!p) return;
ctx.fillStyle = color;
ctx.beginPath();
@@ -1003,8 +1046,8 @@
// 人机标记:坦克上方写「人机」(同队装备分数相同且 < 8000)
if (typeof IsBot === 'function' && IsBot(tank)) {
var botPt = Project3D({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var botPt = ProjectPoint({ x: pos.x, y: pos.y, z: pos.z + 1300 },
camInfo, screenW, screenH);
if (botPt) {
ctx.save();
ctx.font = 'bold 13px Consolas,monospace';
+122 -107
View File
@@ -48,60 +48,29 @@
}
// ===== 获取坦克位置(物理组件里的位置向量) =====
// 物理组件的字段名每个构建都可能平移,这里按"含 f20_1/g20_1/h20_1 的 O$ 向量"做多级探测:
// ejt(tankPhysics)里可能的候选字段都试一遍,命中即用。
// 新旧差异(main.7cfa4058 vs 旧版):
// 物理组件类 ejt(组件名 "tankPhysics")的字段整体平移,构造函数字段序列一一对应:
// 旧 vjt: zzg_1 azh_1 czh_1 dzh_1 ezh_1 fzh_1 **gzh_1** hzh_1 izh_1 ...
// 新 ejt: uzq_1 vzq_1 xzq_1 yzq_1 zzq_1 azr_1 **bzr_1** czr_1 dzr_1 ...
// njt() 里 `bzr_1.l27(上一物理态位置, 当前物理态位置, 插值)` —— bzr_1 就是世界位置。
// ⚠ xzq_1 是车体半尺寸(用于包围盒/出生点:`-xzq_1.h20_1/2 + 10`),绝不能当位置用。
// 返回 {x, y, z},x/y 水平面,z 高度
var POS_FIELDS = ['xzq_1', 'uzq_1', 'azr_1', 'bzr_1', 'dzr_1', 'izr_1', 'jzr_1', 'rzf_1', 'gzh_1', 'pz7_1'];
var POS_FIELDS = ['bzr_1', 'gzh_1', 'pz7_1'];
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; }
if (Comp && Comp[Key] && typeof Comp[Key].f20_1 === 'number') {
return { x: Comp[Key].f20_1, y: Comp[Key].g20_1, z: Comp[Key].h20_1 };
var V = Comp && Comp[Key];
if (V && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === '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 };
}
}
}
// 兜底 1:遍历坦克全部组件,找任意"带 O$ 向量"的组件
var Found = null;
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var V = comp[K];
if (V && typeof V === 'object' && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') {
if (Math.abs(V.f20_1) < 1e6 && Math.abs(V.g20_1) < 1e6 && Math.abs(V.h20_1) < 1e6) {
Found = { x: V.f20_1, y: V.g20_1, z: V.h20_1 };
return;
}
}
}
});
} catch (e) {}
if (Found) return Found;
// 兜底 2:矩阵式位置 —— 引擎的世界矩阵通常是长度 ≥16 的数组,[12],[13],[14] 是平移分量
try {
tank.qz1_1.s2a_1.forEach(function (valueList) {
if (Found) return;
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
for (var K in comp) {
var M = comp[K];
if (M && typeof M === 'object' && typeof M.length === 'number' && M.length >= 15) {
var X = M[12], Y = M[13], Z = M[14];
if (typeof X === 'number' && typeof Y === 'number' && typeof Z === 'number' &&
Math.abs(X) < 1e6 && Math.abs(Y) < 1e6 && Math.abs(Z) < 1e6 &&
(X !== 0 || Y !== 0 || Z !== 0)) {
Found = { x: X, y: Y, z: Z };
return;
}
}
}
});
} catch (e) {}
return Found;
return null;
}
// ===== 获取坦克状态值(Yjt 组件的 vzr_1.k3_1) =====
@@ -193,6 +162,56 @@
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 自动诊断:一次性打印关键链路状态(供新版字段平移后快速定位) =====
function SelfDiagnose() {
var R = { gm: false, tanks: -1, posHit: {}, posFail: 0, camera: null, scene: false, errors: [] };
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); }
var list = null;
try { list = g.m10b_1.j11y(); 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++) {
var T = null;
try { T = list.g1(i); } catch (e) { continue; }
if (!T) continue;
var P = GetTankPosition(T);
if (P) {
// 记录命中来源(候选字段名 或 fallback)
var Src = 'fallback';
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; }
}
R.posHit[Src] = (R.posHit[Src] || 0) + 1;
} else {
R.posFail++;
// 失败时把该坦克组件的字段名与"候选向量"打印出来,便于定位
try {
var Fields = [];
T.qz1_1.s2a_1.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' : '');
if (Kind) Fields.push(K2 + ':' + Kind);
}
}
});
R.errors.push('tank' + i + ' 组件字段向量: ' + Fields.slice(0, 40).join(','));
} catch (e2) { R.errors.push('tank' + i + ' dump err: ' + e2.message); }
}
}
}
try { R.camera = GetCameraInfo(g); } catch (e) { R.errors.push('camera: ' + e.message); }
return R;
}
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -235,70 +254,46 @@
return false;
}
// ===== 获取相机信息($r/gq 相机组件) =====
// 遍历 GameMode entity 和所有坦克 entity 查找活跃相机组件
// 策略1:查找 $r/gq 相机组件(有 kym_1 位置 和 lym_1 方向)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1),从 jzh_1 获取相机组件
// 返回 {yaw, pitch, fov, pos:{x,y,z}}
// 注意:lym_1.h20_1 是 roll(绕Z),lym_1.f20_1 是 pitch(绕X)
// ===== 获取相机信息(场景主相机 scene.iyx_1) =====
// 新旧差异:旧版是找挂在实体上的相机组件(kym_1/lym_1),新版这些字段已被别的类占用;
// 新版直接暴露主相机对象:gm.lz4().jz1_1(场景).iyx_1(相机,类 Jf/lc)
// 相机位置 = t35_1(O$ 向量)
// 视图旋转 = d37_1(列主序 4x4,行·点)
// 视投影矩阵 = g37_1(world → clip,行·点,w>0 为可见)
// 视口 = w36_1.h29()/g29(),FOV = b37_1
// 朝向:视图矩阵第 2 列 = 相机 +Z 轴,相机看向 -Z → forward = -(d37_1[2],[6],[10])
// 返回 {pos, yaw, pitch, fov, heading, matrix, viewW, viewH}
// yaw/pitch 供 Project3D 使用(forward = (-sin(yaw), cos(yaw), sin(pitch)))
// heading 供雷达使用(forward = (sin(heading), cos(heading)),水平面)
function GetCameraInfo(gm) {
var entities = [];
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:$r/gq 相机组件
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:k7 相机控制器
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
return {
yaw: found.dyw_1.h20_1,
pitch: found.dyw_1.f20_1,
fov: found.byw_1 || 1.0471976,
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
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 fx = 0, fy = 1, fz = 0;
if (V && typeof V.length === 'number' && V.length >= 11) {
fx = -V[2]; fy = -V[6]; fz = -V[10];
var len = Math.sqrt(fx * fx + fy * fy + fz * fz) || 1;
fx /= len; fy /= len; fz /= len;
}
var viewW = 0, viewH = 0;
try { viewW = cam.w36_1.h29(); viewH = cam.w36_1.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,
viewW: viewW,
viewH: viewH
};
} catch (e) {
return null;
}
return null;
}
// ===== 获取全局碰撞检测器(uMt 组件,挂在 GameMode entity 上) =====
@@ -366,6 +361,24 @@
}
}
// ===== 3D 到 2D 投影(矩阵版,最可靠) =====
// 直接用相机自带的 world→clip 矩阵(scene.iyx_1.g37_1),不依赖任何欧拉角约定。
// 矩阵按列主序存放、按"行·点"使用(与引擎 z37/a38 一致)。
function Project3DMatrix(camInfo, worldPos, screenW, screenH) {
var M = camInfo && camInfo.matrix;
if (!M || typeof M.length !== 'number' || M.length < 16) return null;
var px = worldPos.x, py = worldPos.y, pz = worldPos.z;
var cx = M[0] * px + M[4] * py + M[8] * pz + M[12];
var cy = M[1] * px + M[5] * py + M[9] * pz + M[13];
var cw = M[3] * px + M[7] * py + M[11] * pz + M[15];
if (!(cw > 0.0001)) return null;
return {
x: (cx / cw * 0.5 + 0.5) * screenW,
y: (1 - (cy / cw * 0.5 + 0.5)) * screenH,
depth: cw
};
}
// ===== 3D 到 2D 投影(用于 UI 标记) =====
// 标准透视投影:forward/right/up 三轴正交基
function Project3D(worldPos, camPos, yaw, pitch, fov, screenW, screenH) {
@@ -520,6 +533,7 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
SelfDiagnose: SelfDiagnose,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
@@ -530,7 +544,8 @@
GetUMtComponent: GetUMtComponent,
MkVec: MkVec,
RaycastDistance: RaycastDistance,
Project3D: Project3D,
Project3D: Project3D,
Project3DMatrix: Project3DMatrix,
TransformVertex: TransformVertex,
MakeDraggable: MakeDraggable
};
+12 -72
View File
@@ -47,79 +47,13 @@
return null;
}
// ===== 获取相机方向(gq/$r 组件的 lym_1.h20_1 = 偏航角) =====
// 相机组件可能在 GameMode entity、玩家坦克或任何坦克上
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段),从 jzh_1 获取相机组件
// ===== 获取相机朝向(新版:直接用场景主相机 scene.iyx_1 的视图矩阵算水平朝向) =====
// 雷达约定:forward = (sin(angle), cos(angle)),x/y 为水平面
function getCameraHeading() {
var entities = [];
// 1. GameMode entity
try {
var gameScene = gm.lz4().jz1_1;
var gmEntity = gm.lz4();
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
// 2. 所有坦克列表中的 entity
try {
var tankList = gm.m10b_1.j11y();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
if (t) entities.push(t);
}
} catch(e) {}
// 在每个 entity 上查找相机组件
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue;
var found = null;
entity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (found) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (!comp) return;
// 策略1:查找 $r/gq 相机组件(有 kym_1 和 lym_1 字段)
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
// 检查是否是活跃的相机(lym_1 不全为0)
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1 字段)
if (comp.jzj_1 && comp.zzi_1) {
var camComp = comp.zzi_1;
if (camComp && camComp.cyw_1 && camComp.dyw_1 &&
(Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) {
found = camComp;
return;
}
}
} catch(e) {}
});
if (found) {
// lym_1.h20_1 = -atan2(t, n) = 偏航角
// lym_1.f20_1 = atan2(i, e) = 俯仰角
return {
yaw: -found.dyw_1.h20_1, // 偏航角(转为正向)
pitch: found.dyw_1.f20_1, // 俯仰角
pos: {
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
}
}
return null;
var ci = null;
try { ci = lib.GetCameraInfo(gm); } catch (e) { ci = null; }
if (!ci) return null;
return { yaw: ci.heading, pitch: ci.pitch, pos: ci.pos };
}
// 玩家坦克:使用公共库
@@ -555,6 +489,12 @@
// ght 尚未捕获 / 不在战局:仍画静态雷达框,内容留空(等进入战局自动出)
var Ready = !!gm;
// 进战局后打印一次自诊断(位置/相机链路),便于新版字段平移时快速定位
if (Ready && !radar._diagPrinted) {
radar._diagPrinted = true;
try { console.log('[雷达] 自诊断:', lib.SelfDiagnose()); } catch (e) {}
}
try {
var tankList = null, count = 0;
if (Ready) {