晏陽擴展 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++ 側同樣可用(快照欄位直讀,零搬運成本),見 資料快照。