資料快照

快照機制

每幀開始時,宿主把該實例的資料一次性搬運處理成一塊 POD 快照(定長標頭 + 變長陣列集區 + UTF-8 字串集區),連同狀態區塊指標一起傳給 mtrRender。SDK 的型別化包裝器(mtr::Vehicle、mtr::Pids、mtr::Lift、mtr::EyeCandy)在這塊記憶體上提供與 JS Wrapper 同名的存取器:

  • 字串以 mtr::StrView(data + len)傳回,用 .str() 轉 std::string、.empty() 判斷是否為空;視圖只在本次 render 呼叫內有效。
  • 一般清單的越界索引擲出 std::out_of_range(與 Java 端 IndexOutOfBounds 語意一致)。
  • 快照指標與包裝器不要跨幀保存。
涵蓋範圍

快照涵蓋高頻讀取的欄位。不在快照裡的資料(網路、檔案、渲染管理器的深層介面等)經 mtr::Api 存取——那是低頻通道,別在熱路徑用。

Vehicle(車輛)

術語:MTR 3 的 train → MTR 4 的 vehicle

MTR 4 統一以 vehicle 稱呼載具(資源包宣告鍵 vehicleScripts、Java 側 VehicleWrapper),本 SDK 的資料包裝類別因此命名為 mtr::Vehicle。更早版本的 SDK 曾按 MTR 3 慣例命名為 mtr::Train,該名稱現保留為原始碼相容別名——兩種寫法均可編譯,行為完全一致。

注意:mtr::Vehicle 命名已進入儲存庫 SDK 原始碼,將隨下一版本的 zip 發布;若你的專案使用 2.3.0-beta-2.yanyang.7 及更早的 SDK zip,請以 mtr::Train 參考該類別(新 SDK 中兩種寫法皆可編譯)。

由 mtr::Vehicle 提供,對應 JS 的 VehicleWrapper + NTETrainWrapper:

存取器型別說明
id() / siding_id() / this_route_id() / departure_index()int64_t車輛 / 側線 / 目前路線 id、發車序號(64 位 id 全程不損失精確度)
car_count() / car(i)int32_t / Car車廂數量與逐廂資料(見下)
speed_kmh() / speed_ms()double車速
rail_progress() / rail_progress(car)double軌道進度;帶參版本按車廂長度推算第 i 節的位置
door_value()double車門開度 0..1
notch_level() / notch_position()int32_t / double檔位 0..5 與正規化位置
reversed() / on_route() / door_opening()bool反向 / 在路線上 / 車門正在開
currently_manual() / manual_allowed()bool手動駕駛狀態
client_player_riding() / rendered()bool玩家乘坐 / 任一車廂可見
total_dwell_time_millis() / elapsed_dwell_time_millis()double停站總時長 / 已停留時長
game_time_millis() / in_game_time()int64_t宿主壁鐘時間(已做伺服器偏移校正)/ 遊戲內 tick 0..24000
stops() / this_route_stops() / next_route_stops()StopList全部 / 目前路線 / 下一條路線的站點表(見下)
next_stop_index()int32_t目前路線的下一站索引(宿主已預先算好,別自己重算)
route_name() / route_color() / circular_state() / circular()StrView / uint32_t / uint8_t / bool目前路線名 / 顏色 ARGB / 環狀線狀態(0 無、1 順時針、2 逆時針)
getTransportMode() / getTransportType()TransportMode / StrView見交通模式與類型
doorDirection() / getDirectionOfDoorOpening()DoorDirection / StrView開門方向:NOT / "not"(兩側皆不開)、LEFT / "left"(車頭左側開)、RIGHT / "right"(車頭右側開)、ALL / "all"(兩側開)。停靠月台時不論車門處於未開 / 半開 / 關閉中 / 已關的哪個階段,回報都與完全打開時相同;正線運行時預測下一站——按停車點擺位、逐車廂執行 MTR 同款 canOpenDoors 方塊掃描。預測優先採用伺服端逐線路持久記憶(伺服端安裝本模組時隨存檔持久化、跨視距有效,月台方塊變動自動增量更新);伺服端未安裝或未應答時,回退為用戶端視距內掃描。同側只要有一節車門可開,整側即記為可開;左右相對車頭(bogie-1 端),行進方向 / 倒行均不改變讀數。純查詢——MTR 的實際開門不受影響。與 JS 的 vehicle.getDirectionOfDoorOpening() 完全一致(ABI 11)
siding_name()StrView側線顯示名稱(使用者自訂名稱 / 描述)
raw()JcmVehicleSnapshot&原始快照(ABI 細節見 C ABI)

