A Create addon for Minecraft 1.21.1 (NeoForge) that lets trains run to a real timetable.
It adds a clock that runs at real-world speed, and a schedule wait condition, Real Time of Day, that follows it. A train that is early waits for its departure time. A train that is late stops for a minimum time only, so it makes up the delay by stopping less.
Status: early (0.1.0-SNAPSHOT). The clock, the sky, the wait condition and running a timetable work and are tested; see what is and is not checked. Command names may still change. What is planned next (a time-distance diagram, a timetable editor, overtaking waits) is in docs/proposal.md, which is written in Japanese.
日本語: README.ja.md
- Minecraft 1.21.1 with NeoForge 21.1 (built against 21.1.262)
- Create 6.0.10 or later
- This mod on the server and on every client. A schedule that holds the new condition cannot be read without it.
The clock is the game time plus an offset that is saved in the world. Twenty ticks are one second and a day is 24 real hours (1,728,000 ticks), 72 times a vanilla day.
- It counts ticks, not wall-clock time. So it stays in step with the trains, stands still while the server is paused or stopped, and goes faster or slower with
/tick rate. If the server's tick rate falls below 20, the clock falls behind real time with it. - A world that has no saved clock yet starts at
start_time(06:00 by default). - On a server that runs around the clock, the clock stays close to real time. Every restart sets it back by the time the server was down;
/rtclock align, or thealign_on_startsetting, sets it to the server's own time of day again.
| Command | Who | What it does |
|---|---|---|
/rtclock get |
everyone | Shows the day and the time of the clock |
/rtclock set <H:MM[:SS]> |
operators | Sets the time of day and keeps the day. Trains that are waiting follow it: a later time lets them go, an earlier one makes them wait longer |
/rtclock align |
operators | Sets the time of day to the server's own |
/rtnetwork export |
operators | Writes the stations of the track network and the running time between them (for a train without fuel at full throttle) to createrealtimeschedule/network.json in the world folder, with a map of the track seen from above and its signals and signal blocks, for the timetable editor |
/rtnetwork tags |
operators | Lists the track tags, how many blocks on the track have each, and what is wrong with their stretches (see "Track tags") |
By default the sky follows the clock: noon at 12:00, midnight at 00:00, and the moon changes phase once a real day. To do that the mod keeps the game rule doDaylightCycle off and sets the day time from the clock every tick. So:
- Sleeping does not skip the night, and
/time setis put back on the next tick. - Do not use it with mods that change the length of the day, unless you turn
sync_skyoff. - Setting
sync_skytofalsehands the sky back to vanilla and putsdoDaylightCycleback as it was. - If you remove the mod while
sync_skyis on,doDaylightCyclestays off. Turnsync_skyoff and start the world once before removing the mod, or run/gamerule doDaylightCycle trueafterwards.
In the schedule window, give a stop a condition and pick Real Time of Day as its type. Its one line of settings has:
- the time the train is meant to leave (scroll over the hours and over the minutes), such as 08:10;
- the minimum stop, from 0 seconds to 10 minutes. A new condition starts with
default_minimum_stop_seconds, 20 seconds by default; - how often the time repeats: daily, or every 12, 6, 4, 3, 2 or 1 hours, or every 45, 30 or 15 minutes. With 08:10 repeating every 15 minutes, the train leaves at :10, :25, :40 and :55 of every hour.
When a train starts to wait at the stop, it works out once when it leaves: at its slot (the departure time that belongs to it), or when the minimum stop is over if it arrived late, whichever is later.
For a slot of 08:10, a minimum stop of 30 seconds and a scheduled arrival of 08:08:
| Arrives | Leaves | Stands for | Delay left |
|---|---|---|---|
| 08:08:00 (on time) | 08:10:00 | 2 min | none |
| 08:09:00 (1 min late) | 08:10:00 | 1 min | none: made up |
| 08:11:00 (3 min late) | 08:11:30 | 30 s | 1 min 30 s |
The slot is not simply the next 08:10 on the clock. It follows the slot the train used at its previous Real Time of Day stop (the first one after it that matches this stop's time). So a train that is late stays late against its own timetable instead of skipping ahead, and a round that asks for the same time twice means tomorrow's the second time. A train takes the slot nearest to its arrival instead when it has no previous slot (the first such stop of a schedule, or right after it got a different schedule) or when the chained slot is a whole repeat interval or more away from its arrival.
The departure time is kept in the train as a clock value, so it survives saving the world and follows /rtclock set. While a train waits, its status text, which Create's Display Link can show, reads "Departs at HH:MM:SS".
The mod can also run trains to a whole day's timetable by itself, without Create's schedules. The timetable is a JSON file of lines, trains and duties, in the format of docs/proposal.md (version 9; a timetable is written in the oldest version that holds it, so one with nothing of versions 3 to 9 is still version 2). A duty is what one train does in a day: it leaves its depot, runs a string of trains one after another, and comes back. Deadhead trains ("deadhead": true) take it from the depot to where its first train starts, and back, and are timed like any other train. The timetable editor, a web page, makes such files by drawing the trains on a diagram (see below).
| Command | Who | What it does |
|---|---|---|
/rttimetable load |
operators | Reads createrealtimeschedule/timetable.json in the world folder and runs it. If anything is wrong with it, it says what and keeps running the timetable it had |
/rttimetable status |
operators | Shows, for each duty, which train runs it, where it is and how late, or when it next leaves; and what went wrong lately |
/rttimetable unload |
operators | Stops running the timetable |
/rteditor copy timetable |
operators | Puts the timetable being run on your clipboard, for the editor (a command of the client, like the two below: only they touch your clipboard, and only when you type them) |
/rteditor paste |
operators | Loads the timetable on your clipboard, as load does the file |
/rteditor copy network |
operators | Puts the track network (stations, running times and the map of the track) on your clipboard, for the editor; /rtnetwork export writes it to createrealtimeschedule/network.json |
/rteditor |
anyone | Opens the timetable editor in your browser (a command of the client) |
/rtdiagram |
anyone | Opens the diagram of the timetable's first line; /rtdiagram line <id> opens that of another |
/rtdiagram refresh |
operators | Works the distances between the stations out from the track again at once; the server also does so by itself 5 seconds after the track has stopped changing |
duty_lead_seconds(60 by default) before a duty first leaves, it is given the train that has waited longest at its depot station. A train counts as waiting there if it stands at a station of the depot's name with no schedule of Create's and no duty.- At each departure time the train is sent to the next station the way Create's destination instruction sends one, and Create drives it. A train that arrives early waits for its time; one that arrives late stops for the minimum stop and goes, by the same rule as the wait condition. The minimum stop is the stop's own if the timetable gives it one, or else its station's, or else
default_minimum_stop_seconds. - The timetable runs every day. A duty whose train is removed, derails or is given a schedule of Create's is given up, and the train is left alone.
- A train later than
recovery_threshold_seconds(10 minutes by default) is put back on time by cancelling trains. When it comes into a station its duty leaves from again later, and what it makes up on the way (standing no longer than its minimum stop, running as fast as the track lets it) would not bring it within the threshold by then, it is held there until the first later call there that it can leave within the threshold, and the trains in between do not run. It is not held past a train the timetable marks"keep": true, nor while a train of the timetable runs through the station or the station has no platform to spare for the trains that come to stand there, nor until a call it could not get away from in time, as when it would have to run backwards and cannot, nor until a call after which only deadheads run./rttimetable statusnotes each hold and the trains it cancels. - A duty that has no free train by the time it is to leave is given one late, once one is free, as long as it can still get back on time so, and is held as above when it is too late. Once it cannot, it does not run that day. A duty that was to start before the timetable was loaded starts late in the same way.
- A stop can wait for another train before it leaves (
"wait_for"in the timetable): until that train has left a station, or run through it without stopping. Make the timetable so that, on time, no train has to wait: a wait keeps a train past its time only when the train it waits for is late. After a train that calls at the station, the waiting train keeps the timetable's gap between that train's time to leave and its own, and so leaves as late as that train did; after one that runs through, it leaves at once, and Create's signals keep the two apart. A local waits so on a siding for an express to pass (name the station of the main line), or for one that calls at the same station to leave first. A train that does not run that day is not waited for, and nor is one cancelled to get its set back on time. A wait for a train in no duty, or one that the track does not take through the station before then (on a loop line, round the other way), waits for nothing: loading the timetable says so, and so does/rttimetable status. A train is seen to run through a station when the back of its last carriage goes past a station of that name; one that comes in at the stop after its next without being seen to has no such station on its way, and the wait ends there (at its next stop, the back of a long train may still be short of the station). On a loop line, which way round the train goes is the track's. A train waits at the mostmax_wait_seconds(5 minutes by default) past its own time, or what the wait says, and leaves at once when the train it waits for is forecast to be later than that;/rttimetable statussays what it waits for. - What a late train makes up is forecast with what the signals hold its trains up by when they run on time, but not with what other trains and the signals hold up a late train by, nor with trains that do not run by the timetable; a hold is decided again each time a train arrives.
- Trains with a schedule of Create's are never touched, so they can share the track with the timetable's trains.
- Create's display boards and Display Links do not show the train number or destination of a train running a duty: it has no schedule of Create's for them to read.
The diagram shows one line of the timetable: time across, the line's stations down, as far apart as the track between them. The timetable's trains are thin lines in the colour of their type (the timetable's colour for it, or else one picked from its name), changing colour at a stop that changes a train's type, with deadheads dashed, and faint where they are cancelled to get a late train back on time; the duties of the service day are drawn over them in thick red as they really ran (purple for one given up), a train held at a station standing there until it leaves, and a train's line bent where it ran through a station that another train waits for it to run through, at the time it did. A stop that waits for another train has a dashed arrow, from where the train waited for leaves the station, or runs through it, to where the waiting train leaves (back in time, where the timetable has the train waited for pass after the waiting train's time); one with a train cancelled on the day or in no duty, where there is no wait, is faint. A key can open it too: bind "Open the diagram" in the controls. The wheel zooms, dragging moves, the left and right arrows change the line, Page Up and Page Down go to the service day before and back, and Home goes back to now. The server keeps the times of finished duties for today and the day before.
The editor is a web page. /rteditor copies it out of the mod into createrealtimeschedule/editor in the game folder and opens it, so it is always the one of your version of the mod; the same page is also at https://eyunya11.github.io/CreateRealTimeSchedule/ for anyone without the mod. To make a timetable:
- Run
/rteditor copy networkin the game and paste it into "Load network": the editor then knows the stations and the running time between them. - Add a line, picking its stations top down (tick "Loop line" for one that goes round).
- Press "Draw a train" and click a station's row for each stop. The line follows the running time of the track, and a click further right arrives later; each stop stands for its station's dwell. Once the train has arrived, move the pointer to the right and the point it leaves at follows; click the same row again to leave there, or click another row to stand for the dwell and go on. "Station stop times" gives each station a dwell, and a minimum stop for late trains; a station without them takes the dwell of the settings (20 seconds by default) and the server's minimum stop. While you draw, the drawing panel gives the train a dwell of its own for the stops you draw next, and its table changes the dwell of a stop drawn already, moving the stops after it. The table of a train changes the dwell and the minimum stop of each of its stops. Double-click or press Enter to finish. Drag the point a stop is arrived at to move the arrival alone, so that it stands longer or shorter, and the point it is left at to make it stand longer or shorter; drag the line between two stations to move that way alone, and the line where the train stands to move the whole train in time (with Ctrl, the line between stations moves it too); a time dragged lands on the time step (15 seconds by default, or 5 while Shift is held; both can be set), and while you drag, the times dragged show beside the pointer and a red dashed line shows the way run in the track's running time, and put times right in the table on the right. However a time is changed, two stops are never closer than the track's running time: a drag stops there, and a time typed in is held to the nearest one the track allows. A train already faster than the track is listed under the problems as a note.
- Add a duty, click its trains in order on the diagram (or pick from the trains that can come next), and press "Add deadheads to and from the depot".
- When the problems list is empty, "Export" and "Copy to clipboard", then run
/rteditor pastein the game. A timetable being run can be brought back into the editor with/rteditor copy timetable.
A speed limit sign of Create Train Next holds a train until it passes the next sign, stops and all, so the editor works out the running time of each way with the limit a train carries from its first stop. Each train is taken to start under no limit: where trains turn back, put the signs up facing both ways.
With track that has signals and signal blocks (/rteditor copy network or /rtnetwork export of this version), the editor also runs the trains of the timetable on time together with the signals between them: each duty as one set, each train in no duty on its own, at the train's length in the timetable or in the settings. A train held back by the aspects of Create Train Next's colour light signals behind another, or waiting at a signal at red, takes longer, and dragging, typed times and the notes go by that. The notes also say where a train stops at a signal at red and for how long, and where one falls more than ten minutes behind and does not get there, held up at the signals or given too little time. It is worked out again once the timetable has stopped changing for half a second, and not while you drag; until then the notes and the limits leave the signals out. While you drag, it is that of the timetable as it was when you started. A timetable that holds up too many trains to work it out says so in the notes, and the track's running times alone are taken. The server's minimum stop is taken to be the default, 20 seconds.
A train's panel gives it a length in blocks ("Length"); one without takes the train length of the settings (40 blocks by default). It also marks it never to be cancelled to get a late train back on time ("Never cancel"), as for the last train of the day, and each of its stops' rows sets the type the train has from there on ("Type from here", such as a local that runs on as a rapid) and the trains it waits for there, with the station each leaves or runs through and the longest wait; the diagram draws a wait as a dashed arrow. "Types" gives each type its colour, picked from the samples or the palette or written as #rrggbb, and their order; a type renamed there is renamed on its trains too. A type without a colour of its own is drawn in one picked from its name, the same in the game and in the editor.
"Map", above the diagram, shows the track as it lies in the world, seen from above (north up), with its curves, the stations, the signals with the lamps they showed and Create Train Next's speed limits, and the trains, each of which can be shown or not. The signals and the trains are as they were when the network was copied. Choosing lines shows the track their trains run along, on the ways of the track tags they are held to too, the rest faint grey. Stretches with track tags are drawn in the tags' colours, in turn for more than one. "Tags" turns each tag, and "No tag", on or off: the stretches of a tag turned off are greyed out, and so are its trains; with "No tag" off, track with no tag is shown only where it joins stretches shown, not out to a siding or to a tag turned off. A track clicked on can be added to the chosen line's map though its trains do not run along it (a depot, say), taken away from it, or hidden from every map (track still being built); the timetable keeps this (version 8). A track is named by where its two ends are, so laying track on from one end renames it, and the editor lists a name the map no longer has as a warning.
The editor checks a timetable by the same rules as the mod and words its problems the same way, undoes and redoes, keeps the draft in the browser, and speaks Japanese and English.
The Map Screen (in Create's tab; a railway casing, a glass pane and paper make two) shows the same map in the world. Place it against a wall: blocks facing the same way side by side make one screen, up to 32 by 32 blocks (map_screen_max_size), and a block put beside a screen takes on what it shows. It shows the dimension it is in.
Use it to set:
- the X and Z in the middle of it;
- how many blocks of the world a block of it shows. "Fit all track" puts on it all the track of the dimension shown by the lines and tags ticked, leaving out the track drawn grey, or all of it if they show none;
- the lines whose track it shows (none for all the track). This needs a timetable being run, and follows its added, taken away and hidden tracks;
- which of the stations, their names, the signals, the signals' heads, the speed limits and the trains it shows;
- the track tags it shows.
It shows the track tags as the editor's map does: stretches in their tags' colours, track not shown faint grey, and trains only on tags turned on.
A station's mark points the way trains come in to stop there. A signal has a small triangle on the track pointing the way it holds the trains, in the colour of its first lamp, and its head (room for two lamps, those not lit grey) on a post on the side its block is on. Turn "Signal heads" off to leave only the triangle.
A speed-limit sign has an open arrow on the track pointing the way it holds the trains (red, or black for a release) and stands on a post on its block's side. A release sign looks as Create Train Next's block does.
The marks and names are kept off the track and off one another where there is room (a name that has none is left out), and grow on a larger screen.
The signals show what they show now, with Create Train Next's colour light signals showing their aspect (one in a chunk that is not loaded shows the aspect last read), the trains where they are now, and the speed limits as they are set now, as the server sends them twice a second while a screen is in sight.
A track tag block gives a stretch of track tags, such as "Local" or "Siding", which you name. A timetable can hold a train to a tag: a train's tag (version 7) holds on every way between its stops, and a stop's tag on the way from it to the next stop only. A train held to a tag runs only on track with no tag and on stretches with its tag, though it may leave the stretch it starts in; at a junction it takes the way to a stretch with its tag, and with no way it does not leave. Loading the timetable and /rttimetable status say which ways have none on their tag, and which tags the world lacks. The editor checks the same from the exported network (version 3), and offers the world's tags in the boxes for a train and its stops.
- There are two blocks, Track Tag (Both Ways) and Track Tag (One Way). Each is placed as a station is: use the item on the track first, facing the way the stretch is to run, then place the block near it. A railway casing and a paper make two blocks for both ways, and either block can be crafted into the other. Both are in Create's creative tab.
- Its stretch runs the way it faces, to the next block of its kind that faces the same way, or to the end of the track. The tags of a block for both ways hold for trains running either way along its stretch; those of a one-way block hold only for trains running the way it faces, so that one track can have a tag for each way. A block may have more than one tag, and where stretches overlap, the track has the tags of them all. A block with no tag only ends the stretch of the block before it. A block facing the other way, or of the other kind, does not end it.
- Anyone can use the block, from up to 64 blocks away, to open its screen. The screen lists the world's tags, with a mark by those the block has: pick one and press "Give" or "Take Off", or press "Take All Off". Type a name to "Create" a tag, which the block is then given, or to "Rename" the tag picked; "Delete" takes out the tag picked if no block on the track has it. A tag renamed is renamed on every block (a timetable that names it will have to be changed by hand). Pick a colour for the tag picked, from the types' colours or as
#rrggbb, for the maps to draw its stretches in; "Colour by Name" goes back to the colour its name picks, as a type's does. The names of the block's tags show over it while you look at it. - A stretch must not come to a junction, where the track goes on more than one way, before its end: put a block just past the junction on each way instead. Nor may the two kinds be mixed: a stretch must not overlap that of a block of the other kind, or have a block of the other kind in it. The screen says what is wrong with the block's stretch, and
/rtnetwork tagslists every such problem.
The settings are per world, in serverconfig/createrealtimeschedule-server.toml of the world folder. A client uses the server's values while it is connected.
| Setting | Default | What it does |
|---|---|---|
sync_sky |
true |
Makes the sky follow the clock (see above) |
start_time |
"06:00" |
The time of day the clock starts at in a world that has no saved clock |
align_on_start |
false |
Sets the clock to the server's own time of day whenever the server starts |
default_minimum_stop_seconds |
20 |
The minimum stop (0 to 600) a new Real Time of Day condition starts with in the schedule window. Conditions that already exist keep their own value. Trains running a duty of the timetable stop this long at the least, unless the timetable gives the stop or its station a minimum stop of its own |
duty_lead_seconds |
60 |
How long (0 to 3600 seconds) before a duty of the timetable first leaves it is given a train |
max_wait_seconds |
300 |
How long (0 to 3600 seconds) past its own time a train waits at the most for a train the timetable has it wait for; it leaves at once when that train is forecast to be later. A wait of the timetable may give its own (max_wait) |
train_length_blocks |
40 |
How long (1 to 10000 blocks) a train is taken to be where the timetable gives it no length, for how many stretches of track between signals it takes up |
map_screen_max_size |
32 |
The most map screen blocks (1 to 64) a screen has each way. A larger wall shows the map on each block alone. A new value takes effect when a block of the screen is put or broken |
recovery_threshold_seconds |
600 |
How late (0 to 7200 seconds) a train running a duty of the timetable may be before trains are cancelled to get it back on time, and how late a duty with no free train by its time may still start (see above). 0 turns both off: a late train runs on late, and such a duty does not run that day |
Checked by the tests (./gradlew test and ./gradlew runGameTestServer): the clock arithmetic, saving and sending the clock, the sky sync, the commands, the condition's rule and how slots follow each other, saving a train that waits on the condition, and a real train running on real track through three stations in a row, arriving early and late. For running a timetable: loading and saving it, two trains at the depot of a loop line taking the duties in the order they leave and coming back, leaving every call on its time (recorded when the train has really left the platform), a late train making up time, a train later than the threshold held at a station and leaving on the later call while the trains in between are cancelled (each rule of when it is held is also tested by taking it out), a duty with no train starting late with the train another duty brings back or, too late, not at all, a duty that was to start before the timetable was loaded starting late, a local waiting for an express to run through a station (seen as its last carriage goes past) or to leave it first, not waiting for one that does not run or past its longest wait, and a train that is given a schedule being let go. For the diagram: the distances between the stations from real track, the times sent to a client, and how the lines are worked out. For the editor: that it finds the same problems as the mod (shared cases read by both the Java and the Node tests), writes the same JSON, draws lines as the game does, and that a timetable made with its own code runs on real track on time; and, on the server's side, the clipboard commands. For track tags: the stretches the blocks give and what is wrong with them, on networks written out by hand and on real track, and the world's list of tags; and, looked at on a real client, the two blocks, their marks on the track (also while the item picks a place), the names over them, the screen with several tags, and placing them with the items.
Not checked yet:
- loops, curves, signals and several trains with the condition on real track;
- how the settings line looks in the schedule window (it only compiles);
- several players on a dedicated server seeing the same time and sky;
- that a client without the mod is refused by a server that has it;
- that a client takes the server's
default_minimum_stop_secondswhen it joins. NeoForge's code sends the server's config files to a joining client, but this has not been tried with a real client; - running a timetable across a restart of the server (only saving its state and reading it back is tested), on track with signals where several trains share a line, and with a train that derails on the way;
- holding a late train at a station on track with signals, where other trains run too, and how cancelled trains look in the diagram on a real client;
- each of the diagram's keys and mouse controls on a real client (the diagram itself has been looked at there: the timetable's lines and the real ones lie over each other, and its clock agrees with the server's);
- the clipboard commands and
/rteditoron a real client, and the editor opened from afile://address (it has been used in a Chromium browser fromhttp://localhost), and the GitHub Pages site, which is published once Pages is switched on and the work is on main.
Java 21. ./gradlew build makes the jar in build/libs. The first run downloads Minecraft, NeoForge and Create, and takes a while (about ten minutes).
./gradlew test # unit tests
./gradlew runGameTestServer # game tests, on a test server
./gradlew runClient # a development client
./gradlew runServer # a development server; accept the EULA in run/eula.txt on the first start
GitHub Actions runs ./gradlew build and ./gradlew runGameTestServer for every pull request into main and for every push to main (.github/workflows/build.yml).
The game tests that time trains under the speed-limit signs of Create Train Next run only when a jar of a build of it is at libs/createtrainnext.jar (git ignores libs/). Create Train Next has no release to depend on, so CI does not run them.
MIT. See LICENSE.