JS 腳本類型

車輛腳本

一個「車輛」在 MTR 4 裡是一節車廂模型條目,玩家在側線介面自由編組成列。腳本不綁定單節車廂也不綁定整列,而是綁定一個腳本條目:車輛條目以 scriptId 引用它,多個車輛可複用同一條腳本——混編、效能、NTE 相容三全其美。

三個回呼(ctx 是 VehicleScriptContext,vehicle 是 VehicleWrapper):

function create(ctx, state, vehicle) { /* 首次渲染前:建紋理、註冊回呼 */ }
function render(ctx, state, vehicle) { /* 每幀:讀狀態、提交繪製與聲音 */ }
function dispose(ctx, state, vehicle) { /* 退場:釋放紋理 */ }

VehicleScriptContext(ctx)

成員說明
getCarRenderManager(carIndex): RenderManager?取第 carIndex 節車廂的渲染管理器。基準 = 車廂中心、底面上方 1 m。非本條目掛的車傳回 null
getCarBogieRenderManager(carIndex, bogieIndex): RenderManager?轉向架渲染:基準相對第 bogieIndex 個轉向架(0 前 1 後;單轉向架車廂為 0)
getCarSoundManager(carIndex): SoundManager?取車廂聲源(基準 = 車廂中心)
playAnnSound(id, volume, pitch)播放報站廣播——只在玩家乘坐本列時可聽見,不定位
getScriptEntryId(): String腳本條目 id
getMyCars(): int[]掛在本條目名下的車廂索引陣列
setDebugInfo(key, value) / setDebugInfo(value)除錯覆蓋層顯示(所有類型 ctx 都有;GraphicsTexture 直接貼圖顯示)

已棄用 drawCarModel(...) / playCarSound(...) / setDataFetchMode(...) 仍可用,但新程式碼請用上面的 RenderManager / SoundManager / 宣告欄位。drawConnModel(車廂連接件)的兩個多載均會擲出 NotImplementedException(未實作例外);drawConnStretchTexture 在此上下文中未提供。兩者均不可用。

VehicleWrapper(vehicle)

成員說明
getStops() / getThisRouteStops() / getNextRouteStops()全部 / 本線路 / 下一線路的 Stop 清單
getNextStopIndex(stopList, overrunTolerance=0.5): int下一停站在清單中的索引;越界容差按公尺計;全部過完傳回 stopList.size()
isStopsDataFetched(): boolean停站資料是否已從伺服端取到(SKIP 模式恆 false)
isRendered() / isCarRendered(cars...)整列 / 指定車廂是否在渲染(未被剔除)
isClientPlayerRiding(): boolean本機玩家是否乘坐本列
getId(): long · getSiding(): Siding? · getTransportMode(): TransportMode唯一 id / 所屬側線 / 交通模式
getVehicleId(carIndex): String? · getLength(carIndex) · getWidth(carIndex) · getCarCount()資源包車型 id / 車廂長寬 / 車廂數
getServiceAcceleration() · getServiceDeceleration() · getMaxManualSpeed() · isManualAllowed()側線設定的加 / 減速度與手動駕駛上限
getSpeedMs() / getSpeedKmh() · getNotchLevel() / getNotchPosition()速度;檔位(-5..5 常規,±7 = 140%,-8 緊急制動)與正規化位置
getDoorValue(): double · isDoorOpenable(car, pos[, pos2]) · isDoorOpen(car, pos[, pos2])車門開度 0..1;按車廂內相對座標查門是否可開 / 已開
getPathData() · getRailProgress(carIndex=0) · getRailIndex(progress, roundDown) · getRailSpeed(pathIndex)路徑與進度(MTR 4 只同步部分路徑)
isCurrentlyManual() · isReversed() · isOnRoute() · getManualToAutomaticTime()手動駕駛 / 反向 / 已上線 / 無人駕駛回 ATO 的時限(毫秒)
getDepartureIndex(): long · getTotalDwellTime() / getElapsedDwellTime()發車序號(UTC 零點歸零,-1 = 未發 / 人工);停站總時長與已停時長(毫秒,不在月台為 -1;已停時長按伺服端同步原樣輸出)
getMtrVehicle(): VehicleExtensionMTR 底層載具物件——內部 API 界線以下,慎入
晏陽 getDirectionOfDoorOpening(): String開門方向記憶:"not" / "left" / "right" / "all",詳見 晏陽擴充
晏陽 getTransportType(): String規範交通類型 id(含 MAGIC 細分),詳見 晏陽擴充

