Advertisement
HandieAndy

Coreless Station V2

Jan 3rd, 2018
441
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.22 KB | None | 0 0
  1. --Station Program
  2. --  Author: Andrew Lalis, all rights reserved.
  3. --  This program is not to be distributed without
  4. --  express permission from the author, by any means.
  5.  
  6. --Constants
  7. local configFile = "station_config"
  8. local pastebinURL = "YZtchLvt"
  9. local VERSION = "1.0"
  10.  
  11. --Protocol for broadcasting station metadata.
  12. local metadataProt = "STATION_META"
  13. --Protocol for requesting metadata from all stations.
  14. local metadataRequestProt = "STATION_META_REQUEST"
  15. --Protocol for requesting a train to be dispatched from a station.
  16. local trainDispatchProt = "TRAIN_DISPATCH"
  17. --Protocol for train admin commands.
  18. local trainAdminProt = "TRAIN_ADMIN"
  19.  
  20. --Config table containing all station information.
  21. local stationConfig
  22.  
  23. --Offset if the user has scrolled on the stations list.
  24. local stationsOffset = 0
  25.  
  26. --Peripherals.
  27. local ticketMachine
  28. local noteBlock
  29. local monitor
  30.  
  31. --Timers
  32. -- Prompt on monitor screen.
  33. local promptTimer
  34. local inPrompt = false
  35. -- Requesting train from another station.
  36. local requestTimer
  37.  
  38. --Returns the number of items in a table.
  39. function tableSize(tbl)
  40.     local count = 0
  41.     for i in pairs(tbl) do count = count + 1 end
  42.     return count
  43. end
  44.  
  45. --Logging function to make output more pretty.
  46. function logMsg(t, msg)
  47.     local c = colors.white
  48.     if (t == "ERROR") then
  49.         c = colors.red
  50.     elseif (t == "INFO") then
  51.         c = colors.blue
  52.     elseif (t == "INFO_DETAIL") then
  53.         c = colors.lightBlue
  54.         term.write("  ")
  55.     elseif (t == "INFO_HIGH") then
  56.         c = colors.magenta
  57.     elseif (t == "WARNING") then
  58.         c = colors.orange
  59.     end
  60.     term.setTextColor(c)
  61.     print("["..t.."]: "..msg)
  62. end
  63.  
  64. --Saves the current station config to a file.
  65. function saveConfig()
  66.     local f = fs.open(configFile, "w")
  67.     f.write(textutils.serialize(stationConfig))
  68.     f.close()
  69.     --logMsg("INFO_DETAIL", "Station config saved.")
  70. end
  71.  
  72. --Wraps a string into a table.
  73. function wrap(str, limit)
  74.   limit = limit or 72
  75.   local here = 1
  76.   local buf = ""
  77.   local t = {}
  78.   str:gsub("(%s*)()(%S+)()",
  79.   function(sp, st, word, fi)
  80.         if fi-here > limit then
  81.            --# Break the line
  82.            here = st
  83.            table.insert(t, buf)
  84.            buf = word
  85.         else
  86.            buf = buf..sp..word  --# Append
  87.         end
  88.   end)
  89.   --# Tack on any leftovers
  90.   if(buf ~= "") then
  91.         table.insert(t, buf)
  92.   end
  93.   return t
  94. end
  95.  
  96. --Gets the cosmetic name of a station, given the destination name.
  97. function getCosmeticName(dest)
  98.     for i,v in ipairs(stationConfig.otherStations) do
  99.         if (v[1] == dest) then
  100.             return v[2]
  101.         end
  102.     end
  103.     return nil
  104. end
  105.  
  106. --Draws a horizontal line across the screen.
  107. function drawLine(y, c)
  108.     local sizeX,sizeY = monitor.getSize()
  109.     monitor.setTextColor(colors.black)
  110.     monitor.setBackgroundColor(c)
  111.     for i=1,sizeX do
  112.         monitor.setCursorPos(i,y)
  113.         monitor.write(" ")
  114.     end
  115. end
  116.  
  117. --Draws a box of a color
  118. function drawBox(x, y, width, height, bc)
  119.     monitor.setBackgroundColor(bc)
  120.     for i=x,width+x do
  121.         for k=y,height+y do
  122.             monitor.setCursorPos(i,k)
  123.             monitor.write(" ")
  124.         end
  125.     end
  126. end
  127.  
  128. --Writes to the screen with a certain color, background color, and other parameters.
  129. function writeToScreen(text, x, y, tc, bc)
  130.     monitor.setCursorPos(x,y)
  131.     monitor.setTextColor(tc)
  132.     monitor.setBackgroundColor(bc)
  133.     monitor.write(text)
  134. end
  135.  
  136. --Writes to the screen centered at some x coordinate.
  137. function writeCentered(text, x, y, tc, bc)
  138.     monitor.setTextColor(tc)
  139.     monitor.setBackgroundColor(bc)
  140.     monitor.setCursorPos((x - (string.len(text)/2)),y)
  141.     monitor.write(text)
  142. end
  143.  
  144. --Function to draw station monitor with a given offset for the stations list.
  145. function drawMonitor()
  146.     local sizeX,sizeY = monitor.getSize()
  147.     monitor.setBackgroundColor(colors.black)
  148.     monitor.clear()
  149.     --Draw Station Name.
  150.     drawLine(1, colors.blue)
  151.     writeCentered(stationConfig.name, sizeX/2, 1, colors.white, colors.blue)
  152.     --Draw help button.
  153.     writeToScreen("Help",sizeX-3,1,colors.yellow,colors.lightBlue)
  154.     --Draw list of stations, as found in the station config.
  155.     local index = 0
  156.     for i=1,tableSize(stationConfig.otherStations) do
  157.         local bc = colors.gray
  158.         if (i % 2 == 0) then
  159.             bc = colors.lightGray
  160.         end
  161.         drawLine(i+1, bc)
  162.         writeToScreen(stationConfig.otherStations[i][2], 2, i+1, colors.white, bc)
  163.     end
  164. end
  165.  
  166. --Draws wrapped text.
  167. function drawWrappedText(text, x, y, width, height, tc, bc)
  168.     drawBox(x,y,width,height,bc)
  169.     local t = wrap(text, width)
  170.     if (tableSize(t) > height) then
  171.         logMsg("WARNING", "Text is too large to wrap in given bounds.")
  172.     end
  173.     for i,line in ipairs(t) do
  174.         writeToScreen(line, x, y + i - 1, tc, bc)
  175.     end
  176. end
  177.  
  178. --Draws an alert which disappears in a small time.
  179. function prompt(text, delay, tc, bc)
  180.     local sizeX, sizeY = monitor.getSize()
  181.     drawBox(3,3,sizeX-6,sizeY-1, bc)
  182.     drawWrappedText(text,4,4,sizeX-8,sizeY-2, tc, bc)
  183.     promptTimer = os.startTimer(delay)
  184.     inPrompt = true
  185. end
  186.  
  187. --Beep the noteblock to indicate the track on which a train is being dispatched.
  188. function beep(count)
  189.     for i=1,count do
  190.         noteBlock.triggerNote()
  191.         os.sleep(0.5)
  192.     end
  193. end
  194.  
  195. --Updates the station file from pastebin.
  196. function updateStationFile()
  197.     prompt("Updating station software. Please wait while the system restarts. Thank you.",
  198.     5,
  199.     colors.yellow,
  200.     colors.white)
  201.     logMsg("INFO", "Fetching update from pastebin.")
  202.     fs.delete("station")
  203.     shell.run("pastebin", "get", pastebinURL, "station")
  204.     logMsg("INFO", "System will now reboot.")
  205.     --Send a message to the train admin.
  206.     rednet.broadcast(stationConfig.dest.." has updated.", trainAdminProt)
  207.     os.sleep(3)
  208.     os.reboot()
  209. end
  210.  
  211. --Request a train from a station which has more than 1 train.
  212. function requestTrain()
  213.     for i=1,tableSize(stationConfig.otherStations) do
  214.         if (stationConfig.otherStations[i][3] > 1) then
  215.             rednet.send(stationConfig.otherStations[i][4], stationConfig.dest, trainDispatchProt)
  216.             logMsg("INFO", "Sent request to "..stationConfig.otherStations[i][1].." for a train.")
  217.             return
  218.         end
  219.     end
  220.     logMsg("WARNING", "Unable to find a station with an extra train, waiting 1 min before next attempt.")
  221.     requestTimer = os.startTimer(60)
  222. end
  223.  
  224. --Dispatches a train from a track, by sending a ticket to it.
  225. function dispatchTrain(destination, trainCountAtDest, isPlayer)
  226.     --Iterate until an occupied track is found.
  227.     for i=1,tableSize(stationConfig.tracks) do
  228.         if (stationConfig.tracks[i][1]) then
  229.             local success = ticketMachine.createTicket(destination,1)
  230.             if (not success) then
  231.                 logMsg("ERROR", "Unable to create ticket.")
  232.                 prompt("Unable to create ticket. Please check that the ticket machine has ink and paper.",
  233.                 5,
  234.                 colors.red,
  235.                 colors.white)
  236.                 return false
  237.             end
  238.             logMsg("INFO_HIGH", "Train on track "..i.." will be dispatched to "..destination..".")
  239.             if (isPlayer) then
  240.                 prompt("Train to "..getCosmeticName(destination).." leaves on track "..i..". Have a pleasant journey!",
  241.                     5,
  242.                     colors.green,
  243.                     colors.white)
  244.             end
  245.             beep(i)
  246.             redstone.setBundledOutput("top", stationConfig.tracks[i][2])
  247.             os.sleep(2)
  248.             redstone.setBundledOutput("top", 0)
  249.             return true
  250.         end
  251.     end
  252.     logMsg("ERROR", "Unable to find train to dispatch.")
  253.     prompt("Error, unable to find train to dispatch. Please ensure that the system has n+1 trains, where n = number of stations.",
  254.         5,
  255.         colors.red,
  256.         colors.white)
  257.     return false
  258. end
  259.  
  260. --Broadcasts the station's name, destination, and train count to all other stations.
  261. function broadcastMetadata()
  262.     rednet.broadcast({stationConfig.dest,stationConfig.name,stationConfig.trainCount}, metadataProt)
  263.     --logMsg("INFO_DETAIL", "Broadcasted metadata.")
  264. end
  265.  
  266. --Scans redstone inputs from each track.
  267. --  Updates tracks table, and train count, then broadcasts.
  268. function scanTracks()
  269.     local trainCount = 0
  270.     local changed = false
  271.     for i=1,tableSize(stationConfig.tracks) do
  272.         local previous = stationConfig.tracks[i][1]
  273.         stationConfig.tracks[i][1] = colors.test(redstone.getBundledInput("top"), stationConfig.tracks[i][3])
  274.         if (stationConfig.tracks[i][1]) then
  275.             trainCount = trainCount + 1
  276.         end
  277.         if (previous ~= stationConfig.tracks[i][1]) then
  278.             changed = true
  279.         end
  280.         --Test if a train has arrived, and provide a welcome message.
  281.         if (not previous and stationConfig.tracks[i][1]) then
  282.             prompt("Welcome to "..stationConfig.name..". Thank you for using HandieBahn!",
  283.             5,
  284.             colors.blue,
  285.             colors.white)
  286.         end
  287.     end
  288.     stationConfig.trainCount = trainCount
  289.     if (trainCount < 1) then
  290.         requestTrain()
  291.     end
  292.     if (changed) then
  293.         broadcastMetadata()
  294.     end
  295.     saveConfig()
  296.     --logMsg("INFO_DETAIL", "Tracks scanned.")
  297. end
  298.  
  299. --Determines if occupancy of the tracks is changed according to config.
  300. function tracksChanged()
  301.     for i=1,tableSize(stationConfig.tracks) do
  302.         local previous = stationConfig.tracks[i][1]
  303.         local current = colors.test(redstone.getBundledInput("top"), stationConfig.tracks[i][3])
  304.         if (previous ~= current) then
  305.             --logMsg("INFO_DETAIL", "Track occupancy has changed.")
  306.             return true
  307.         end
  308.     end
  309.     return false
  310. end
  311.  
  312. --What to do when a redstone event is triggered.
  313. function onRedstoneEvent()
  314.     if (tracksChanged()) then
  315.         scanTracks()
  316.     end
  317. end
  318.  
  319. --Saves a station's metadata into the stationConfig file.
  320. function saveStation(dest, name, trainCount, compID)
  321.     --First determine if the station already exists in the file.
  322.     local index = 0
  323.     for k,v in pairs(stationConfig.otherStations) do
  324.         if (v[1] == dest) then
  325.             index = k
  326.             break
  327.         end
  328.     end
  329.     if (index ~= 0) then
  330.         stationConfig.otherStations[index] = {dest, name, trainCount, compID}
  331.     else
  332.         table.insert(stationConfig.otherStations, {dest, name, trainCount, compID})
  333.     end
  334.     saveConfig()
  335. end
  336.  
  337. --What to do when a rednet message is received.
  338. function onRednetMessage(id, msg, prot)
  339.     --If the computer is receiving metadata from another station.
  340.     if (prot == metadataProt or (prot == metadataRequestProt and msg ~= "REQUEST")) then
  341.         saveStation(msg[1], msg[2], msg[3], id)
  342.         logMsg("INFO", "Received metadata from station: "..msg[1])
  343.         if not inPrompt then drawMonitor() end
  344.     --If another computer has requested this computer's metadata.
  345.     elseif (prot == metadataRequestProt and msg == "REQUEST") then
  346.         logMsg("INFO", "Computer "..id.." has requested metadata. Transmitting.")
  347.         rednet.send(id, {stationConfig.dest,stationConfig.name,stationConfig.trainCount}, metadataRequestProt)
  348.     --If another computer has requested a train to be dispatched to a location.
  349.     elseif (prot == trainDispatchProt) then
  350.         logMsg("INFO", "Train requested at: "..msg)
  351.         dispatchTrain(msg, 0, false)
  352.     --If an administrator has sent a message to the station.
  353.     elseif (prot == trainAdminProt) then
  354.         if (msg == "UPDATE") then
  355.             updateStationFile()
  356.         end
  357.     end
  358. end
  359.  
  360. --What to do when the monitor is touched.
  361. function onMonitorTouch(x, y)
  362.     local stationIndex = y - 1 + stationsOffset
  363.     local sizeX,sizeY = monitor.getSize()
  364.     --Test first if the user clicks while a prompt is shown, then it will close that.
  365.     if (inPrompt) then
  366.         os.queueEvent("timer", promptTimer)
  367.         return
  368.     end
  369.     --Test if the user clicked on a station's name.
  370.     if (y > 1) then
  371.         --Make sure that a station exists first.
  372.         if (stationIndex <= tableSize(stationConfig.otherStations)) then
  373.             local selectedStation = stationConfig.otherStations[stationIndex]
  374.             --logMsg("INFO", "User selected station: "..selectedStation[1])
  375.             dispatchTrain(selectedStation[1], selectedStation[3], true)
  376.         end  
  377.     --The user may have clicked the help button.
  378.     elseif (x >= sizeX-3) then
  379.         --The user has requested help.
  380.         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.",
  381.         10,
  382.         colors.black,
  383.         colors.white)
  384.     end
  385. end
  386.  
  387. --What to do when a timer completes.
  388. function onTimerEvent(timerId)
  389.     --If the timer for the pop-up prompt on screen goes off.
  390.     if (timerId == promptTimer) then
  391.         drawMonitor()
  392.         inPrompt = false
  393.     --Timer for checking if it's possible to dispatch a train to a requested destination.
  394.     elseif (timerId == requestTimer) then
  395.         scanTracks()
  396.     end
  397. end
  398.  
  399. --Handles all incoming events.
  400. function eventHandler()
  401.     local event, p1, p2, p3, p4, p5 = os.pullEvent()
  402.     --If a redstone signal changes, then do something.
  403.     if (event == "redstone") then
  404.         onRedstoneEvent()
  405.     --If the monitor is touched, pass the x and y coordinates to a function.
  406.     elseif (event == "monitor_touch") then
  407.         onMonitorTouch(p2, p3)
  408.     --If a rednet message is received, pass the ID, message, and protocol to a function.
  409.     elseif (event == "rednet_message") then
  410.         onRednetMessage(p1, p2, p3)
  411.     elseif (event == "timer") then
  412.         onTimerEvent(p1)
  413.     end
  414. end
  415.  
  416. --Sets up the config for the first time, and returns the resulting table.
  417. function firstTimeSetup()
  418.     term.setTextColor(colors.lightBlue)
  419.     logMsg("WARNING", "No config file found. Starting first-time setup...")
  420.     local config = {
  421.         tracks={},
  422.         otherStations={},
  423.     }
  424.     term.setTextColor(colors.white)
  425.     print("  What is the number of the monitor connected to this station?")
  426.     config.monitor_id = "monitor_"..read()
  427.     print("  What is the number of the note block connected to this station?")
  428.     config.note_block_id = "note_block_"..read()
  429.     print("  What is the cosmetic name of this station?")
  430.     config.name = read()
  431.     print("  What is the railcraft destination for this station?")
  432.     config.dest = read()
  433.     print("  How many tracks does this station have?")
  434.     local trackCount = tonumber(read())
  435.     for i=1,trackCount do
  436.         config.tracks[i] = {}
  437.         config.tracks[i][1] = false
  438.         print("    What is track "..i.."'s dispatch decimal color code?")
  439.         config.tracks[i][2] = tonumber(read())
  440.         print("    What is track "..i.."'s detection decimal color code?")
  441.         config.tracks[i][3] = tonumber(read())
  442.     end
  443.     local f = fs.open(configFile, "w")
  444.     f.write(textutils.serialize(config))
  445.     f.close()
  446.     term.setTextColor(colors.lime)
  447.     print("Setup is complete.")
  448.     term.setTextColor(colors.white)
  449.     return config
  450. end
  451.  
  452. --Loads config from a file, or if there is none, create it from user input.
  453. function loadConfig()
  454.     if (not fs.exists(configFile)) then
  455.         return firstTimeSetup()
  456.     else
  457.         local f = fs.open("station_config", "r")
  458.         local config = textutils.unserialize(f.readAll())
  459.         f.close()
  460.         return config
  461.     end    
  462. end
  463.  
  464. --Perform things before the program begins listening for input.
  465. function setup()
  466.     logMsg("INFO_HIGH", "Station Version: "..VERSION)
  467.     stationConfig = loadConfig()
  468.     --Setup peripherals
  469.     rednet.open("bottom")
  470.     ticketMachine = peripheral.wrap("back")
  471.     noteBlock = peripheral.wrap(stationConfig.note_block_id)
  472.     monitor = peripheral.wrap(stationConfig.monitor_id)
  473.     --Broadcast metadata to all other stations.
  474.     scanTracks()
  475.     --Broadcast a request message to other stations to get their information.
  476.     rednet.broadcast("REQUEST", metadataRequestProt)
  477.     --Draw onto the monitor for the first time.
  478.     drawMonitor()
  479. end
  480.  
  481. --Main loop
  482. setup()
  483. while (true) do
  484.     eventHandler()
  485. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement