Create: Computing AgesBrass Docs
Devices

Trains

Create's Train Station, Train Signal and Train Observer: the train at the platform, its schedule, where it can go, assembly, and the signals along the line.

A computer next to a Create Train Station knows which train stands at the platform, which one is coming, and where it could go from there. It renames trains, reads and writes their schedules, and assembles or takes them apart. Train Signals and Train Observers tell it what happens along the line. With them, a program runs a depot, a passenger board or a whole timetable.

Brass
local st = peripheral.find("station")
if st.train_present() then
  print(st.train_name() .. " stands at " .. st.name())
end
Methods
station.name()The name of the station, as written in its screen.
station.set_name(name)Renames the station, as if you typed the name in its screen.
station.train_present()True when a train has stopped at this station.
station.train_imminent()True when the train coming here is less than 30 blocks away along the track, or already at the platform: the moment to ring a bell or to light a lamp at the edge of the platform.
station.train_enroute()True as soon as a train has this station as its next stop, however far away it is. It stays true while the train stands at the platform, and turns false when the train leaves.
station.train_name()The name of the train standing at the station. Stops the program with there is no train present when the platform is empty: a program that wants the name of a train that just left must keep it in a variable.
station.set_train_name(name)Renames the train standing at the station. Every player sees the new name at once.
station.has_schedule()Whether the train standing at the station follows a schedule.
station.schedule()The schedule of the train standing at the station. Stops the program with train doesn't have a schedule when it has none.
station.set_schedule(schedule)Gives a schedule to the train standing at the station. It replaces the one the train had, and the train starts it at once, from its first entry (or from the entry a progress key names, counted from 0, when the table has one).
station.can_reach(name)Whether the train standing at the station can get to a station matching name.
station.distance_to(name)How far the train standing at the station would travel to a station matching name.
station.assembling()Whether the station is in assembly mode. While it is, no scheduled train comes to it.
station.set_assembly_mode(on)Enters or leaves assembly mode.
station.assemble()Assembles the train standing in the station's assembly zone, then leaves assembly mode: the new train is ready to go.
station.disassemble()Takes the train standing at the platform apart, into blocks on the track.
sig.state()The light the signal shows now.
sig.forced_red()Whether the signal is held at red, whatever the trains do.
sig.set_forced_red(red)Holds the signal at red, as a redstone signal on it would: trains wait before it.
sig.blocking_trains()The trains in the section after the signal: the ones a train waiting at a red light waits for.
sig.signal_type()The kind of signal:
sig.cycle_signal_type()Switches the signal to the other kind, as Create's Wrench does.
obs.train_passing()Whether a train is passing now. The observer stays on for a few ticks after the last carriage, so asking every 0.2 second misses no train.
obs.passing_train()The name of the train going past, or nil when none is.

Setting up

Three blocks. The Train Station is a peripheral of type station, the Train Signal of type signal, the Train Observer of type track_observer. Put one against a face of the computer, or against a Data Cable of its network (Minicomputer and newer, named like station@120,64,-35), then wrap it: peripheral.wrap("left"), or peripheral.find("station"). See Peripherals.

On a track. A station or a signal only works once it is placed against a track, the way Create places it (right-click the track with it first). Otherwise the station stops the program with station is not connected to a track, and the signal with no signal: place it against a track.

Trains are shared. A train belongs to the whole server: a name or a schedule a program changes is seen at once by every player and every other station.

The train at the platform. Many methods work on the train standing at the station. When there is none, they stop the program with there is no train present: ask train_present() first.

No events: ask in a loop. Create's own ComputerCraft peripherals send events (train_arrival, train_passing...). Brass has no peripheral events: a program asks again and again, with a short sleep between two questions. Half a second is plenty for trains.

Brass
local st = peripheral.find("station")
while not st.train_present() do
  sleep(0.5)
end
print(st.train_name() .. " has arrived")

To do other work meanwhile, see the main loop. When a block is broken, its methods stop the program with station removed, signal removed or track observer removed, and with peripheral is not loaded when its chunk is not loaded.

The station

#

station.name()

→ string

The name of the station, as written in its screen.

Returns
string
the name of the station
Brass
local st = peripheral.find("station")
print("Welcome to " .. st.name())

See also station.set_name()

#

station.set_name(name)

Renames the station, as if you typed the name in its screen.

Parameters
name string
the new name, cut to 64 characters

Schedules name their stations by text: a train whose schedule asks for the old name will not find the station any more. A station that is not on a track cannot be renamed: the program stops with could not set station name.

See also station.name()

#

station.train_present()

→ boolean

True when a train has stopped at this station.

Returns
boolean
true while a train stands at the platform

A train that runs through without stopping does not count: watch it with a Train Observer instead.

See also station.train_imminent() station.train_enroute() station.train_name()

#

station.train_imminent()

→ boolean

True when the train coming here is less than 30 blocks away along the track, or already at the platform: the moment to ring a bell or to light a lamp at the edge of the platform.

Returns
boolean
true when a train is about to arrive, or already stands at the platform
Brass
local st = peripheral.find("station")
while true do
  rs.set("top", st.train_imminent())  -- a lamp above the computer
  sleep(0.5)
end

See also station.train_present()

#

station.train_enroute()

→ boolean

True as soon as a train has this station as its next stop, however far away it is. It stays true while the train stands at the platform, and turns false when the train leaves.

Returns
boolean
true when a train is on its way to this station, or stands at it

The three questions answer true one after the other: train_enroute, then train_imminent, then train_present.

See also station.train_present()

#

station.train_name()

→ string

The name of the train standing at the station. Stops the program with there is no train present when the platform is empty: a program that wants the name of a train that just left must keep it in a variable.

Returns
string
the name of the train at the platform

See also station.set_train_name()

#

station.set_train_name(name)

Renames the train standing at the station. Every player sees the new name at once.

Parameters
name string
the new name, cut to 64 characters
Brass
local st = peripheral.find("station")
if st.train_present() then
  st.set_train_name("Iron Express")
end

See also station.train_name()

Schedules

A train follows its schedule, the one written in a Train Schedule: a list of stops, each with the conditions to wait for before leaving. A program reads it as a table, and gives a new one to the train at the platform. The table has the shape of Create's own schedule data, with its keys in snake_case (the same shape as Create's ComputerCraft peripheral):

Brass
local schedule = {
  cyclic = true,
  entries = {
    {
      instruction = {id = "create:destination", data = {text = "Iron Mine"}},
      conditions = {
        { {id = "create:delay", data = {value = 20, time_unit = 1}} },
      },
    },
    {
      instruction = {id = "create:destination", data = {text = "Smeltery"}},
      conditions = {
        { {id = "create:idle", data = {value = 5, time_unit = 1}} },
      },
    },
  },
}
  • cyclic: true starts again from the first entry after the last one; false stops the train after it.
  • entries: the steps, in order. Each one has an instruction, {id =, data =}.
  • conditions, for a destination: a list of groups of conditions. The train leaves as soon as one group is complete, and a group is complete when its conditions are met, one after the other. Create's screen shows each group as a column. Even a single condition sits in its own group: { {condition} }. A stop without any condition keeps the train there for good.

The usual instructions and conditions:

idwhat it doesdata
create:destinationgo to a station (* allowed in the name)text: the name of the station
create:renamechange the title of the schedule, the text displays showtext: the new title
create:throttlelimit the top speed of the trainvalue: percent
create:delaycondition: wait some timevalue, and time_unit: 0 ticks, 1 seconds, 2 minutes
create:idlecondition: the cargo has not moved for some timevalue, time_unit
create:poweredcondition: the station receives redstonenone

For any other, set it up in a Train Schedule in game, give it to a train, and look at what schedule() returns.

#

station.has_schedule()

→ boolean

Whether the train standing at the station follows a schedule.

Returns
boolean
true when the train at the platform has a schedule
Brass
local st = peripheral.find("station")
if st.train_present() and not st.has_schedule() then
  print(st.train_name() .. " has no schedule")
end

A train keeps its schedule once it is done with it (a schedule that is not cyclic stops the train, it does not go away).

See also station.schedule()

#

station.schedule()

→ table⚙ cost 16

The schedule of the train standing at the station. Stops the program with train doesn't have a schedule when it has none.

Returns
table
the schedule of the train at the platform, shaped as above

Listing the stops:

Brass
local st = peripheral.find("station")
local s = st.schedule()
for i, entry in ipairs(s.entries) do
  if entry.instruction.id == "create:destination" then
    print(i .. ". " .. entry.instruction.data.text)
  end
end

See also station.set_schedule() station.has_schedule()

#

station.set_schedule(schedule)

Gives a schedule to the train standing at the station. It replaces the one the train had, and the train starts it at once, from its first entry (or from the entry a progress key names, counted from 0, when the table has one).

Parameters
schedule table
a schedule shaped like the one schedule() returns

The table may hold 4096 values at most, 16 levels deep. Without any entry the program stops with a schedule needs at least one entry. When the train had no schedule, the new one behaves like a schedule handed out by a station: a player who takes it off the train gets no Train Schedule back.

Brass
local st = peripheral.find("station")
local s = st.schedule()
s.cyclic = false  -- one more trip, then stop
st.set_schedule(s)