已棄用:NTE 同名舊介面(getAllPlatforms / trainCars / speed / doorValue / mtrTrain 等)全部保留可用,逐一等價於上表新介面。

Stop(停站)

欄位 / 方法說明
route: SimplifiedRoute? · station: Station? · platform: Platform?所屬線路 / 車站 / 月台;資料未取到為 null(記得判空)
destinationName: String · customDestination: String?終點顯示名與自訂終點原文
routeInterchanges: List<RouteInterchange>本站轉乘線路(不含本線路、不含隱藏線路)
connectingInterchanges: Map<String, List<RouteInterchange>>按「相連車站名」分組的轉乘表(同名站合併)
dwellTimeMillis: long · distance: double停站時長(未知為 -1);停點軌道進度(SKIP 模式為 -1)
isRouteSwitchoverStop: boolean · roundUpRoute: Stop是否線路交接站;是則經 roundUpRoute 取「下一線路視角」的同一站副本
turnbackPlatform: boolean本站是否折返

RouteInterchange(轉乘線路)

color: int(RGB)· name: String;晏陽 擴充欄位 getTransportMode(): TransportMode?、getTransportType(): String(含 MAGIC 細分)、getRouteId(): long(來源線路 id,缺資料為 0)——見 晏陽擴充。

隱藏原版顯示牌

基於原版模型的車輛可用 hideDisplayParts(MTR 4)/ hide_display_cube(MTR 3)隱藏內建的目的地顯示方塊,改用腳本自繪。

車輛資料取得模式

MTR 4 只同步附近資料,而車輛腳本往往要整趟線路的車站名。JCM 的三級模式(dataFetchMode 欄位):

模式行為
SKIPMTR 4 預設。不向伺服端要資料,本機盡力拼一份停站表——不保證完整。夠用於「車一動就響鈴」這類只看車速的腳本
ALL先取整趟停站表,再按表裡的 id 逐個取車站 / 線路資料。取到之前表現同 SKIP;即便停站表到手,Stop.station / Stop.route 仍可能為 null——判空
MANDATORY同 ALL,但資料全部到手前腳本完全不啟動。MTR 3 格式預設值(老腳本預設資料即到即用,受不了 null)

裝飾物腳本

掛在 MTR 裝飾物(Decoration Object)模型條目上:資源包裡選中該模型的裝飾物方塊被渲染時腳本開始工作。註冊用 objects + objectScripts(MTR 3 舊格式寫在 eyecandy.json),內容條目上 isScriptControlled: true 可隱藏原模型把畫面全交給腳本。

三個回呼:ctx 是 EyeCandyScriptContext,eyecandy 是 EyecandyWrapper。

EyeCandyScriptContext(ctx)

成員說明
getRenderManager(): RenderManager基準 = 方塊位置 + GUI 位移,含 GUI 旋轉
getSoundManager(): SoundManager基準 = 方塊位置;另有便捷的 ctx.playSound(id, volume, pitch) 直接在方塊處播放
drawModel(model, matrices?)畫模型(也收 DynamicModelHolder)——等價於 getRenderManager().drawModel(...)
events(): EyecandyEvents取事件:右鍵 events().onBlockUse.occurred() → 處理 → events().handled() 確認;事件詳情 .detail().player() 給出操作者 PlayerEntity(手持刷子時右鍵不算)
setOutlineShape(shape) / setCollisionShape(shape)自訂輪廓 / 碰撞箱(VoxelShape),使用刷子時無效
setDebugInfo(...)同其他類型

EyecandyWrapper(eyecandy)

getModelId(): String(目前模型條目 id)· getTranslateX/Y/Z(): float(GUI 位移,公尺)· getRotateX/Y/Z(): float(GUI 旋轉,弧度)· pos(): Vector3f(方塊 + 位移)· blockPos(): Vector3f · facing(): Direction · facingAngle(): double(朝向角度)· isCrosshairTarget(): boolean(準星指著,可用來彈提示)· getFullBrightness(): boolean · redstoneLevel(): int(0–15)。

已棄用:欄位直讀形式(eyecandy.translateX、eyecandy.prefabId 等)仍可用。

範例:隨右鍵變暗的循環音樂

用 TickableSoundInstance.create(...) + setRepeatable(true) + play() 掛循環音樂,右鍵 5 秒內 setSoundVolume(0.25)、到時回 1.0,dispose 裡 stop——完整程式碼見上游 sounds 文件。