Car(車廂)

存取器說明
length() / width()車廂長度 / 寬度
left_door_open() / right_door_open()左右門是否完全打開
rendered()該廂本幀經剔除後是否仍可見
vehicle_type()車廂類型 id(如 mtr_custom_train_demo_cab_1)

StopList

存取器說明
size() / empty() / at(i) / operator[](i)容量與隨機存取(越界擲出例外)
next_stop_index(rail_progress, tolerance=0.5)按軌道進度推算下一站;目前路線清單請直接用 Vehicle::next_stop_index()(宿主已預先計算)

Stop / 轉乘 / 出口

mtr::Stop 對應 JS 的 VehicleWrapper.Stop:

存取器說明
name()站名
destination_name()該月台的終點站顯示名稱
has_custom_destination() / custom_destination()自訂終點站(未設定時 JS 傳回 null,C++ 用 has_custom_destination() 判斷是否為空)
distance()軌道進度(不可用時為 -1)
dwell_time_millis()該站停站時間
route_id() / station_id() / platform_id()路線 / 車站 / 月台 id(64 位)
is_route_switchover()是否路線切換站
route_circular_state()該站所屬路線的環狀線狀態(區分跨線後的方向變化)
interchange_count() / interchange(i)轉乘資訊:每項含 color(ARGB)、name(路線名)、getTransportMode()、getTransportType()
exit_count() / exit(i)車站出口:每項含 name(出口字母,如 "A")與 destination(j)(地標字串清單)

交通模式與類型

兩條互補的口徑,與 JS 側 getTransportMode() / getTransportType() 傳回完全相同的值,跨語言腳本可共用版面配置邏輯:

  • 模式(mtr::TransportMode 列舉):UNKNOWN / TRAIN / BOAT / CABLE_CAR / AIRPLANE,涵蓋四種基礎載具。
  • 類型(字串 id,mtr::transport_type 常數):基礎模式的小寫名稱("train"、"boat"、"cable_car"、"airplane"),加上 MAGIC MTR Addon 的擴充路線類型 train_normal / train_light_rail / train_high_speed / train_metro / train_bus / train_tram / train_sbahn;無法解析時為 "unknown" 或空視圖(舊伺服器資料)。
if (vehicle.getTransportType() == mtr::transport_type::TRAIN_METRO) { /* ... */ }

for (int i = 0; i < stop.interchange_count(); i++) {
    mtr::Stop::Interchange ic = stop.interchange(i);
    if (ic.getTransportType() == mtr::transport_type::TRAIN_HIGH_SPEED) { /* ... */ }
}
資料來源

MAGIC 類型在用戶端安裝了該 addon 時從本機設定解析;伺服器安裝了而用戶端沒裝時經同步資料集傳送;舊伺服器不傳送路線中繼資料時傳回空 / UNKNOWN——裝了本模組的伺服器可提供遠端車站的完整中繼資料。

Pids(到站顯示器)

mtr::Pids 對應 JS 的 PIDSWrapper:

存取器說明
width() / height()顯示器寬高(1.0 單位 = 16 px,螢幕座標系)
rows() / is_row_hidden(i)文字行數 / 第 i 行是否被隱藏(最多 32 行)
station_id()所在車站 id
is_key_block() / is_platform_number_hidden()是否按鍵方塊(控制隱藏其它螢幕)/ 是否隱藏月台號
get_custom_message(i)第 i 行自訂訊息;未設定時傳回空(JS 傳回 null)
arrivals()Arrivals:到站清單(見下)
game_time_millis() / in_game_time()時鐘(已做伺服器偏移校正)

Arrivals / Arrival

