Migration from NTE
Background#
The Nemo Transit Expansion is an addon mod for the Minecraft Transit Railway 3.x version, which adds high performance OBJ rendering as well as JS scripting for trains and decoration object. While the former feature is merged into MTR 4.x, the scripting feature remained a gap in MTR 4.
JCM Scripting is an attempt at re-implementing the NTE scripting functionality, inheriting the same script engine (Rhino) and design paradigm for it's API.
Backward compatibility policy with NTE#
Important!
This policy does not apply to any NTE derivative fork, such as ANTE (Aphrodite's Nemo's Transit Expansion).
For ANTE specifically, see Migrating from ANTE
Backward compatibility with NTE scripts are made on a best-effort basis, in the sense that we won't go out of our way to intentionally break existing scripts, and we will add stub/redirect methods to retain existing script compatibility. However if a major redesign has occured for reasons outside of our variable (e.g. Internal workings of MTR 4), we are not able to provide full backward compatibility for scripts.
Compatibility Overview#
Registration#
- Both
mtr_custom_resources.jsonandmtr_custom_resources_pending_migration.json, in both MTR 3 and MTR 4 format is supported. - Eyecandy scripts registered in the old NTE format are also supported.
API#
- Most if not all functions/methods provided by NTE should continue working as-is.
- Known Exception:
VehicleScriptContext.drawConnModel/VehicleScriptContext.drawConnStretchTexturehas not been re-added yet due to low demand.
- Known Exception:
DisplayHelperis implemented, most DisplayHelper scripts should work out of the box without any issue.DynamicModelHolderis implemented.- Partial misc. functions from ANTE has been added, though without backward compatibility. (New API)
- (Important!) Code accessing MTR 3's internal code may break. (e.g. Most common one being
route.lightRailRouteNumbernot being in MTR 4 anymore, causing instances of "undefined" in route displays.)
Give me some statistics...#
Take this with a grain of salt
- Test conducted in Apr 2026, before the first beta release of JCM v2.2.
- Only versions with NTE Scripting are picked for the test, even if an updated version for MTR 4 is available.
- Content enlisted in MTR Content DB is not representative of the work of the wider MTR community, who may choose to publish their work in other platform.
- To prevent non-faithful comparisons between packs, the tested packs will not be listed out.
- 23 NTE (non-ANTE) packs from MTR Content DB were tested against JCM Scripting, with the pack being unmodified.
- 8 (~34%) packs are able to work without any issue.
- 6 (~26%) packs are able to function, with minor issues. (Such as light rail route not showing)
- 5 (~22%) packs errored out during execution, requiring some level of migration by script developers.
- 2 (~8%) packs are heavily reliant on MTR 3's feature.
- 2 (~8%) packs are unloadable in MTR 4, thus scripting functionalities cannot be tested.
Migration Checklist#
Before continuing with the migration process, it is important to understand whether your script will have any chance of migrating to MTR 4 in the first place.
Listed below are several situations in which it may become a potential roadblocker to migrate your script to MTR 4.
If you think any of the below suits the situation for your script, you should expand it to see the instructions.
If you managed to get pass all questions, it likely means that your script have a high feasibility of being able to migrate to JCM.
My script makes use of external Java classes (Anything outside of AWT/MTR).
JCM introduced a Scripting Restriction feature for security reasons.
If the packages you use is not listed above, please check whether the newly introduced API like Networking, Files, DataReader and BackgroundWorker replace your need?
- If yes: You should migrate to use these new API. (-Continue to next situation-)
- If no:
-
- If you think your use case should be included in the API, please open an issue for it to be added in JCM.
-
- Otherwise if you think your use case is special enough, you may need to turn off Scripting Restrictions in JCM Settings, as well as telling your player to do the same.
My script makes use of ANTE-specific features.
Does your script make use of the eyecandy's custom config/GUI features in ANTE?
- If yes: Unfortunately these features aren't available in JCM at the moment.
- If no: Please check whether the features described in the ANTE section covers your use case?
-
- If yes: You will need to migrate your script to these methods/functions.
-
- If no: Unfortunately these features aren't available in JCM at the moment.
My script utilize advanced model processing techniques (MaterialProp / VertArrays etc.)
- Unfortunately these features aren't available in JCM at the moment.
RawModel/RawMesh/RawMeshBuilder/ModelClusterare currently already implemented, however there are no plan to expose other deeper classes like MaterialProperties / VertArrays. Please try working around them with the existing NTE model API.
My script make use of the BVE CSV/OpenBVE Animated models.
You need to migrate to the OBJ model format as CSV/Animated is not supported in MTR 4.
My script access the internal of MTR Mod (e.g. Use of MTRClientData / Station / Route etc.)
You will need to look in the MTR 4 codebase to adapt the changes to your script.
- For MTRClientData in MTR 4, see MinecraftClientData.
- For
Route.lightRailRouteNumber, they are no longer transferred as part of the route. Please see VehicleExtraData to obtain them.
Got through all of them? Great, below are some resources/tips to get started:
- Try your scripted resource pack in MTR 4 w/ JCM, enable Script Debug Overlay, and see which part it errors out, or if it works at all!
- Confused on the equivalent of
StationandRouteobject in MTR 4? See the Transport Simulation Core page for a type listing.- Note: Due to some irresistable force, Rhino is able to read protected fields for these types. Stuff such as
Station.namewill work, which incidentally make a lot of scripts compatible. Not implying it's a good practice to rely on it however...
- Note: Due to some irresistable force, Rhino is able to read protected fields for these types. Stuff such as
- Data fetching mechanism is changed (Affecting
MTRClientDataand other scripts relying on client data). Please see Data Obtaining / Fetching for more details. - Through conditional logic, you can support both MTR 3/NTE and MTR 4/JCM!
- The NTE F3+5 quick reload is now Ctrl+R
Migrating from ANTE#
Aphrodite's Nemo's Transit Expansion (ANTE) is a fork of the Nemo Transit Expansion, which brings several new features such as rail tilting and some new scripting abilities.
Note that the additional features within ANTE is not considered (at least for now) within the scope of JCM scripting, and therefore we may not make any guarentee about backward compatibility with ANTE, and some features were delibrately not implemented as they aren't deemed fit in JCM. That said some features from ANTE were also recognized and adapted to JCM, which should make migration work just a tad bit easier.
Scripting Engine#
ANTE currently uses GraalJS in place of the Rhino JavaScript Engine, which provides support for more modern JS syntax, at the cost of some incompatibilities introduced. For compatibility reasons JCM will currently stay on the Rhino engine. If you make heavy use of modern JS syntax, unfortunately you may have to try your luck.
Rhino 1.9 (The current version used by JCM) makes a more marginal leap to modern JS features, though still arguably outdated in the rapidly changing environment of the web. See the Rhino compatibility table for details.
Registry Access (CONFIG_INFO)#
ANTE exposes the corresponding registry entry (i.e. The JSON object within a Train/Eyecandy entry) to scripts via the CONFIG_INFO variable.
JCM on the other hand exposes only the (scriptInput for NTE/scripting.input for MTR 4) field to scripts via the SCRIPT_INPUT variable.
Why not just expose the entire registry object?
While it may seem counterintuitive that you have to repeatedly specify a value at times, it's done this way to ensure that all the dependencies (fields) the script needs are in one place, anyone reading the json can immediately tell apart which variables may alter the script execution and which doesn't. You are also not imposed by any upstream format change or restrictions in your field's name.
Migration Example#
When using the NTE Eyecandy format (Individualized json file), put the necessary variables within scriptInput.
When using the MTR 4 Custom Resources Format, the corresponding field is input within the script object.
{
"objects": [
{
"id": "nte_lcd",
"name": "NTE LCD Test",
"scriptId": "nte_lcd"
}
],
"objectScripts": [
{
"id": "nte_lcd",
"scriptLocations": ["mtrsteamloco:eyecandies/js/nte_lcd_test/main.js"],
+ "input": {
+ "name": "NTE LCD Test",
+ "parameters": {
+ "stDay": "310",
+ "ksDay": "514"
+ }
+ }
}
]
}
Then rename CONFIG_INFO to SCRIPT_INPUT within the script.
- print(CONFIG_INFO.name); // NTE LCD Test
- print(CONFIG_INFO.parameters.stDay) // 310
- print(CONFIG_INFO.parameters.ksDay) // 514
+ print(SCRIPT_INPUT.name); // NTE LCD Test
+ print(SCRIPT_INPUT.parameters.stDay) // 310
+ print(SCRIPT_INPUT.parameters.ksDay) // 514
Custom shapes for eyecandy#
In ANTE, you would invoke BlockEyeCandy.setShape(shape: String) and BlockEyeCandy.setCollisionShape(shape: String) to set the outline and collision shape of the eyecandy, and then invoke BlockEyeCandy.sendUpdateC2S to sync it to the server.
In JCM, the key differences are:
- JCM scripting is completely client-side, therefore custom collision shape is not supported as it would de-sync from the server.
- Shapes are now serialized with VoxelShape rather than string, which provides more intuitive API and better guard against errors.
BlockEyeCandy.setShapehas now moved toEyeCandyScriptContext.setOutlineShape- When a player is holding a brush, it would always reset to the default outline block shape to ensure operators/admin can't be 'locked out' due to a block shape that is infeasible to right click on.
Migration example#
Block Use Event#
In ANTE, the use function would be invoked with 4 parameters: ctx, state, blockEyecandy, player when a player right click on it with anything other than an MTR Brush.
In JCM, you would have to check the event by yourself within the render function.
Migration Example#
ComponentUtil#
ANTE allows creating Raw Minecraft Text Component with the use of ComponentUtil. JCM instead creates a wrapper for the Component via VanillaText.
Migration Example#
| example.js | |
|---|---|
WrappedEntity#
ANTE uses WrappedEntity to represent a Minecraft entity, which is obtainable via the use event and MinecraftClient.getPlayer().
JCM provides PlayerEntity as the wrapper to represent a Minecraft entity, which contains a couple more methods to obtain information. The client player can be obtained with MinecraftClient.localPlayer() instead of MinecraftClient.getPlayer()
Migration Example#
| example.js | |
|---|---|
TickableSound#
In ANTE, TickableSound is a sound instance where it's parameter (Such as volume/pitch) can be updated midway during playback.
The equivalent in JCM is TickableSoundInstance, which can be used together with SoundManager
UtilitiesClient#
MTR 3 contains a UtilitiesClient class that is exposed by ANTE, which is no longer the case in MTR 4. Known equivalent methods can be found as follows:
| ANTE Scripting | JCM/MTR 4 Scripting |
|---|---|
static UtilitiesClient.getRenderDistance(): int |
static MinecraftClient.renderDistance(): int |
static UtilitiesClient.hasResource(resourceLocation: Identifier): boolean |
static Resources.exist(id: Identifier): boolean |