電梯腳本

掛在電梯轎廂樣式(材質)條目上,玩家用電梯重新整理器選中該樣式後生效。註冊用 lifts + liftScripts,樣式條目上 isScriptControlled: true 隱藏原模型。

三個回呼:ctx 是 LiftScriptContext,lift 是 LiftWrapper。

LiftScriptContext(ctx)

getRenderManager(): RenderManager(基準 = 電梯基礎位置 + 偏移,隨電梯旋轉)· getSoundManager(): SoundManager(同基準)· setDebugInfo(...)。

LiftWrapper(lift)與 Floor

成員說明
getMtrLift(): Lift · getId(): longMTR 底層物件與唯一 id
getWidth() / getHeight() / getDepth()轎廂尺寸(公尺,重新整理器設定)
getOffset(): Vector3f · getPos() / getRawPos()偏移;基礎位置 + 偏移 / 基礎位置
getAngleDegrees() / getAngleRadians() · isDoubleSided()朝向角與雙開門
getDoorValue(): float · getDirection(): int門開度 0..1;運行方向:-1 下行 / 0 靜止 / 1 上行
isDoorway1Openable() / isDoorway1Open() / isDoorway2...前 / 後門可開與已開
isClientPlayerRiding(): boolean · shouldRender(): boolean乘坐中;未被遮擋剔除
getFloors(): List<Floor>停靠樓層,每項 getIndex()(0 為最底)· getNumber() / getDescription()(層軌刷子設定)· getPos() · isCurrentFloor() / isTargetFloor()

PIDS 腳本

獨立體系

本系統與 PIDS Layout Editor / PIDS Modularization / PIDS JSON(EpicPuppy)及 MTR 4 早期測試版的資訊屏自訂互不相容,格式無法互導——入門門檻更高,但能做更複雜的版面配置。

在 joban_custom_resources.json 的 pids_images 陣列裡,預設物件帶上 scriptFiles 或 scriptTexts 即為腳本預設(scriptInput / scriptRateLimit 含義同他類;Yanyang 也接受 scriptLocations 混入 C++,見 混合條目):

{
    "pids_images": [
        {
            "id": "pids_demo",
            "name": "Demo Scripted PIDS",
            "scriptFiles": ["jsblock:scripts/pids_demo.js"],
            "scriptInput": { "theme": "dark" },
            "scriptRateLimit": 0.75
        }
    ]
}

預設欄位 outOfBound: boolean(上游 v2.3.0-prerelease.3):螢幕移出相機視界時仍繼續渲染——投影機預設(pids_projector)須臾不可停者所必需。

PIDS 上畫的是世界裡的多邊形,不是 2D 畫布——元素可以溢出螢幕邊界,界內裁切是腳本自己的責任(確需複雜裁切 / 影像處理時用 GraphicsTexture 畫好再貼)。繪製順序即層次:後畫的蓋先畫的;預設每次繪製沿 z 方向平移一小步(0.0002)防 z-fighting,可用 ctx.setAutoZOrdering(false) 關掉改手動 zOrder()(ctx.setZOrderStep(m) 調步長)。

PIDSScriptContext(ctx)與 PIDSBlockEntity(pids)

ctx.getRenderManager() / ctx.getSoundManager()(基準 = 方塊位置)· ctx.setAutoZOrdering(b) · ctx.setZOrderStep(m) · ctx.setDebugInfo(...)。

pids 成員說明
type: String螢幕類型:rv_pids / rv_pids_sil_1 / rv_pids_sil_2 / lcd_pids / pids_projector / pids_1a
width / height / rows可用螢幕寬高與到站行數
isRowHidden(i) / getCustomMessage(i)第 i 行是否被方塊設定隱藏;行自訂文案(未設為空串)
getTargetPlatformIds() · isPlatformAutoDetected() · isPlatformNumberHidden()偵測到的月台 id 清單;是否自動偵測;月台號是否隱藏
blockPos() · isKeyBlock() · station(): Station?方塊座標;是否雙面螢幕的「鍵」方塊;所在車站(不在站內或用戶端未知為 null)
arrivals(): ArrivalEntries到站資料,見下

ArrivalEntries 與 ArrivalEntry

ArrivalEntries:get(i): ArrivalEntry?(每月台最多取 10 條到站)· length(): int(到站總條數,上游 v2.3.0-prerelease.3)· mixedCarLength(): boolean · platforms(): List<Platform> · forEach(consumer) / forEach(platformId, consumer)(逐條遍歷,免手動判空)。

ArrivalEntry:destination(): String · arrivalTime() / departureTime()(Unix 毫秒,new Date(v) 轉 JS 日期)· arrived() / departed() · deviation(): long(相對圖定:正晚點 / 負早點)· realtime(): boolean(圖定或即時推算)· departureIndex(): long · terminating(): boolean(本站終到)· route(): SimplifiedRoute? / routeId() / routeName() / routeNumber() / routeColor() / circularState() · platform(): Platform? / platformId() / platformName() · carCount() / cars(): List<CarDetails>。

Text / Texture / Rect(繪製三件套)

三者都是「建立 → 鏈式配置 → draw(ctx)」的流式物件;Rect 等於貼圖硬編碼為純白 PNG 的 Texture:

通用說明
create() / create(comment)新建(註解僅便於閱讀程式碼)
pos(x, y) · size(w, h)位置與尺寸(螢幕像素單位)
color(rgb)染色(RGB 整數)
naturalLight() · renderType(str) · matrices(m) · zOrder(n) · draw(ctx)按世界光照取亮度(預設全亮);渲染類型(同 QuadDrawCall);附加變換;手動 z 序(關自動 z 後生效);提交繪製
Text 專屬說明
text(str) · scale(i) · fontMC() / font(id)內容;整體縮放(預設 1,配合 size 要自行換算);原生字型 / 資源字型(預設 mtr:mtr,MTR 設定「自訂字型」關閉時無效)
leftAlign() / centerAlign() / rightAlign()對齊(預設左)
shadowed() · italic() · bold() · lineHeight(factor)陰影 / 斜體 / 粗體;行距因子(換行用,預設 1 = 9 單位)
stretchXY() / scaleXY() / wrapText() / marquee() / marquee(durationTick) / withMarqueeProgress(p)溢出策略四選一:溢出軸拉伸 / 等比縮放 / 換行 / 跑馬燈(可指定週期或直接給進度 0..1)
measureWidth(): int按目前配置量寬——傳回 int,鏈式到此為止

Texture 專屬:texture(id)(PNG 或 mcmeta)、uv(u1, v1, u2, v2)(0–1)。

動態紋理貼上 PIDS

PIDS 也能貼 GraphicsTexture:create 裡 new GraphicsTexture(pids.width, pids.height),render 裡畫完 upload() 再 Texture.create().texture(state.tex.identifier).size(pids.width, pids.height).draw(ctx),dispose 裡 close()——對文字排版無能為力時的後備方案,代價是效能與記憶體。

不需要腳本?pids_images 裡只寫 background / color / fonts / showWeather / showClock / hidePlatform / hideRow / topPadding / textOverflowMode 等 JSON 欄位即為「JSON 預設」——簡單改臉夠用,且與腳本預設二選一不可混寫。

PSD 腳本(屏蔽門)

為屏蔽門玻璃與頂部面板提供腳本。在 mtr_custom_resources.json 新增 psdScripts 類别——形式同 vehicleScripts(scriptLocations 支援 JS 與 C++ 混排、逐條目語言判定),新增 type 欄位選擇綁定表面:

"psdScripts": [
    {
        "id": "psd_glass_pids",
        "type": "glass",
        "scriptLocations": [
            "mtr:my_pack/js/psd_glass_pids/main.js",
            "mtr:my_pack/native/psd_glass_pids"
        ],
        "dataFetchMode": "ALL"
    }
]

遊戲内用新物品「屏蔽門腳本編輯器」(與 MTR 的「刷」在同一創造分類)对任意屏蔽門玻璃(含端部玻璃、半高閘門 APG 玻璃;MTR、天津地铁等一切基於 MTR PSD 類的模組)或屏蔽門頂部面板右鍵,在彈出介面中選擇該類型的條目或任一腳本化 PIDS 預設(PIDS 腳本原樣執行,畫布自動適配 PSD 表面);「無腳本」解除綁定。綁定保存在伺服器并同步給同维度所有玩家。

  • 玻璃:加上不透明底板——月台一側黑色(真實 PIDS 深色螢幕觀感),軌道一側純白(車內乘客只見白色面板,看不到鏡像內容)。腳本畫布位於月台一側、夾於玻璃之內,自動裁剪至玻璃的透明視窗(依資源包模型與貼圖 alpha 推導;門框、拉筋等不透明部分照常顯示在前並遮擋內容);兩道門之間的整段連續玻璃共用同一畫布,全高兩格玻璃上下合併為一。畫布尺寸隨視窗自適應(每方塊 96 像素)。
  • 頂部面板:同一門區上方的整段 psd_top 共用一塊畫布,該段不再顯示原有箭頭 / 路線圖——內容完全由腳本控制;其餘段保持原樣。畫布每方塊寬 96 像素(左右各留 0.125/16 邊距)× 高 42 像素。

連續玻璃段或頂板段不超過 64 格時仍共用一個完整畫布,即使跨越座標網格邊界也不拆分。超過 64 格的長段依畫布向右方向的有號世界座標切為對齊的 64 格區間(首尾區間可較短);編輯器綁定、解除綁定及頂板原有內容的隱藏都只作用於同一子段。

繪製 API 與 PIDS 完全一致(Text / Texture / Rect + ctx.draw,畫布原点為可见面左上角)。資料 API(JS 的 psd 参数 / C++ 的 mtr::Psd,完整訪問器见 資料快照 · Psd):

JS說明
psd.type() / psd.width() / psd.height()綁定表面("glass" / "top")與畫布尺寸
psd.platform()站台:編號 / 名稱(number() / name())、車站、停站時間
psd.routes()经停線路全量信息;dataFetchMode: "ALL" 時每条線路含完整車站列表(轉乘 / 出口)
psd.arrivals()到站列車:终点站、線路、車厢数、倒计時(arrivalCountdown())、將開啟的門編號 doorNumbers()、即時 / 终到 / 環線狀態
psd.stop()完整車站資訊(ABI 13):名稱、顏色(ARGB + 十六進位)、票價區 1–3、停靠本站的全部線路、鄰站線路(按站名分組)、實體連通的站名、出口與去向、月台編號與總數——無月台 / 車站時回傳 null。C++ 側:has_station() + mtr::PsdStation 存取器(見 資料快照 · Psd)
psd.doors() / psd.doorValue() / psd.isDoorOpen()門單元列表(1 起編號、即時開門 / 上鎖)與整牆開門量

C++ 側為第五類基類 mtr::PsdScript<State>(MTR_REGISTER_PSD_SCRIPT),資料快照 JcmPsdSnapshot(ABI 12;車站條目自 ABI 13 起),见 native/examples/psd_glass.cpp。

DisplayHelper(內建多螢幕助手)

車載多螢幕的現成方案:include(Resources.id("mtrsteamloco:scripts/display_helper.js")) 後——

let slotCfg = {
    "version": 1,
    "texSize": [2048, 1024],
    "slots": [
        { "name": "lcd_north", "texArea": [0, 0, 2048, 274],
          "pos": [[[-0.59, 2.125, -1.75], [-0.755, 2.03, -1.75],
                   [-0.755, 2.03, -3.25], [-0.59, 2.125, -3.25]]],
          "offsets": [[0, 0, -5], [0, 0, 0], [0, 0, 5], [0, 0, 10]] }
    ]
};
let dhBase = new DisplayHelper(slotCfg);

function create(ctx, state, vehicle) {
    state.rl = new RateLimit(0.05);
    state.dh = dhBase.create();          // 實例化並綁定紋理
}
function render(ctx, state, vehicle) {
    if (state.rl.shouldUpdate()) {
        let g = state.dh.graphicsFor("lcd_north");  // 槽位畫布(自動平移)
        // ... AWT 繪製 ...
        state.dh.upload();
    }
    for (let car of ctx.getMyCars())
        ctx.getCarRenderManager(car).drawModel(state.dh.model, null);  // 每幀都要畫
}
function dispose(ctx, state, vehicle) {
    state.dh.close();
}
  • 所有槽位共享一張 texSize 大紋理;texArea 指定槽位用哪塊區域(x, y, w, h)。
  • pos 是三維陣列:每螢幕四角座標(左上 → 左下 → 右下 → 右上),單位公尺(Blockbench 的 1/16 單位要除 16),原點在列車中心、地板高度,+X 左 / +Y 上 / +Z 後。
  • offsets 複製同內容螢幕(門上小螢幕連排時省紋理);graphicsFor(name) 拿自動平移好的畫布,graphics() 拿整張。
  • 上傳放 RateLimit 裡、畫模型放外面——模型必須每幀提交。

接下來:晏陽擴充 API —— 混合 JS+C++ 條目、開門方向記憶、交通類型細分與直寫像素上傳。