See also station.schedule()

Where can the train go?

Before sending a train, a program can ask whether it can get there at all: a station renamed, a track broken or a signal missing is enough to cut a line. These two methods look at every station of the train's track network whose name matches a filter:

  • * stands for any text, none included: "Mine*" matches Mine, Mine 2 and Mine North;
  • ? stands for exactly one character: "Platform ?" matches Platform 1 to Platform 9, not Platform 10;
  • everything else must be exactly the same, capitals included. A filter has 64 characters at most.

Both answer for the train standing at the platform (there is no train present otherwise), the way Create's navigation would drive it: a train that can only go forwards may not reach a station that another train could. The search through the network is what makes them costly.

#

station.can_reach(name)

→ boolean⚙ cost 64

Whether the train standing at the station can get to a station matching name.

Parameters
name string
the name of a station, * and ? allowed
Returns
boolean
true when the train at the platform can reach a station of that name
Brass
local st = peripheral.find("station")
if st.can_reach("Iron Mine") then
  print("the line to the mine is open")
else
  print("no way to the mine from here")
end

See also station.distance_to()

#

station.distance_to(name)

→ number|nil⚙ cost 64

How far the train standing at the station would travel to a station matching name.

Parameters
name string
the name of a station, * and ? allowed
Returns
number|nil
the distance in blocks along the track, rounded to 0.01, or nil when no such station can be reached

When several stations match, it is the distance to the one the navigation would choose, usually the nearest.

Brass
local st = peripheral.find("station")
local d = st.distance_to("Quarry*")
if d then
  print("nearest quarry: " .. math.floor(d) .. " blocks")
end

See also station.can_reach()

Assembling trains

In assembly mode, a station turns the bogeys and glued blocks standing on its track into a train, and takes a train apart into blocks again. These methods do what the buttons of the station's screen do.

Like a player clicking those buttons, a station changes once per tick at most: a second set_assembly_mode, assemble or disassemble on the same station in the same tick stops the program with the station already changed this tick: sleep(0) waits for the next. Put a sleep(0) between two of them.

#

station.assembling()

→ boolean

Whether the station is in assembly mode. While it is, no scheduled train comes to it.

Returns
boolean
true while the station is in assembly mode

See also station.set_assembly_mode()

#

station.set_assembly_mode(on)

Enters or leaves assembly mode.

Parameters
on boolean
true to enter assembly mode, false to leave it

Entering it with a train at the platform takes that train apart, like disassemble(). Asking for the mode the station is already in stops the program, with failed to enter assembly mode or failed to exit assembly mode: ask assembling() first.

See also station.assembling() station.assemble()

#

station.assemble()

Assembles the train standing in the station's assembly zone, then leaves assembly mode: the new train is ready to go.

Brass
local st = peripheral.find("station")
if not st.assembling() then
  st.set_assembly_mode(true)
  sleep(0)   -- one change per tick
end
st.assemble()
print(st.train_name() .. " is ready")

Outside assembly mode it stops the program with station must be in assembly mode. When Create refuses to assemble (no Train Controls, a bogey missing...), it stops with failed to assemble train: the station's screen tells why.

See also station.set_assembly_mode() station.disassemble()

#

station.disassemble()

Takes the train standing at the platform apart, into blocks on the track.

The station is then in assembly mode: call assemble() to build the train again, or set_assembly_mode(false) to open the station to trains. A Train Schedule a player gave to the train drops at the station. Errors: station must not be in assembly mode, there is no train present, and could not disassemble train when Create cannot do it.

See also station.assemble()

Train Signal

A Train Signal splits the track into sections, and lets a train into the section after it only when that section is free. A program reads its light, sees which trains hold the section, and can keep it at red.

#

sig.state()

→ string

The light the signal shows now.

Returns
string
"red", "yellow", "green" or "invalid"

"yellow" only shows on a cross signal: some ways out of its section are free, others are not. "invalid" (the light blinks in game) means the signal is not on a track, or does not separate two sections, as on a loop with no other signal.

Brass
local sig = peripheral.find("signal")
if sig.state() == "red" then
  print("the section ahead is taken")
end

See also signal.blocking_trains()

#

sig.forced_red()

→ boolean

Whether the signal is held at red, whatever the trains do.

Returns
boolean
true when the signal is forced to red, by a program or by redstone

See also signal.set_forced_red()

#

sig.set_forced_red(red)

Holds the signal at red, as a redstone signal on it would: trains wait before it.

Parameters
red boolean
true to hold the signal at red, false to let it go

The change is made at the end of the program's tick. Redstone keeps the last word: when a block next to the signal changes, the signal follows its redstone input again. Leave a signal that a program drives without redstone around it.

