Files
TankIC/tools/hornet_schema.js
T
TermiNexus bea22bb5f9 feat(noknockback): 新增拒绝击退开关,中弹不再被推开
在本地坦克实体的 kz7 事件派发处丢弃外力事件(ODt:作用点/方向/强度),坦克物理收不到就不产生位移与旋转;悬挂与行驶仍走 m9k 直调,不受影响。面板新增「拒绝击退」开关。字段名走 hornet_schema。
2026-10-20 20:35:19 +08:00

257 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// hornet_schema.js — 引擎匿名字段抽象层(字段映射总表)
//
// 为什么需要它:
// 游戏每个构建版本都会把类的字段名整体平移(如 ejt 的 zzq_1 / bzr_1),
// 工具里到处硬编码 `comp.zzq_1`、`tank.qz1_1.s2a_1` 这类匿名名,
// 版本一换就要全局搜替换,还容易改错(同名复用极严重)。
// 这里把「语义名 → 匿名字段路径」集中成一张表:
// · 工具只用语义名(Schema.Get(tank, 'Tank.StateValue'))
// · 换版本时只改这张表,并跑一次 SelfCheck() 看哪几项失配
//
// 路径语法:
// 'a.b.c' 属性链
// 'lz4()' 方法调用(末尾带括号即调用,中间也可混用,如 'm10b_1.j11y()')
//
// 用法:
// var S = window.__hornetSchema__;
// S.Get(gm, 'GameMode.TankList')
// S.Get(tank, 'Tank.StateValue')
// S.Check(gm, 'GameMode') // → { Ok: [...], Missing: [...] }
// S.SelfCheck() // 用当前战局对象自检并打印
//
// 版本:main.7cfa4058(字段核对自 module_87072 / 61378 / 92980 / 55088)
(function () {
'use strict';
// ===== 字段映射总表 =====
// 命名规则:<对象类型>.<语义名>
var Fields = {
// ---- 战斗实体基类(fe / BattleEntity)----
'Entity.Id': 'iz1_1', // 实体 ID(坦克/战场唯一)
'Entity.Components': 'qz1_1.s2a_1', // Map<组件标识, 组件列表>,取值要 valueList.g1(0)
// ---- GameMode(ght,window.__gameMode__)----
'GameMode.Scene': 'lz4()', // 场景实体
'GameMode.TankList': 'm10b_1.j11y()', // 坦克列表(e1() 数量 / g1(i) 取)
'GameMode.OwnedTank': 'u10b_1', // 本地玩家拥有的坦克(onTankPossessed)
'GameMode.ObservedTank': 't10b_1', // 当前观察目标(死亡/观战时可能指向敌人)
'GameMode.LocalTeam': 'v10b_1', // 本地队伍(与 u10b_1 同生命周期)
// ---- 坦克组件(lxt,含状态机)----
'Tank.Fsm': 'p10c_1', // 状态机字段(组件识别用)
'Tank.State': 'k10c_1', // 状态枚举对象
'Tank.StateValue': 'k10c_1.k3_1', // 状态数值:0=DEAD 1=DEAD_PHANTOM 2=SEMI_ACTIVE 3=ACTIVE 4=DISABLED 5=TELEPORT
'Tank.IsAlive': 's14j()', // 引擎可见性判定(2/3/5)
'Tank.TeamSource': 'l10c_1', // 状态机里下发的队伍对象
// ---- 队伍组件(GIt)与队伍对象 ----
'Team.Field': 'b135_1', // 队伍组件上保存队伍对象的字段
'Team.Id': 'k3_1', // 队伍编号:0 / 1 / 2(2 = 中立)
'Team.NeutralId': 2,
// ---- 坦克物理组件(ejt,组件名 tankPhysics)----
'Physics.Position': 'bzr_1', // 世界位置向量(f20_1/g20_1/h20_1)
'Physics.Size': 'xzq_1', // 车体半尺寸(**不是位置**)
'Physics.Hull': 'zzq_1', // 车体主碰撞形状(x9j_1 === 'hull-main')
'Physics.Body': 'pbk()', // 物理刚体(jM)
'Physics.AddMask': 'n155()', // z9j_1 |= t
'Physics.RemoveMask': 'm155()', // z9j_1 &= ~t
'Physics.BodyShapes': 'pbk().y9i_1', // 刚体上的形状列表(e1()/g1(i))
// ---- 碰撞形状(yi)----
'Shape.Name': 'x9j_1',
'Shape.Category': 'y9j_1', // bit0=坦克(1) bit3=地雷(8) bit4=地形(16)
'Shape.Mask': 'z9j_1', // 愿意碰撞的分类位
'Shape.Ghost': 'g9k_1', // true = 只探测不产生碰撞响应
// ---- 实体事件总线 / 外力事件(ODt)----
'Entity.Dispatch': 'kz7()', // 向实体内所有订阅者派发事件
'Event.PushPoint': 'o13i_1', // 外力事件:作用点(O$ 向量)
'Event.PushDir': 'p13i_1', // 外力事件:方向(O$ 向量)
'Event.PushStrength': 'q13i_1', // 外力事件:强度(坦克物理交给 m9k)
// ---- 血量组件(Vkt)----
'Health.Max': 'p12o_1',
'Health.Current': 'q12o_1',
// ---- 用户信息组件(yot)----
'User.Name': 'c12n_1',
'User.Clan': 'e12n_1',
// ---- 装备分组件(BattleGearScoreCC)----
'Gear.Score': 'cjf_1',
// ---- 皮肤组件(eUt)----
'Skin.HullSkin': 'g159_1', // 皮肤对象
'Skin.HullRoot': 'h159_1', // 车身渲染树根(BP 节点)
'Skin.Parts': 'g159_1.g17r_1', // 部件表
'Skin.PartNode': 'j17r_1', // 部件 → BP 节点
// ---- 炮塔皮肤组件(LUt)----
'Turret.Wrapper': 'a11p_1', // 组件首字段(包装对象)
'Turret.Root': 'e11p_1.a17q_1', // 炮塔 BP 根节点
'Turret.GetRoot': 'u11p()',
// ---- BP 场景节点(zl)----
'Node.Name': 'g35_1',
'Node.Matrix': 'p35_1', // 世界矩阵(列主序,[12][13][14] = 平移)
'Node.Children': 'r35_1',
'Node.Mesh': 'v3m_1',
'Node.Lod': 'u4x_1',
// ---- 相机(场景实体 .jz1_1.iyx_1)----
'Camera.SceneRef': 'jz1_1',
'Camera.Main': 'jz1_1.iyx_1',
'Camera.Position': 'jz1_1.iyx_1.t35_1',
'Camera.ViewMatrix': 'jz1_1.iyx_1.d37_1',
'Camera.ClipMatrix': 'jz1_1.iyx_1.g37_1',
'Camera.Fov': 'jz1_1.iyx_1.b37_1'
};
// ===== module_87072 的导出键($_.xx),每版都可能平移 =====
// 队伍:vd = WIt(t) 取实体队伍;dl = R7(a,b) 同队且非中立
// ⚠ 旧版是 kd / rk;新版 kd 已指向无关构造器
var Exports = {
'Export.TeamOf': ['vd', 'kd'],
'Export.TeamSame': ['dl', 'rk']
};
// ===== 路径解析 =====
function IsCall(Token) {
var N = Token.length;
return N > 2 && Token.charAt(N - 1) === ')' && Token.charAt(N - 2) === '(';
}
function ResolvePath(Target, Path) {
if (Target === null || Target === undefined || !Path) return undefined;
var Tokens = String(Path).split('.');
var Cur = Target;
for (var i = 0; i < Tokens.length; i++) {
if (Cur === null || Cur === undefined) return undefined;
var Tok = Tokens[i];
if (IsCall(Tok)) {
var Name = Tok.slice(0, -2);
if (typeof Cur[Name] !== 'function') return undefined;
try { Cur = Cur[Name](); } catch (e) { return undefined; }
} else {
Cur = Cur[Tok];
}
}
return Cur;
}
// 取字段值:Key 是总表键(如 'Tank.StateValue'),未命中则把 Key 当字面路径
function Get(Target, Key) {
var Path = Fields[Key];
return ResolvePath(Target, Path !== undefined ? Path : Key);
}
// 取字段路径(字符串),用于打印/自检
function PathOf(Key) {
return Fields[Key] !== undefined ? Fields[Key] : String(Key);
}
// 取单段属性名:'Node.Matrix' → 'p35_1';带方法或多段时返回 null
// 工具里写成 `Node[FieldName('Node.Matrix') || 'p35_1']`,即「优先用总表,缺表时用字面名」
function FieldName(Key) {
var P = Fields[Key];
if (!P || P.indexOf('.') >= 0 || IsCall(P)) return null;
return P;
}
// 取单段方法名:'Physics.RemoveMask' → 'm155'
function MethodName(Key) {
var P = Fields[Key];
if (!P || !IsCall(P) || P.indexOf('.') >= 0) return null;
return P.slice(0, -2);
}
// 自检:按前缀检查某对象上哪些语义字段能解析
function Check(Target, Prefix) {
var Out = { Ok: [], Missing: [] };
Object.keys(Fields).forEach(function (Key) {
if (Prefix && Key.indexOf(Prefix + '.') !== 0) return;
if (typeof Fields[Key] !== 'string') return; // 常量项(如 Team.NeutralId)不参与自检
var V = Get(Target, Key);
if (V === undefined) Out.Missing.push(Key + ' → ' + Fields[Key]);
else Out.Ok.push(Key + ' → ' + Fields[Key]);
});
return Out;
}
// 用当前战局对象做一次全量自检(控制台调用 __hornetSchema__.SelfCheck())
function SelfCheck() {
var Gm = window.__gameMode__;
var Report = { HasGameMode: !!Gm, Groups: {}, Missing: [] };
if (!Gm) return Report;
var Scene = Get(Gm, 'GameMode.Scene');
var TankList = Get(Gm, 'GameMode.TankList');
var Tank = null;
try {
Tank = Get(Gm, 'GameMode.OwnedTank');
if (!Tank && TankList && typeof TankList.e1 === 'function' && TankList.e1() > 0) Tank = TankList.g1(0);
} catch (e) {}
var Targets = {
Entity: Tank || Scene,
GameMode: Gm,
Tank: Tank,
Physics: Tank ? FindPhysics(Tank) : null,
Shape: null,
Skin: Tank,
Turret: Tank,
Node: null,
Camera: Scene
};
Object.keys(Targets).forEach(function (Prefix) {
if (!Targets[Prefix]) { Report.Groups[Prefix] = '无样本对象'; return; }
var R = Check(Targets[Prefix], Prefix);
Report.Groups[Prefix] = R;
R.Missing.forEach(function (M) { Report.Missing.push(Prefix + '.' + M); });
});
if (Report.Missing.length) {
console.warn('[字段抽象] 失配 ' + Report.Missing.length + ' 项:', Report.Missing);
} else {
console.log('[字段抽象] 全部语义字段解析正常');
}
return Report;
}
// 找坦克物理组件(与工具内实现同源,放这里方便自检)
function FindPhysics(Tank) {
if (!Tank) return null;
var Map = Get(Tank, 'Entity.Components');
if (!Map || typeof Map.forEach !== 'function') return null;
var Found = null;
Map.forEach(function (ValueList) {
if (Found) return;
var Comp = (ValueList && typeof ValueList.g1 === 'function') ? ValueList.g1(0) : ValueList;
if (!Comp) return;
var Hull = Get(Comp, 'Physics.Hull');
if (Hull && Get(Hull, 'Shape.Name') === 'hull-main') Found = Comp;
});
return Found;
}
// ===== 导出 =====
window.__hornetSchema__ = {
Version: 'main.7cfa4058',
Fields: Fields,
Exports: Exports,
Get: Get,
PathOf: PathOf,
FieldName: FieldName,
MethodName: MethodName,
Check: Check,
SelfCheck: SelfCheck,
ResolvePath: ResolvePath,
FindPhysics: FindPhysics
};
console.log('[3DTank] 字段抽象层已加载(版本 main.7cfa4058,语义字段 ' + Object.keys(Fields).length + ' 项)');
})();