Instantanés de données

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() en std::string et testez la vacuité avec .empty() ; la vue n’est valable que durant l’appel render en cours.
  • Dans les listes ordinaires, un index hors limites lève std::out_of_range (sémantique identique à l’IndexOutOfBounds côté Java).
  • Ne conservez pas les pointeurs d’instantané ni les enveloppes d’une frame à l’autre.
Couverture

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)

Terminologie : train (MTR 3) → vehicle (MTR 4)

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 :

AccesseurTypeDescription
id() / siding_id() / this_route_id() / departure_index()int64_tid 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 / Carnombre de voitures et données par voiture (voir ci-dessous)
speed_kmh() / speed_ms()doublevitesse du véhicule
rail_progress() / rail_progress(car)doubleprogression 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()doubleouverture des portes 0..1
notch_level() / notch_position()int32_t / doublecran 0..5 et position normalisée
reversed() / on_route() / door_opening()boolmarche arrière / sur ligne / portes en cours d’ouverture
currently_manual() / manual_allowed()boolétat de conduite manuelle
client_player_riding() / rendered()booljoueur client à bord / au moins une voiture visible
total_dwell_time_millis() / elapsed_dwell_time_millis()doubledurée totale d’arrêt / durée déjà passée à quai
game_time_millis() / in_game_time()int64_thorloge murale de l’hôte (décalage serveur corrigé) / tick en jeu 0..24000
stops() / this_route_stops() / next_route_stops()StopListtable des stations : toutes / ligne courante / ligne suivante (voir ci-dessous)
next_stop_index()int32_tindice 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 / boolnom de la ligne courante / couleur ARGB / état de ligne circulaire (0 aucune, 1 sens horaire, 2 sens antihoraire)
getTransportMode() / getTransportType()TransportMode / StrViewvoir Modes et types de transport
doorDirection() / getDirectionOfDoorOpening()DoorDirection / StrViewSens 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()StrViewnom 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)

AccesseurDescription
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

AccesseurDescription
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 :

AccesseurDescription
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 Addon train_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) { /* ... */ }
}
Source des données

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 :

AccesseurDescription
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

AccesseurDescription
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) :

AccesseurDescription
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)

AccesseurDescription
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 :

AccesseurDescription
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)

AccesseurDescription
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.

ChampDescription
station_id / nameId et nom de la gare
color / color_hexCouleur de la gare — entier ARGB et chaîne #RRGGBB
zone1 / zone2 / zone3Zones tarifaires 1–3
interchangesLignes de correspondance : route_id / color / name / transport_mode / type
connecting_groupsLignes des gares de correspondance regroupées par nom de gare (chaque groupe = le nom de la gare + ses lignes)
connected_station_namesNoms des gares physiquement connectées
exitsSorties : nom + destinations
platform_numbersLes 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é) :

AccesseurDescription
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 :

AccesseurDescription
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());
MembreDescription
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
StrViewvue 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 */ }
MembreDescription
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.