Brass
local sig = peripheral.find("signal")
sig.set_forced_red(true)   -- works on the line
sleep(120)
sig.set_forced_red(false)

See also signal.forced_red()

#

sig.blocking_trains()

→ table

The trains in the section after the signal: the ones a train waiting at a red light waits for.

Returns
table
the names of the trains in the section the signal guards, an empty list when it is free
Brass
local sig = peripheral.find("signal")
for _, name in ipairs(sig.blocking_trains()) do
  print(name .. " is in the way")
end

See also signal.state()

#

sig.signal_type()

→ string

The kind of signal:

Returns
string
"entry_signal" or "cross_signal"
  • "entry_signal" lets a train in when the section is free;
  • "cross_signal" lets a train in only when it can go all the way through the section, so that no train stops in the middle of a junction or a crossing.

See also signal.cycle_signal_type()

#

sig.cycle_signal_type()

Switches the signal to the other kind, as Create's Wrench does.

See also signal.signal_type()

Train Observer

A Train Observer watches one point of the track and gives a redstone signal while a train goes past. When an item is set in its filter, only trains carrying that cargo count.

#

obs.train_passing()

→ boolean

Whether a train is passing now. The observer stays on for a few ticks after the last carriage, so asking every 0.2 second misses no train.

Returns
boolean
true while a train goes past the observed point

See also track_observer.passing_train()

#

obs.passing_train()

→ string|nil

The name of the train going past, or nil when none is.

Returns
string|nil
the name of the train passing now, or nil

Counting the trains of a line:

Brass
local obs = peripheral.find("track_observer")
local count = 0
local was = false
while true do
  local now = obs.train_passing()
  if now and not was then
    count = count + 1
    print(count .. ": " .. (obs.passing_train() or "a train"))
  end
  was = now
  sleep(0.2)
end

See also track_observer.train_passing()

Examples

An arrivals board

A computer on the platform, with no monitor: its own screen announces each train. The time comes from the world's clock (os.day_time()).

startup
local st = peripheral.find("station")
local last_train = "The train"

local function clock()
  local t = (os.day_time() + 6000) % 24000
  return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end

local function situation()
  if st.train_present() then return "present" end
  if st.train_imminent() then return "imminent" end
  if st.train_enroute() then return "enroute" end
  return "empty"
end

term.clear()
term.set_cursor(1, 1)
print(st.name())
local was = "empty"
while true do
  local now = situation()
  if now ~= was then
    if was == "present" then
      print(clock() .. "  " .. last_train .. " has left")
    end
    if now == "present" then
      last_train = st.train_name()
      print(clock() .. "  " .. last_train .. " at the platform")
    elseif now == "imminent" then
      print(clock() .. "  arriving: stand back")
    elseif now == "enroute" then
      print(clock() .. "  a train is on its way")
    end
    was = now
  end
  sleep(0.5)
end

The name of the train is kept in last_train while it stands at the platform: once it has left, train_name() would stop the program.

A depot that only sends trains where they can go

Each train that stops at the depot gets a new schedule: one trip to the next mine in turn, then back to the depot. A mine whose line is cut is skipped, and when no mine can be reached, the train waits at the depot until one can.

startup
local st = peripheral.find("station")
local DEPOT = st.name()
local MINES = {"Iron Mine", "Coal Mine", "Quarry*"}
local turn = 1

-- one stop: go to a station, wait there some seconds
local function stop(station, seconds)
  return {
    instruction = {id = "create:destination", data = {text = station}},
    conditions = {
      { {id = "create:delay", data = {value = seconds, time_unit = 1}} },
    },
  }
end

-- the next mine the train at the platform can reach, or nil
local function next_mine()
  for i = 1, #MINES do
    local mine = MINES[turn]
    turn = turn % #MINES + 1
    if st.can_reach(mine) then
      return mine
    end
  end
  return nil
end

local served = false
while true do
  if not st.train_present() then
    served = false
  elseif not served then
    local mine = next_mine()
    if mine then
      local blocks = math.floor(st.distance_to(mine))
      st.set_schedule({cyclic = false, entries = {stop(mine, 30), stop(DEPOT, 5)}})
      print(st.train_name() .. " leaves for " .. mine .. ", " .. blocks .. " blocks")
      served = true
    end
  end
  sleep(2)
end

served remembers that the train at the platform has its orders, so it gets them once. When it comes back from the mine, it stops at the depot again and the program sends it to the next mine. A train with no mine in reach is asked again every two seconds: a can_reach costs 64 instructions, a third of a tick of a Tube Computer, so do not ask at every tick.