晏陽擴充 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 順序的像素列資料直寫進紋理緩衝,並只把這塊矩形上傳 GPU:

  • 定位:高頻逐像素更新的省頻寬通道。普通 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++ 原生腳本。