fix(tools): 按新旧差异修复位置与相机链路(雷达/透视恢复绘制)
This commit is contained in:
1 parent
9506648877
commit
a34cae5474
5 files changed
+537
-543
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
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
+121
-106
@@ -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,
|
||||
@@ -531,6 +545,7 @@
|
||||
MkVec: MkVec,
|
||||
RaycastDistance: RaycastDistance,
|
||||
Project3D: Project3D,
|
||||
Project3DMatrix: Project3DMatrix,
|
||||
TransformVertex: TransformVertex,
|
||||
MakeDraggable: MakeDraggable
|
||||
};
|
||||
|
||||
+12
-72
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user