存取器說明
arrivals().size() / .at(i)到站條目數與存取
arrivals().next_for_platform(platform_id, out, cursor)按月台順序遍歷(對應 JS 的 forEach(platformId, cb)),游標可複用、零配置
arrivals().mixed_car_length()各條目車廂數是否不一致(對應 mixedCarLength())
destination() / route_name() / route_number() / platform_name()終點站 / 路線名 / 路線號 / 月台名
arrival_time() / departure_time() / deviation()到站 / 離站時刻與偏差(epoch 毫秒,已校正)
arrived() / departed()是否已到站 / 已離站(以目前時刻比較)
realtime() / terminating() / circular_state()即時資料 / 本班次終止 / 環狀線狀態
route_id() / platform_id() / route_color() / car_count() / departure_index()路線 / 月台 id、路線顏色 ARGB、車廂數、發車序號

Psd(屏蔽門)

mtr::Psd 鏡像 JS 的 PSDWrapper(psdScripts 條目,见 JS 腳本類型 · PSD 腳本):

訪問器說明
type() / is_glass() / is_top()綁定表面:Psd::GLASS(玻璃)或 Psd::TOP(頂部面板)
width() / height()畫布尺寸(PIDS 像素):玻璃為整段透明視窗的尺寸(每方塊 96 像素,整柱兩格上下合併為一);頂部面板每方塊寬約 96(扣除邊距)× 高 42
platform_id() / has_platform() / station_id()所服務的站台與車站(無站台時 id 為 0)
platform_number() / station_name()月台編號(如 "1")與車站名
door_value() / is_door_open()整牆当前開門量(0..1)與是否有門在开
doors()PsdDoorList:門單元列表(见下)
arrivals()PsdArrivalList:到站列車(见下)
routes()PsdRouteList:经停線路(见下;dataFetchMode: "ALL" 才含車站列表)
stop() / has_station()所服務車站的完整資訊(ABI 13):mtr::PsdStation 存取器見下;整牆無月台 / 車站時 has_station() 為假(stop() 為空)
game_time_millis() / in_game_time()時鐘(已做伺服器偏移校正)

PsdDoor(門單元)

訪問器說明
number()門單元編號(1 起,沿牆方向计)——一面連續牆内的滑動門組(2 塊 LEFT+RIGHT 或 3 塊奇数門為一單元)
is_open() / is_locked()即時開門 / 上鎖狀態(逐幀直讀門方塊實體)
x() / y() / z()單元首門方塊的坐标

PsdArrival(到站列車)

具備 Pids Arrival 的全部訪問器(destination / route_name / route_number / platform_name / arrival_time / departure_time / deviation / realtime / terminating / circular_state / route_id / platform_id / route_color / car_count / departure_index,见 Pids),另有:

訪問器說明
door_count() / door_number(i)該列車將開啟的門單元編號:停站中的列車读實際開門狀態(門動画間隙回退預測);未到站按「列車停于站台中点 + 按車輛资源逐卡累加車長」近似預測

PsdRoute / PsdStop(線路與車站)

訪問器說明
id() / name() / route_number() / color() / circular_state() / getTransportType()線路 id / 名稱 / 編號 / 顏色 ARGB / 環線狀態 / 規範交通類型 id
stops()PsdStopList:按序車站,布局同車輛 Stop(name / destination_name / custom_destination / dwell_time_millis / route_id / station_id / platform_id / route_circular_state / interchange(i) / exit(i),见 Stop / 轉乘 / 出口)

PsdStation(車站記錄,ABI 13)

JcmPsdSnapshot.station_offset(位元組偏移 124;-1 = 無車站——該欄位在 ABI 12 中是填補位)指向 96 位元組的 JcmPsdStation 記錄——與 JS psd.stop() 物件同一資料:

PsdStation 及其巢狀車站存取器在索引或記錄無效時傳回空物件、空字串或預設值,不擲出索引例外。若在宿主以外建立快照,請向 mtr::Psd(snapshot, byteLength) 傳入實際緩衝區位元組數;僅傳指標的相容建構子要求完整可讀的標頭及其宣告的緩衝區,無法由裸指標推斷配置大小。

欄位說明
station_id / name車站 id 與名稱
color / color_hex車站顏色——ARGB 整數與 #RRGGBB 字串
zone1 / zone2 / zone3票價區 1–3
interchanges轉乘線路:route_id / color / name / transport_mode / type
connecting_groups鄰站線路,按站名分組(每組 = 站名 + 該站線路)
connected_station_names實體連通的站名
exits出口:名稱 + 去向
platform_numbers本站月台編號(JS 側另加總數)

ABI 12 → 13 纯增量(填補位原地升格,既有結構零變化),ABI 6–12 模組照常載入。

