晏阳扩展 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 同条目共存。

与纯 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++ 的词汇一致

同一数据在两种语言里叫同一个名字——混编条目两侧画同一个东西时不会精神分裂:

JSC++(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++ 侧同样可用(快照字段直读,零搬运成本),见 数据快照。


回到:JS 脚本类型 · JS 公共 API;想要零 JVM 开销的同类数据?看 C++ 原生脚本。