晏陽擴展 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++ 原生腳本。