Lift(電梯)

mtr::Lift(快照 + Java 包裝器結合;快照部分):

存取器說明
id()電梯實例 id
should_render() / is_double_sided() / is_client_player_riding()渲染 / 雙面 / 玩家乘坐
width() / height() / depth()尺寸
door_value()門開度 0..1
angle_degrees() / angle_radians()朝向角
direction()行進方向
floor_count() / floor(i)樓層表:每層含 index、current_floor / target_floor 旗標、position[3]、樓層號與描述字串(經 string(offset,len) 取得)
game_time_millis() / in_game_time()時鐘

渲染管理器、聲音管理器、可見性等更深的介面經 Api 的 wrapper() 存取(電梯沒有獨立的 JS 文件頁,以 Java 包裝器為準)。

EyeCandy(裝飾)

mtr::EyeCandy 對應 EyecandyBlockEntityWrapper:

存取器說明
model_id()目前模型 id
translate_x() / _y() / _z() / rotate_x() / _y() / _z()使用者設定的平移 / 旋轉
facing()朝向(MC Direction.ordinal(),0..5)
facing_angle()朝向角(Direction.asRotation() 度數:南 0 / 西 90 / 北 180 / 東 270)——與 JS facingAngle() 一致
full_brightness()全亮渲染
redstone_level()紅石訊號 0..15
is_crosshair_target()是否被準星指向
block_pos(i)方塊座標分量(i = 0..2)
consume_block_use()取用一次本幀累積的 onBlockUse 事件(每幀至多一次;同幀多次點擊被合併——與 JS 事件佇列行為一致)
game_time_millis() / in_game_time()時鐘

事件詳情(玩家、手持物品等)經 Api 讀取:

mtr::Api api(ctx.input());
auto event = api.context().call("events").get("onBlockUse");
if (event.call("occurred").as<bool>()) {
    auto detail = event.call("detail");
    auto player = detail.call("player");
    auto item   = detail.call("item");
}

裝飾的繪製上下文 EyeCandyContext 提供 draw_model(model, matrices)、play_sound(sound, volume, pitch)、set_outline_shape(boxes, count)、set_collision_shape(boxes, count)(每箱 6 個 float;碰撞箱沿用 Java 端 1.5 格高度上限,超出時由宿主驗證並記錄日誌)。電梯複用同一上下文型別(LiftContext = EyeCandyContext)。

工具型別

<mtr/util.hpp> 提供熱路徑友善的輪播與狀態追蹤工具(零分配):

// "A|B|C" 每 40 tick 輪替一段,視圖切換只做偏移計算
mtr::CycleTracker dest{"KOWLOON|TSUEN WAN"};
StrView current = dest.value(vehicle.in_game_time());
成員說明
CycleTracker(packed, interval_ticks=40)建構輪播器(對應 TextUtil.cycleString)
value(game_tick) / value_str(game_tick)目前活躍段(視圖 / 字串)
interval() / part_count()輪替間隔與段數
StrView{data, len} 字串視圖:.str()、.empty(),可用 == 比較

狀態機追蹤 mtr::StateTracker<T>(對應上游 StateTrackerJS;時鐘顯式傳入——快照的 game_time_millis() 即為天然等價物):把狀態欄位放進 State,在 render 裡只做一次轉移判定:

struct State {
    mtr::StateTracker<Phase> phase;   // 跨幀持久
};

// render 裡:
if (s.phase.set_state(next, vehicle.game_time_millis())) {
    // 傳回 true —— 進入該狀態的首幀(上游 prerelease.2 新語意)
}
double staying = s.phase.state_now_duration(vehicle.game_time_millis());
if (s.phase.changed_from_to(PHASE_IDLE, PHASE_ARRIVING)) { /* 播放進站動畫 */ }
成員說明
set_state(value, now_ms)更新狀態;傳回是否為進入該狀態的首幀(上游 v2.3.0-prerelease.2 起 setState 傳回布林值)
state_now() / state_last() / state_now_first()目前 / 上一狀態;是否首幀
state_now_duration(now_ms)目前狀態已持續時間(毫秒)
changed_to(state) / changed_from_to(old, new)本幀是否進入指定狀態 / 從指定狀態轉入

接下來:繪製與畫布 —— 把這些資料畫出來。