Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --Station Program
- -- Author: Andrew Lalis, all rights reserved.
- -- This program is not to be distributed without
- -- express permission from the author, by any means.
- --Constants
- local configFile = "station_config"
- local pastebinURL = "YZtchLvt"
- local VERSION = "1.0"
- --Protocol for broadcasting station metadata.
- local metadataProt = "STATION_META"
- --Protocol for requesting metadata from all stations.
- local metadataRequestProt = "STATION_META_REQUEST"
- --Protocol for requesting a train to be dispatched from a station.
- local trainDispatchProt = "TRAIN_DISPATCH"
- --Protocol for train admin commands.
- local trainAdminProt = "TRAIN_ADMIN"
- --Config table containing all station information.
- local stationConfig
- --Offset if the user has scrolled on the stations list.
- local stationsOffset = 0
- --Peripherals.
- local ticketMachine
- local noteBlock
- local monitor
- --Timers
- -- Prompt on monitor screen.
- local promptTimer
- local inPrompt = false
- -- Requesting train from another station.
- local requestTimer
- --Returns the number of items in a table.
- function tableSize(tbl)
- local count = 0
- for i in pairs(tbl) do count = count + 1 end
- return count
- end
- --Logging function to make output more pretty.
- function logMsg(t, msg)
- local c = colors.white
- if (t == "ERROR") then
- c = colors.red
- elseif (t == "INFO") then
- c = colors.blue
- elseif (t == "INFO_DETAIL") then
- c = colors.lightBlue
- term.write(" ")
- elseif (t == "INFO_HIGH") then
- c = colors.magenta
- elseif (t == "WARNING") then
- c = colors.orange
- end
- term.setTextColor(c)
- print("["..t.."]: "..msg)
- end
- --Saves the current station config to a file.
- function saveConfig()
- local f = fs.open(configFile, "w")
- f.write(textutils.serialize(stationConfig))
- f.close()
- --logMsg("INFO_DETAIL", "Station config saved.")
- end
- --Wraps a string into a table.
- function wrap(str, limit)
- limit = limit or 72
- local here = 1
- local buf = ""
- local t = {}
- str:gsub("(%s*)()(%S+)()",
- function(sp, st, word, fi)
- if fi-here > limit then
- --# Break the line
- here = st
- table.insert(t, buf)
- buf = word
- else
- buf = buf..sp..word --# Append
- end
- end)
- --# Tack on any leftovers
- if(buf ~= "") then
- table.insert(t, buf)
- end
- return t
- end
- --Gets the cosmetic name of a station, given the destination name.
- function getCosmeticName(dest)
- for i,v in ipairs(stationConfig.otherStations) do
- if (v[1] == dest) then
- return v[2]
- end
- end
- return nil
- end
- --Draws a horizontal line across the screen.
- function drawLine(y, c)
- local sizeX,sizeY = monitor.getSize()
- monitor.setTextColor(colors.black)
- monitor.setBackgroundColor(c)
- for i=1,sizeX do
- monitor.setCursorPos(i,y)
- monitor.write(" ")
- end
- end
- --Draws a box of a color
- function drawBox(x, y, width, height, bc)
- monitor.setBackgroundColor(bc)
- for i=x,width+x do
- for k=y,height+y do
- monitor.setCursorPos(i,k)
- monitor.write(" ")
- end
- end
- end
- --Writes to the screen with a certain color, background color, and other parameters.
- function writeToScreen(text, x, y, tc, bc)
- monitor.setCursorPos(x,y)
- monitor.setTextColor(tc)
- monitor.setBackgroundColor(bc)
- monitor.write(text)
- end
- --Writes to the screen centered at some x coordinate.
- function writeCentered(text, x, y, tc, bc)
- monitor.setTextColor(tc)
- monitor.setBackgroundColor(bc)
- monitor.setCursorPos((x - (string.len(text)/2)),y)
- monitor.write(text)
- end
- --Function to draw station monitor with a given offset for the stations list.
- function drawMonitor()
- local sizeX,sizeY = monitor.getSize()
- monitor.setBackgroundColor(colors.black)
- monitor.clear()
- --Draw Station Name.
- drawLine(1, colors.blue)
- writeCentered(stationConfig.name, sizeX/2, 1, colors.white, colors.blue)
- --Draw help button.
- writeToScreen("Help",sizeX-3,1,colors.yellow,colors.lightBlue)
- --Draw list of stations, as found in the station config.
- local index = 0
- for i=1,tableSize(stationConfig.otherStations) do
- local bc = colors.gray
- if (i % 2 == 0) then
- bc = colors.lightGray
- end
- drawLine(i+1, bc)
- writeToScreen(stationConfig.otherStations[i][2], 2, i+1, colors.white, bc)
- end
- end
- --Draws wrapped text.
- function drawWrappedText(text, x, y, width, height, tc, bc)
- drawBox(x,y,width,height,bc)
- local t = wrap(text, width)
- if (tableSize(t) > height) then
- logMsg("WARNING", "Text is too large to wrap in given bounds.")
- end
- for i,line in ipairs(t) do
- writeToScreen(line, x, y + i - 1, tc, bc)
- end
- end
- --Draws an alert which disappears in a small time.
- function prompt(text, delay, tc, bc)
- local sizeX, sizeY = monitor.getSize()
- drawBox(3,3,sizeX-6,sizeY-1, bc)
- drawWrappedText(text,4,4,sizeX-8,sizeY-2, tc, bc)
- promptTimer = os.startTimer(delay)
- inPrompt = true
- end
- --Beep the noteblock to indicate the track on which a train is being dispatched.
- function beep(count)
- for i=1,count do
- noteBlock.triggerNote()
- os.sleep(0.5)
- end
- end
- --Updates the station file from pastebin.
- function updateStationFile()
- prompt("Updating station software. Please wait while the system restarts. Thank you.",
- 5,
- colors.yellow,
- colors.white)
- logMsg("INFO", "Fetching update from pastebin.")
- fs.delete("station")
- shell.run("pastebin", "get", pastebinURL, "station")
- logMsg("INFO", "System will now reboot.")
- --Send a message to the train admin.
- rednet.broadcast(stationConfig.dest.." has updated.", trainAdminProt)
- os.sleep(3)
- os.reboot()
- end
- --Request a train from a station which has more than 1 train.
- function requestTrain()
- for i=1,tableSize(stationConfig.otherStations) do
- if (stationConfig.otherStations[i][3] > 1) then
- rednet.send(stationConfig.otherStations[i][4], stationConfig.dest, trainDispatchProt)
- logMsg("INFO", "Sent request to "..stationConfig.otherStations[i][1].." for a train.")
- return
- end
- end
- logMsg("WARNING", "Unable to find a station with an extra train, waiting 1 min before next attempt.")
- requestTimer = os.startTimer(60)
- end
- --Dispatches a train from a track, by sending a ticket to it.
- function dispatchTrain(destination, trainCountAtDest, isPlayer)
- --Iterate until an occupied track is found.
- for i=1,tableSize(stationConfig.tracks) do
- if (stationConfig.tracks[i][1]) then
- local success = ticketMachine.createTicket(destination,1)
- if (not success) then
- logMsg("ERROR", "Unable to create ticket.")
- prompt("Unable to create ticket. Please check that the ticket machine has ink and paper.",
- 5,
- colors.red,
- colors.white)
- return false
- end
- logMsg("INFO_HIGH", "Train on track "..i.." will be dispatched to "..destination..".")
- if (isPlayer) then
- prompt("Train to "..getCosmeticName(destination).." leaves on track "..i..". Have a pleasant journey!",
- 5,
- colors.green,
- colors.white)
- end
- beep(i)
- redstone.setBundledOutput("top", stationConfig.tracks[i][2])
- os.sleep(2)
- redstone.setBundledOutput("top", 0)
- return true
- end
- end
- logMsg("ERROR", "Unable to find train to dispatch.")
- prompt("Error, unable to find train to dispatch. Please ensure that the system has n+1 trains, where n = number of stations.",
- 5,
- colors.red,
- colors.white)
- return false
- end
- --Broadcasts the station's name, destination, and train count to all other stations.
- function broadcastMetadata()
- rednet.broadcast({stationConfig.dest,stationConfig.name,stationConfig.trainCount}, metadataProt)
- --logMsg("INFO_DETAIL", "Broadcasted metadata.")
- end
- --Scans redstone inputs from each track.
- -- Updates tracks table, and train count, then broadcasts.
- function scanTracks()
- local trainCount = 0
- local changed = false
- for i=1,tableSize(stationConfig.tracks) do
- local previous = stationConfig.tracks[i][1]
- stationConfig.tracks[i][1] = colors.test(redstone.getBundledInput("top"), stationConfig.tracks[i][3])
- if (stationConfig.tracks[i][1]) then
- trainCount = trainCount + 1
- end
- if (previous ~= stationConfig.tracks[i][1]) then
- changed = true
- end
- --Test if a train has arrived, and provide a welcome message.
- if (not previous and stationConfig.tracks[i][1]) then
- prompt("Welcome to "..stationConfig.name..". Thank you for using HandieBahn!",
- 5,
- colors.blue,
- colors.white)
- end
- end
- stationConfig.trainCount = trainCount
- if (trainCount < 1) then
- requestTrain()
- end
- if (changed) then
- broadcastMetadata()
- end
- saveConfig()
- --logMsg("INFO_DETAIL", "Tracks scanned.")
- end
- --Determines if occupancy of the tracks is changed according to config.
- function tracksChanged()
- for i=1,tableSize(stationConfig.tracks) do
- local previous = stationConfig.tracks[i][1]
- local current = colors.test(redstone.getBundledInput("top"), stationConfig.tracks[i][3])
- if (previous ~= current) then
- --logMsg("INFO_DETAIL", "Track occupancy has changed.")
- return true
- end
- end
- return false
- end
- --What to do when a redstone event is triggered.
- function onRedstoneEvent()
- if (tracksChanged()) then
- scanTracks()
- end
- end
- --Saves a station's metadata into the stationConfig file.
- function saveStation(dest, name, trainCount, compID)
- --First determine if the station already exists in the file.
- local index = 0
- for k,v in pairs(stationConfig.otherStations) do
- if (v[1] == dest) then
- index = k
- break
- end
- end
- if (index ~= 0) then
- stationConfig.otherStations[index] = {dest, name, trainCount, compID}
- else
- table.insert(stationConfig.otherStations, {dest, name, trainCount, compID})
- end
- saveConfig()
- end
- --What to do when a rednet message is received.
- function onRednetMessage(id, msg, prot)
- --If the computer is receiving metadata from another station.
- if (prot == metadataProt or (prot == metadataRequestProt and msg ~= "REQUEST")) then
- saveStation(msg[1], msg[2], msg[3], id)
- logMsg("INFO", "Received metadata from station: "..msg[1])
- if not inPrompt then drawMonitor() end
- --If another computer has requested this computer's metadata.
- elseif (prot == metadataRequestProt and msg == "REQUEST") then
- logMsg("INFO", "Computer "..id.." has requested metadata. Transmitting.")
- rednet.send(id, {stationConfig.dest,stationConfig.name,stationConfig.trainCount}, metadataRequestProt)
- --If another computer has requested a train to be dispatched to a location.
- elseif (prot == trainDispatchProt) then
- logMsg("INFO", "Train requested at: "..msg)
- dispatchTrain(msg, 0, false)
- --If an administrator has sent a message to the station.
- elseif (prot == trainAdminProt) then
- if (msg == "UPDATE") then
- updateStationFile()
- end
- end
- end
- --What to do when the monitor is touched.
- function onMonitorTouch(x, y)
- local stationIndex = y - 1 + stationsOffset
- local sizeX,sizeY = monitor.getSize()
- --Test first if the user clicks while a prompt is shown, then it will close that.
- if (inPrompt) then
- os.queueEvent("timer", promptTimer)
- return
- end
- --Test if the user clicked on a station's name.
- if (y > 1) then
- --Make sure that a station exists first.
- if (stationIndex <= tableSize(stationConfig.otherStations)) then
- local selectedStation = stationConfig.otherStations[stationIndex]
- --logMsg("INFO", "User selected station: "..selectedStation[1])
- dispatchTrain(selectedStation[1], selectedStation[3], true)
- end
- --The user may have clicked the help button.
- elseif (x >= sizeX-3) then
- --The user has requested help.
- prompt("To use this station, simply click on one of the station names in gray shown below, and a train will be dispatched to that destination.",
- 10,
- colors.black,
- colors.white)
- end
- end
- --What to do when a timer completes.
- function onTimerEvent(timerId)
- --If the timer for the pop-up prompt on screen goes off.
- if (timerId == promptTimer) then
- drawMonitor()
- inPrompt = false
- --Timer for checking if it's possible to dispatch a train to a requested destination.
- elseif (timerId == requestTimer) then
- scanTracks()
- end
- end
- --Handles all incoming events.
- function eventHandler()
- local event, p1, p2, p3, p4, p5 = os.pullEvent()
- --If a redstone signal changes, then do something.
- if (event == "redstone") then
- onRedstoneEvent()
- --If the monitor is touched, pass the x and y coordinates to a function.
- elseif (event == "monitor_touch") then
- onMonitorTouch(p2, p3)
- --If a rednet message is received, pass the ID, message, and protocol to a function.
- elseif (event == "rednet_message") then
- onRednetMessage(p1, p2, p3)
- elseif (event == "timer") then
- onTimerEvent(p1)
- end
- end
- --Sets up the config for the first time, and returns the resulting table.
- function firstTimeSetup()
- term.setTextColor(colors.lightBlue)
- logMsg("WARNING", "No config file found. Starting first-time setup...")
- local config = {
- tracks={},
- otherStations={},
- }
- term.setTextColor(colors.white)
- print(" What is the number of the monitor connected to this station?")
- config.monitor_id = "monitor_"..read()
- print(" What is the number of the note block connected to this station?")
- config.note_block_id = "note_block_"..read()
- print(" What is the cosmetic name of this station?")
- config.name = read()
- print(" What is the railcraft destination for this station?")
- config.dest = read()
- print(" How many tracks does this station have?")
- local trackCount = tonumber(read())
- for i=1,trackCount do
- config.tracks[i] = {}
- config.tracks[i][1] = false
- print(" What is track "..i.."'s dispatch decimal color code?")
- config.tracks[i][2] = tonumber(read())
- print(" What is track "..i.."'s detection decimal color code?")
- config.tracks[i][3] = tonumber(read())
- end
- local f = fs.open(configFile, "w")
- f.write(textutils.serialize(config))
- f.close()
- term.setTextColor(colors.lime)
- print("Setup is complete.")
- term.setTextColor(colors.white)
- return config
- end
- --Loads config from a file, or if there is none, create it from user input.
- function loadConfig()
- if (not fs.exists(configFile)) then
- return firstTimeSetup()
- else
- local f = fs.open("station_config", "r")
- local config = textutils.unserialize(f.readAll())
- f.close()
- return config
- end
- end
- --Perform things before the program begins listening for input.
- function setup()
- logMsg("INFO_HIGH", "Station Version: "..VERSION)
- stationConfig = loadConfig()
- --Setup peripherals
- rednet.open("bottom")
- ticketMachine = peripheral.wrap("back")
- noteBlock = peripheral.wrap(stationConfig.note_block_id)
- monitor = peripheral.wrap(stationConfig.monitor_id)
- --Broadcast metadata to all other stations.
- scanTracks()
- --Broadcast a request message to other stations to get their information.
- rednet.broadcast("REQUEST", metadataRequestProt)
- --Draw onto the monitor for the first time.
- drawMonitor()
- end
- --Main loop
- setup()
- while (true) do
- eventHandler()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement