晏陽擴充 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 順序的像素列資料直寫進紋理緩衝,並只把這塊矩形上傳 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++ 的詞彙一致
同一資料在兩種語言裡叫同一個名字——混編條目兩側畫同一個東西時不會精神分裂:
| 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++ 側同樣可用(快照欄位直讀,零搬運成本),見 資料快照。