晏阳扩展 API
本分支(晏阳技术组修改版)在上游 JCM 2.3.0-prerelease.3 的 JS API 之上增加的能力。上游已有的一切照旧可用;本页只写增量。
扩展一览
| 扩展 | 入口 | 一句话 |
|---|---|---|
| 混合 JS + C++ 条目 | 声明条目 | 一个条目里 JS 与 C++ 模块随意混搭,双引擎同时渲染 |
| 开门方向记忆 | VehicleWrapper.getDirectionOfDoorOpening() | 到站开哪侧门——站台期、正线预测、跨视距由服务端记忆 |
| 交通类型细分 | VehicleWrapper.getTransportType() 等 | 线路细分类型 id(与 MAGIC MTR 联动)与换乘扩展字段 |
| 直写像素上传 | GraphicsTexture.uploadRawABGR() | 字节行阵直写 + 只上传脏区,高频逐像素更新的省带宽通道 |
| 兼容别名 | 声明条目 | 接受 NTE 文档示例的 script_input 键名;nativeLibrary / nativeLibraries 与 .js 混用 |
混合 JS + C++ 条目
上游格式里一条脚本要么全 JS。晏阳把判定权交给文件特征:scriptLocations 的每个条目单独看——*.js(不区分大小写)走 JavaScript,其余按 C++ 模块解析。一条里两种随便混、数量不限:
"vehicleScripts": [
{
"id": "demo:sp1900",
"scriptLocations": [
"demo:js/sp1900_ui.js",
"demo:natives/sp1900_motor",
{"windows": "demo:natives/win-x64/libmotor.dll",
"linux": "demo:natives/linux-x64/libmotor.so",
"macos": "demo:natives/macos-arm64/libmotor.dylib"}
],
"prependExpressions": ["print('JS 侧照常执行')"],
"input": { "era": 2004 }
}
]
C++ 声明有三种形式:
| 形式 | 写法 | 解析 |
|---|---|---|
| 平台无关 stem | "demo:natives/sp1900_motor" | 每个平台各自展开:natives/linux-x64/libsp1900_motor.so、natives/windows-x64/sp1900_motor.dll、natives/macos-arm64/libsp1900_motor.dylib……(目录布局详见 C++ 快速上手) |
| 完整库路径 | "demo:natives/linux-x64/libfoo.so" | 按扩展名绑定对应系统,其他系统跳过该条 |
| 按平台映射 | {"windows": ..., "linux": ..., "macos": ...} | 键可精确到架构(macos-arm64、linux-x64……),精确键优先于通用键;值本身又是 stem 或完整路径 |
两个引擎各画各的:JS 部分(连同 prependExpressions 与 input)拼成一份脚本解析;C++ 部分全部挂到同一脚本 id 下由原生管理器加载。渲染时两侧各自捕获、各自回放,绘制指令互不串线;一侧缺失或加载失败只是该侧不画,不会拖垮另一侧。
language 字段(兜底开关)
| 值 | 效果 |
|---|---|
| 不写(推荐) | 按文件特征自动判定——绝大多数场景零配置 |
"cpp" | 纯原生条目(老语义):scriptLocations 里的 .js 会被忽略并留日志提示;配合 nativeLibrary / nativeLibraries 使用 |
"javascript" / "js" | 强制全部按 JS 解析——无扩展名 JS 文件名的逃生通道 |
字段名按格式:MTR 4 键是 language,旧格式是 scriptLang(驼峰)/ script_lang(下划线)。nativeLibrary / nativeLibraries(后者让一条 id 挂多库)任何时候都按 C++ 处理,也能与 .js 同条目共存。
prependExpressions / input / rateLimit / dataFetchMode 等 JS 字段含义不变;纯 JS 条目(全是 .js)行为与上游完全一致。C++ 侧读 input 的方式见 C++ 文档。
开门方向记忆
VehicleWrapper.getDirectionOfDoorOpening(): String 回答「这列车会开哪侧门」,取值 "not"(哪侧都不开)/ "left"(车头左侧)/ "right"(车头右侧)/ "all"(两侧):
- 停靠站台时:不论车门处于未开 / 半开 / 关闭中 / 已关,答案都与完全打开时一致——平台方块(或已解锁的 PSD / APG 门)在哪侧,哪侧就算「会开」。门值 > 0 时直接读本帧的逐厢判定。
- 正线运行时:预测下一站的开门侧——把列车按停点摆到自己的路径上,用 MTR 同款
canOpenDoors方块扫描。停点区块已加载时立即可用;超出视距时改由服务端作答:服务端全图扫描过一次并持久化(站台方块增删改会自动增量重扫),按线路缓存两侧答案应答请求——中途上车、重启游戏、首次造访都照常工作。 - 服务端未装本模组或未应答:回退为客户端自己的视距内扫描,此时远处站台的预测可能拿不到。
- 聚合规则:一侧只要有一节车厢的门旁有平台方块,整侧即记为可开(按侧聚合,不按单门)。左右相对车头(转向架 1 端):每节车厢在自己的坐标系里评估(-X = 车头左侧,MTR 模型惯例),行进方向与折返不改变读数。
- 纯查询:只服务显示逻辑(开门侧指示、车内屏箭头),不干预 MTR 实际开门。
function render(ctx, state, vehicle) {
let dir = vehicle.getDirectionOfDoorOpening(); // "not"/"left"/"right"/"all"
if (dir == "left" || dir == "all") drawLamp(ctx, state, "left", 0x00FF00);
if (dir == "right" || dir == "all") drawLamp(ctx, state, "right", 0x00FF00);
}
交通类型细分
MTR 的 TransportMode 只有四种基模式(train / boat / cable_car / airplane),不够显示用。MAGIC MTR 附加模组允许玩家给「列车」再细分显示类型(存于其 route_types.json,按线路 id 键控)。本分支把这些类型贯通到脚本:
VehicleWrapper.getTransportType(): String—— 当前线路的规范类型 id:MAGIC 指派了细分(train_normal/train_light_rail/train_high_speed/train_metro/train_bus/train_tram/train_sbahn)就用之,否则回落基模式 id(train/boat/cable_car/airplane),未知为unknown,永不为 null。Stop.routeInterchanges[i](RouteInterchange)新增:getTransportMode(): TransportMode?(换乘线路的交通模式,老数据可能为 null)、getTransportType(): String(同上规范 id)、getRouteId(): long(来源线路 id,服务端数据缺省为 0)——换乘指示牌按类型选图标 / 配色时不再只能读颜色。- MAGIC 未安装或该线路没有指派时一切照旧回落基模式——脚本无需探测附加模组在不在。类型查询结果按线路短时缓存(约 1 秒),MTR 里改类型很快就能看到。
其他调用位置
「规范类型 id」不止 vehicle.getTransportType() 一处在用——宿主内部与两侧脚本的全部调用位置:
| 调用位置 | 侧 | 说明 |
|---|---|---|
vehicle.getTransportType() | JS · 车辆脚本 | 本节主入口;车辆挂有 C++ 脚本时,宿主构建快照也会调它(每车每帧一次),把字符串写进快照的字符串池 |
stop.routeInterchanges[i].getTransportType()stop.connectingInterchanges[相连车站名][i].getTransportType() | JS · 车辆脚本 | 换乘条目逐项同款 id;类型由服务端预先解析、随换乘数据同步下发(服务端为准,客户端装没装 MAGIC 都能收到),旧版数据缺字段时才由客户端按 getRouteId() 现查补齐 |
mtr::Vehicle::getTransportType()(旧名 mtr::Train)mtr::Stop::interchange(i).getTransportType() | C++ 快照 · ABI 9 起 | 直读快照字符串池里预写的那份字符串,与 JS 完全一致;详见 数据快照 |
以上所有位置读的是同一份按线路解析的结果——一处看到的 id,其余各处必然一致。
直写像素上传
GraphicsTexture.uploadRawABGR(source: byte[], dstOffsetX: int, dstOffsetY: int, width: int, height: int, ppm: int[]): void
把一段 width × height × 4 字节、行优先、BGRA 顺序的像素行阵直写进纹理缓冲,并只把这块矩形上传显存:
- 定位:高频逐像素更新的省带宽通道。普通 AWT 路线的
upload()搬整张图;一块 4 Mpx 的屏幕每帧整传就是十几 MB 的搬运量,而逐点动画(进度条、跳字)通常只改一小条——本方法几百 KB 就够。 ppm是 4 × 256 项的预乘查找表(依次 B / G / R / A 四段各 256 项),调用方持有;字节按表展开写入,避免逐像素做乘法。- 写入后自动只上传该矩形,纹理其余部分保持原样——比
upload(x, y, w, h)省掉「先在 AWT 画布上改」的一步。 - JS 里可用(配合
Java.newArray组字节数组),但设计初衷是服务 C++ 桥的帧像素回传;纯 JS 作者用 AWT + 子区域上传即可。
JS 与 C++ 的词汇一致
同一数据在两种语言里叫同一个名字——混编条目两侧画同一个东西时不会精神分裂:
| JS | C++(SDK) | 引入 |
|---|---|---|
vehicle.getTransportType() | mtr::Vehicle::getTransportType() | ABI 9 |
Stop.routeInterchanges[i].getTransportType() 等 | mtr::Stop::interchange(i) 的类型字段 | ABI 9 |
vehicle.getDirectionOfDoorOpening() | mtr::Vehicle::doorDirection() / getDirectionOfDoorOpening() | ABI 11 |
纯 JS 读者记住一件事即可:本页的扩展在 C++ 侧同样可用(快照字段直读,零搬运成本),见 数据快照。