資料快照
每幀一次、由宿主搬運處理的唯讀資料。全部存取器都是普通記憶體讀取——零 JNI、零反射、零裝箱。
快照機制
每幀開始時,宿主把該實例的資料一次性搬運處理成一塊 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 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) | 本幀是否進入指定狀態 / 從指定狀態轉入 |
接下來:繪製與畫布 —— 把這些資料畫出來。