Instantanés de données
Des données en lecture seule, marshallées par l’hôte une fois par frame. Tous les accesseurs sont de simples lectures mémoire — zéro JNI, zéro réflexion, zéro boxing.
Mécanique des instantanés
Au début de chaque frame, l’hôte marshalle en une seule fois les données de l’instance en un instantané POD (en-tête de longueur fixe + pools de tableaux de longueur variable + pool de chaînes UTF-8), qu’il transmet à mtrRender avec le pointeur du bloc d’état. Les enveloppes typées du SDK (mtr::Vehicle, mtr::Pids, mtr::Lift, mtr::EyeCandy) fournissent sur ce bloc mémoire des accesseurs homonymes de ceux du Wrapper JS :
- Les chaînes sont retournées sous forme de
mtr::StrView(data+len) ; convertissez avec.str()enstd::stringet testez la vacuité avec.empty(); la vue n’est valable que durant l’appelrenderen cours. - Dans les listes ordinaires, un index hors limites lève
std::out_of_range(sémantique identique à l’IndexOutOfBoundscôté Java). - Ne conservez pas les pointeurs d’instantané ni les enveloppes d’une frame à l’autre.
L’instantané couvre les champs lus à haute fréquence. Les données absentes de l’instantané (réseau, fichiers, interfaces profondes des gestionnaires de rendu, etc.) s’accèdent via mtr::Api — un canal basse fréquence, à ne pas utiliser sur le chemin chaud.
Vehicle (véhicule)
MTR 4 désigne uniformément les véhicules par vehicle (clé de déclaration vehicleScripts côté pack de ressources, VehicleWrapper côté Java) ; la classe d’enveloppe de données de ce SDK est donc nommée mtr::Vehicle. Les versions antérieures du SDK la nommaient mtr::Train, selon l’usage de MTR 3 ; ce nom est aujourd’hui conservé comme alias de compatibilité source — les deux écritures compilent et se comportent strictement de la même façon.
Remarque : le nom mtr::Vehicle est présent dans les sources du SDK du dépôt et sera livré avec le zip de la prochaine version ; si votre projet utilise le zip du SDK 2.3.0-1.0.2 (ou antérieur), référencez cette classe par mtr::Train (les deux graphies compilent avec le nouveau SDK).
Fourni par mtr::Vehicle, miroir des VehicleWrapper + NTETrainWrapper de JS :
| Accesseur | Type | Description |
|---|---|---|
id() / siding_id() / this_route_id() / departure_index() | int64_t | id du véhicule / de la voie de garage / de la ligne courante, indice de départ (les id 64 bits ne perdent jamais en précision) |
car_count() / car(i) | int32_t / Car | nombre de voitures et données par voiture (voir ci-dessous) |
speed_kmh() / speed_ms() | double | vitesse du véhicule |
rail_progress() / rail_progress(car) | double | progression sur rail ; la version paramétrée déduit la position de la i-ème voiture d’après les longueurs des voitures |
door_value() | double | ouverture des portes 0..1 |
notch_level() / notch_position() | int32_t / double | cran 0..5 et position normalisée |
reversed() / on_route() / door_opening() | bool | marche arrière / sur ligne / portes en cours d’ouverture |
currently_manual() / manual_allowed() | bool | état de conduite manuelle |
client_player_riding() / rendered() | bool | joueur client à bord / au moins une voiture visible |
total_dwell_time_millis() / elapsed_dwell_time_millis() | double | durée totale d’arrêt / durée déjà passée à quai |
game_time_millis() / in_game_time() | int64_t | horloge murale de l’hôte (décalage serveur corrigé) / tick en jeu 0..24000 |
stops() / this_route_stops() / next_route_stops() | StopList | table des stations : toutes / ligne courante / ligne suivante (voir ci-dessous) |
next_stop_index() | int32_t | indice de la prochaine station sur la ligne courante (précalculé par l’hôte, ne le recalculez pas) |
route_name() / route_color() / circular_state() / circular() | StrView / uint32_t / uint8_t / bool | nom de la ligne courante / couleur ARGB / état de ligne circulaire (0 aucune, 1 sens horaire, 2 sens antihoraire) |
getTransportMode() / getTransportType() | TransportMode / StrView | voir Modes et types de transport |
doorDirection() / getDirectionOfDoorOpening() | DoorDirection / StrView | Sens d’ouverture des portes : NOT / "not" (aucun côté), LEFT / "left" (gauche par rapport à la tête), RIGHT / "right" (droite), ALL / "all" (deux côtés). À quai, la valeur est identique quel que soit le stade des portes (pas encore ouvertes, partielles, en fermeture, fermées) ; sur la ligne, elle prédit le prochain arrêt — en positionnant le véhicule à son point d’arrêt et en y appliquant, voiture par voiture, la même analyse de blocs canOpenDoors que MTR. La prédiction privilégie la mémoire persistante côté serveur, par ligne (persistée avec la sauvegarde lorsque le serveur exécute ce mod — valable au-delà de la distance de rendu, actualisée incrémentiellement à chaque modification de bloc de quai) ; sans réponse du serveur, elle revient au balayage client limité à la distance de rendu. Une seule voiture ouvrable suffit à marquer tout son côté comme ouvrable ; gauche/droite se rapportent à la tête du véhicule (extrémité bogie-1) — le sens de marche comme la marche arrière ne les inversent jamais. Purement indicative — MTR reste seul maître des portes réellement ouvertes. Identique au JS vehicle.getDirectionOfDoorOpening() (ABI 11) |
siding_name() | StrView | nom affiché de la voie de garage (nom / description personnalisés par l’utilisateur) |
raw() | JcmVehicleSnapshot& | instantané brut (détails ABI dans ABI C) |
Car (voiture)
| Accesseur | Description |
|---|---|
length() / width() | longueur / largeur de la voiture |
left_door_open() / right_door_open() | portes gauche / droite entièrement ouvertes |
rendered() | la voiture est encore visible après culling pour cette frame |
vehicle_type() | id du type de voiture (par ex. mtr_custom_train_demo_cab_1) |
StopList
| Accesseur | Description |
|---|---|
size() / empty() / at(i) / operator[](i) | taille et accès aléatoire (hors limites : exception levée) |
next_stop_index(rail_progress, tolerance=0.5) | déduit la prochaine station d’après la progression sur rail ; pour la liste de la ligne courante, utilisez directement Vehicle::next_stop_index() (précalculé par l’hôte) |
Stop / correspondances / sorties
mtr::Stop est le miroir du VehicleWrapper.Stop de JS :
| Accesseur | Description |
|---|---|
name() | nom de la station |
destination_name() | nom affiché du terminus pour ce quai |
has_custom_destination() / custom_destination() | terminus personnalisé (lorsqu’il n’est pas défini, JS retourne null ; en C++, testez avec has_custom_destination()) |
distance() | progression sur rail (-1 si indisponible) |
dwell_time_millis() | durée d’arrêt à cette station |
route_id() / station_id() / platform_id() | id de ligne / station / quai (64 bits) |
is_route_switchover() | station de changement de ligne ou non |
route_circular_state() | état de ligne circulaire de la ligne de rattachement de la station (distingue les changements de sens après changement de ligne) |
interchange_count() / interchange(i) | informations de correspondance : chacune contient color (ARGB), name (nom de ligne), getTransportMode(), getTransportType() |
exit_count() / exit(i) | sorties de la station : chacune contient name (lettre de la sortie, par ex. "A") et destination(j) (liste de chaînes de points de repère) |
Modes et types de transport
Deux axes complémentaires, qui retournent exactement les mêmes valeurs que getTransportMode() / getTransportType() côté JS ; des scripts écrits dans des langues différentes peuvent donc partager la logique de mise en page :
- Mode (énumération
mtr::TransportMode) :UNKNOWN / TRAIN / BOAT / CABLE_CAR / AIRPLANE, couvrant les quatre catégories de véhicules de base. - Type (id sous forme de chaîne, constantes
mtr::transport_type) : le nom en minuscules du mode de base ("train","boat","cable_car","airplane"), augmenté des types de ligne étendus de MAGIC MTR Addontrain_normal / train_light_rail / train_high_speed / train_metro / train_bus / train_tram / train_sbahn; en cas d’échec d’analyse :"unknown"ou vue vide (données de serveurs anciens).
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) { /* ... */ }
}
Les types MAGIC sont analysés à partir de la configuration locale lorsque l’addon est installé côté client ; si le serveur le possède mais pas le client, ils parviennent par le jeu de données synchronisé ; les serveurs anciens n’envoyant pas de métadonnées de ligne, la valeur retournée est alors vide / UNKNOWN — les serveurs équipés de ce mod peuvent fournir les métadonnées complètes des stations distantes.
Pids (écran d’arrivées)
mtr::Pids est le miroir du PIDSWrapper de JS :
| Accesseur | Description |
|---|---|
width() / height() | largeur / hauteur de l’écran (1,0 unité = 16 px, système de coordonnées écran) |
rows() / is_row_hidden(i) | nombre de lignes de texte / la ligne i est masquée ou non (32 lignes au maximum) |
station_id() | id de la station où se trouve l’écran |
is_key_block() / is_platform_number_hidden() | bloc à touche ou non (contrôle le masquage des autres écrans) / numéro de quai masqué ou non |
get_custom_message(i) | message personnalisé de la ligne i ; vide si non défini (JS retourne null) |
arrivals() | Arrivals : la liste des arrivées (voir ci-dessous) |
game_time_millis() / in_game_time() | horloge (décalage serveur corrigé) |
Arrivals / Arrival
| Accesseur | Description |
|---|---|
arrivals().size() / .at(i) | nombre d’entrées d’arrivée et accès |
arrivals().next_for_platform(platform_id, out, cursor) | parcours dans l’ordre du quai (miroir du forEach(platformId, cb) de JS), curseur réutilisé, zéro allocation |
arrivals().mixed_car_length() | les entrées ont-elles des nombres de voitures incohérents (miroir de mixedCarLength()) |
destination() / route_name() / route_number() / platform_name() | terminus / nom de ligne / numéro de ligne / nom de quai |
arrival_time() / departure_time() / deviation() | horodatages d’arrivée / de départ et écart (millisecondes epoch, corrigés) |
arrived() / departed() | déjà arrivé / déjà parti (comparé à l’instant courant) |
realtime() / terminating() / circular_state() | données temps réel / service terminus ici / état de ligne circulaire |
route_id() / platform_id() / route_color() / car_count() / departure_index() | id de ligne / de quai, couleur ARGB de la ligne, nombre de voitures, indice de départ |
Psd (portes palières)
mtr::Psd reflète le PSDWrapper JS (une entrée psdScripts — voir Types de scripts JS · Scripts PSD) :
| Accesseur | Description |
|---|---|
type() / is_glass() / is_top() | Surface liée : Psd::GLASS (vitre) ou Psd::TOP (bandeau supérieur) |
width() / height() | Taille du canevas (pixels PIDS) : vitre — la fenêtre transparente de tout le tronçon (96 px par bloc, les colonnes pleine hauteur fusionnent verticalement) ; bandeau supérieur — ~96 px de large par bloc (marges déduites) × 42 de haut |
platform_id() / has_platform() / station_id() | Quai et station desservis (id à 0 sans quai) |
platform_number() / station_name() | Numéro de quai (ex. « 1 ») et nom de la station |
door_value() / is_door_open() | Ouverture actuelle du mur entier (0..1) et présence d’une porte ouverte |
doors() | PsdDoorList : les unités de porte (voir ci-dessous) |
arrivals() | PsdArrivalList : les trains à venir (voir ci-dessous) |
routes() | PsdRouteList : les lignes desservant le quai (voir ci-dessous ; listes de stations uniquement avec dataFetchMode: "ALL") |
stop() / has_station() | Renseignements complets sur la gare desservie (ABI 13) : accesseurs mtr::PsdStation ci-dessous ; has_station() vaut faux (et stop() est vide) quand le mur n’a ni quai ni gare |
game_time_millis() / in_game_time() | Horloges (décalage serveur corrigé) |
PsdDoor (unité de porte)
| Accesseur | Description |
|---|---|
number() | Numéro d’unité (à partir de 1, le long du mur) — un groupe de vantaux coulissants (paire LEFT+RIGHT, ou modèle impair à 3 blocs) au sein d’un même tronçon continu |
is_open() / is_locked() | État ouverture / verrouillage en direct (lu chaque frame sur les block entities des portes) |
x() / y() / z() | Position du premier bloc de porte de l’unité |
PsdArrival (train à venir)
Reprend tous les accesseurs d’Arrival de Pids (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 — voir Pids), et ajoute :
| Accesseur | Description |
|---|---|
door_count() / door_number(i) | Les unités de porte que ce train ouvrira : état réellement ouvert pendant l’arrêt (avec repli sur la prédiction pendant les phases d’animation) ; avant l’arrivée, une approximation — le train s’arrête centré sur le milieu du quai, sa longueur étant sommée depuis les ressources de chaque voiture |
PsdRoute / PsdStop (lignes et stations)
| Accesseur | Description |
|---|---|
id() / name() / route_number() / color() / circular_state() / getTransportType() | Id / nom / numéro / couleur ARGB / état circulaire de la ligne, et id de type de transport canonique |
stops() | PsdStopList : les arrêts dans l’ordre, avec la même disposition que le Stop véhicule (name / destination_name / custom_destination / dwell_time_millis / route_id / station_id / platform_id / route_circular_state / interchange(i) / exit(i) — voir Stop / correspondances / sorties) |
PsdStation (l’enregistrement de gare, ABI 13)
JcmPsdSnapshot.station_offset (décalage d’octet 124 ; -1 = pas de gare — le champ était du remplissage en ABI 12) pointe l’enregistrement JcmPsdStation de 96 octets — les mêmes données que l’objet JS psd.stop() :
PsdStation et ses accesseurs de station imbriqués renvoient des objets absents, des chaînes vides ou des valeurs par défaut si un index ou un enregistrement est invalide, sans exception d’index. Pour construire un instantané hors de l’hôte, passez la taille réelle du tampon à mtr::Psd(snapshot, byteLength). Le constructeur compatible à pointeur seul exige un en-tête entièrement lisible et le tampon qu’il déclare ; un pointeur brut ne révèle pas la taille de l’allocation.
| Champ | Description |
|---|---|
station_id / name | Id et nom de la gare |
color / color_hex | Couleur de la gare — entier ARGB et chaîne #RRGGBB |
zone1 / zone2 / zone3 | Zones tarifaires 1–3 |
interchanges | Lignes de correspondance : route_id / color / name / transport_mode / type |
connecting_groups | Lignes des gares de correspondance regroupées par nom de gare (chaque groupe = le nom de la gare + ses lignes) |
connected_station_names | Noms des gares physiquement connectées |
exits | Sorties : nom + destinations |
platform_numbers | Les numéros de quai de la gare (le côté JS ajoute le nombre) |
ABI 12 → 13 est purement additif (un champ de remplissage promu en place, aucune structure existante modifiée) : les modules ABI 6–12 continuent de se charger.
Lift (ascenseur)
mtr::Lift (combinaison instantané + enveloppe Java ; partie instantané) :
| Accesseur | Description |
|---|---|
id() | id de l’instance d’ascenseur |
should_render() / is_double_sided() / is_client_player_riding() | rendu / double face / joueur client à bord |
width() / height() / depth() | dimensions |
door_value() | ouverture des portes 0..1 |
angle_degrees() / angle_radians() | angle d’orientation |
direction() | sens de déplacement |
floor_count() / floor(i) | table des étages : chaque étage contient index, les indicateurs current_floor / target_floor, position[3], le numéro d’étage et une chaîne de description (obtenus via string(offset,len)) |
game_time_millis() / in_game_time() | horloge |
Les interfaces plus profondes — gestionnaire de rendu, gestionnaire de sons, visibilité, etc. — s’accèdent via le wrapper() de Api (l’ascenseur n’a pas de page de documentation JS dédiée ; l’enveloppe Java fait foi).
EyeCandy (décoration)
mtr::EyeCandy est le miroir de l’EyecandyBlockEntityWrapper :
| Accesseur | Description |
|---|---|
model_id() | id du modèle courant |
translate_x() / _y() / _z() / rotate_x() / _y() / _z() | translation / rotation configurées par l’utilisateur |
facing() | orientation (Direction.ordinal() de MC, 0..5) |
facing_angle() | angle d’orientation en degrés (Direction.asRotation() : S 0 / O 90 / N 180 / E 270) — identique au facingAngle() JS |
full_brightness() | rendu à pleine luminosité |
redstone_level() | signal de redstone 0..15 |
is_crosshair_target() | visé par le réticule ou non |
block_pos(i) | composante de la position du bloc (i = 0..2) |
consume_block_use() | consomme un événement onBlockUse accumulé durant la frame (au plus une fois par frame ; les clics multiples dans la même frame sont fusionnés — comportement identique à la file d’événements JS) |
game_time_millis() / in_game_time() | horloge |
Les détails de l’événement (joueur, objet en main, etc.) se lisent via 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");
}
Le contexte de dessin de l’eyecandy, EyeCandyContext, fournit draw_model(model, matrices), play_sound(sound, volume, pitch), set_outline_shape(boxes, count) et set_collision_shape(boxes, count) (6 float par boîte ; les boîtes de collision conservent la limite de hauteur de 1,5 bloc appliquée côté Java, tout dépassement est validé et journalisé par l’hôte). L’ascenseur réutilise le même type de contexte (LiftContext = EyeCandyContext).
Types utilitaires
<mtr/util.hpp> fournit des utilitaires de carrousel et de suivi d’état adaptés au chemin chaud (zéro allocation) :
// "A|B|C" alterne un segment toutes les 40 ticks ; le changement de vue se résume à un calcul d'offset
mtr::CycleTracker dest{"KOWLOON|TSUEN WAN"};
StrView current = dest.value(vehicle.in_game_time());
| Membre | Description |
|---|---|
CycleTracker(packed, interval_ticks=40) | construit un carrousel (miroir de TextUtil.cycleString) |
value(game_tick) / value_str(game_tick) | segment actif courant (vue / chaîne) |
interval() / part_count() | intervalle de rotation et nombre de segments |
StrView | vue de chaîne {data, len} : .str(), .empty(), comparable avec == |
Suivi de machine à états mtr::StateTracker<T> (miroir du StateTrackerJS amont ; l’horloge est passée explicitement — le game_time_millis() de l’instantané est l’équivalent naturel) : placez le champ dans State et effectuez un seul contrôle de transition par rendu :
struct State {
mtr::StateTracker<Phase> phase; // persiste d’une frame à l’autre
};
// dans render() :
if (s.phase.set_state(next, vehicle.game_time_millis())) {
// true — première frame dans cet état (nouvelle sémantique amont prerelease.2)
}
double staying = s.phase.state_now_duration(vehicle.game_time_millis());
if (s.phase.changed_from_to(PHASE_IDLE, PHASE_ARRIVING)) { /* animation d’arrivée */ }
| Membre | Description |
|---|---|
set_state(value, now_ms) | met à jour l’état ; renvoie true s’il s’agit de la première frame dans cet état (setState renvoie un booléen depuis l’amont v2.3.0-prerelease.2) |
state_now() / state_last() / state_now_first() | état actuel / précédent ; indicateur de première frame |
state_now_duration(now_ms) | durée écoulée dans l’état actuel (millisecondes) |
changed_to(state) / changed_from_to(old, new) | si cette frame entre dans l’état donné / passe d’un état donné à un autre |
Ensuite : Dessin et canevas — comment dessiner ces données.