Advertisement
hnor

Elevator Control for Minecraft

Jun 5th, 2013
3,104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.86 KB | None | 0 0
  1. --[[
  2. Elevator Control by Hamish Robertson
  3. Aliases: OminousPenguin, MC username: Peren
  4.  
  5. Thanks to Jonnathon Clayton for identifying a replacement for Rednet Cables (Project Red)
  6.  
  7. Licence: Creative Commons Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/)
  8.  
  9. This is a LUA program for ComputerCraft, a Minecraft mod by Daniel Ratcliffe aka dan200 (http://www.computercraft.info/)
  10. This program is for controlling elevators from the Railcraft mod (http://www.railcraft.info/)
  11. For help and feedback please use this forum thread: http://www.computercraft.info/forums2/index.php?/topic/1302-railcraft-mod-elevator-control/
  12.  
  13. The following four variables can be used to specify details of the wiring you have used:
  14. --]]
  15.  
  16. bundleSide = "bottom" -- The side of the computer to which the bundled cable is attached
  17. elevatorWire = "purple" -- Colour of insulated wire from bundled cable to back of elevator track. Purple = 10th color on rednet cable
  18. detetctorWire = "white" -- Colour of insulated wire from bundled cable to cart detector. White = default color on rednet cable
  19. boardingWire = "lime" -- Colour of insulated wire from bundled cable to boarding rail. Lime = 5th color on rednet cable
  20.  
  21. --[[
  22. Use Ctrl+T to terminate the program.
  23.  
  24. You do not need to change anything below this point.
  25.  
  26. You are permitted to modify and or build upon this work.
  27. If you make your own version publicly available, then please also publish it at the above forum thread. Thank you and happy coding!
  28. --]]
  29.  
  30. version = "2.3.1"
  31.  
  32. function writeToPos(x, y, str)
  33.     term.setCursorPos(x, y)
  34.     term.write(str)
  35. end
  36.  
  37. function updateMenu()
  38.     term.clear()
  39.     local selected = 1
  40.     writeToPos(4, 2, "Current version: "..version)
  41.     writeToPos(5, 3, "Latest version: "..latestVersion)
  42.     writeToPos(4, 5, "Please select one of the following options:")
  43.     writeToPos(6, 7, "[x] Update all elevators within range")
  44.     writeToPos(6, 8, " x  Update this elevator")
  45.     writeToPos(1, 19, "Arrow keys change selection    Backspace to go back")   
  46.  
  47.     local function changeSelection(previousSelection)
  48.         writeToPos(6,6+previousSelection," x ")
  49.         writeToPos(6,6+selected,"[x]")
  50.     end
  51.  
  52.     local keyHandlers = {
  53.         [200] = function () -- up arrow
  54.             if selected == 2 then
  55.                 selected = 1
  56.                 changeSelection(2)
  57.             end
  58.         end,
  59.  
  60.         [208] = function () -- down arrow
  61.             if selected == 1 then
  62.                 selected = 2
  63.                 changeSelection(1)
  64.             end
  65.         end,
  66.  
  67.         [28] = function () -- enter/return
  68.             transmit(CHANNEL_ELEVATOR, os.getComputerID(), "UPDATE", (selected == 1))
  69.             doUpdate()
  70.         end,
  71.  
  72.         [14] = function () -- backspace
  73.             return true
  74.         end
  75.     }
  76.  
  77.     while true do
  78.         _, keycode = os.pullEvent("key")
  79.         f = keyHandlers[keycode]
  80.         if type(f) == "function" then
  81.             if f() then return end
  82.         end
  83.     end
  84. end -- updateMenu()
  85.  
  86. function doUpdate()
  87.     if updateAvailable then
  88.         if shell.run("pastebin", "get", "iJWyUQVr", "elevator-main-update.lua") then
  89.             fs.delete("elevator-main.lua")
  90.             fs.move("elevator-main-update.lua", "elevator-main.lua")
  91.             os.reboot()
  92.         end
  93.     end
  94. end
  95.  
  96. function unserialise(s)
  97.     local t = {}
  98.     t.heights = {}
  99.     mt = {
  100.         -- The table t uses the y coord as it's indices which are therefore not contiguous and so the retrevial order can not be relied upon.
  101.         -- The 'heights' table is a contiguously indexed array whos values are the y coords of each floor. This enables us to sort the floors into (reverse) height order.
  102.         __newindex = function(t,k,v)
  103.             t.heights[#t.heights+1] = k -- k is the height coordinate of the floor
  104.             rawset(t,k,v)
  105.         end
  106.     }
  107.     setmetatable(t, mt)
  108.     for u in string.gmatch(s, "[^\030]+") do
  109.         local iter = string.gmatch(u, "([^\031]+)")
  110.         t[tonumber(iter())] = iter()
  111.     end
  112.     return t
  113. end
  114.  
  115. function serialise(t)
  116.     local s = ""
  117.     for i=1,#t.heights do
  118.         s = s..t.heights[i].."\031"..t[t.heights[i]].."\030"
  119.     end
  120.     return s
  121. end
  122.  
  123. function addFloor(sMessage)
  124.     local iter = string.gmatch(sMessage, "([^\031]+)")
  125.     local y,label = tonumber(iter()),iter()
  126.     floors[y] = label
  127.     newFloorTimer = os.startTimer(3)
  128. end
  129.  
  130. function sortReverse(t)
  131.     table.sort(t.heights, function (a,b) return (a > b) end)
  132.     for i=1,#t.heights do
  133.         if t.heights[i] == y then
  134.             t.heights.y = i; break -- t.heights.y stores the index of the current floor in the t.heights table
  135.             -- t.heights[t.heights.y] would give the y coordinate of the current floor
  136.             -- t[t.heights[t.heights.y]] would give the text label of the current floor
  137.         end
  138.     end
  139. end
  140.  
  141. function transmit(sChannel, sReplyChannel, sMessage, tBroadcast)
  142.     -- tBroadcast optionaly specifies that "ALL" should be used instead of the elevator ID
  143.     -- Messages are sent in this format: "ELEV" elevID/"ALL" messageID [messageBody]
  144.     modem.close(sChannel)
  145.     modem.transmit(sChannel, sReplyChannel, "ELEV\030"..(tBroadcast and "ALL" or elevID).."\030"..sMessage)
  146.     modem.open(sChannel)
  147. end
  148.  
  149. function pause()
  150.     print ("Press any key to continue...")
  151.     os.pullEvent("key")
  152. end
  153.  
  154. eventHandlers = {
  155.     modemFunctions = {
  156.         ["DISCOVER"] = function (sReplyChannel)
  157.         -- Respond to elevator ID discovery request
  158.             transmit(sReplyChannel, CHANNEL_ELEVATOR, "ID") -- No need for broadcast flag as the code in setup.lua doesn't filter based on elevID
  159.         end,
  160.  
  161.         --["ID"] This function doesn't exist here as these messages are only for the setup program
  162.  
  163.         ["ANNOUNCE"] = function (sReplyChannel, sMessage)
  164.         -- Received new floor announcement broadcast
  165.             -- Add floor to local table
  166.             addFloor(sMessage)
  167.             -- Send reply of own y and label
  168.             transmit(sReplyChannel, CHANNEL_ELEVATOR, "REPLY\030"..y.."\031"..floors[y])
  169.         end,
  170.  
  171.         ["REPLY"] = function (_, sMessage)
  172.         -- Received a reply to own announcement
  173.             addFloor(sMessage)
  174.         end,
  175.  
  176.         ["ACTIVATE"] = function (sReplyChannel, sMessage)
  177.         -- Elevator activation message from another floor
  178.             acceptInput = false
  179.             -- Check if this floor is the destination
  180.             if tonumber(sMessage) == y then
  181.                 ignoreDetector = false
  182.                 redstone.setBundledOutput(bundleSide, colors[elevatorWire])
  183.                 term.clear()
  184.                 writeToPos(19,7,"Incoming cart")
  185.                 writeToPos(15,9,"Please clear the track")
  186.             else
  187.                 displayBusy()
  188.             end
  189.         end,
  190.  
  191.         ["CALL"] = function ()
  192.         -- Received cart call message
  193.             acceptInput = false
  194.             displayBusy()
  195.             -- Pulse the boarding rail to send any cart that might be on it
  196.             redstone.setBundledOutput(bundleSide, colors[boardingWire])
  197.             sleep(1)
  198.             redstone.setBundledOutput(bundleSide, 0)
  199.         end,
  200.  
  201.         ["CLEAR"] = function ()
  202.         -- Recaived notifcation that a cart has arrived at another floor (and so the elevator is probably clear for use)
  203.             if departedTimer then departedTimer = nil end
  204.             acceptInput = true
  205.             renderFloorList(true)
  206.         end,
  207.        
  208.         ["UPDATE"] = function (sReplyChannel, _, eID)
  209.             if not ignoreUpdates then
  210.                 ignoreUpdates = true
  211.                 ignoreUpdateTimer = os.startTimer(10)
  212.                 transmit(CHANNEL_ELEVATOR, sReplyChannel, "UPDATE", eID == "ALL")
  213.                 doUpdate()
  214.             end
  215.         end
  216.     },
  217.    
  218.     modem_message =
  219.         function (_, sChannel, sReplyChannel, sMessage)
  220.             if sChannel == CHANNEL_ELEVATOR or sChannel == os.getComputerID() then
  221.                 local iter = string.gmatch(sMessage, "([^\030]+)")
  222.                 if iter() ~= "ELEV" then return end -- Right channel but not meant for us
  223.                 local eID = iter()
  224.                 if eID == elevID or eID == "ALL" then -- Correct elevID or message is for all elevators
  225.                     local f = eventHandlers.modemFunctions[iter()]
  226.                     if type(f) == "function" then
  227.                         return f(sReplyChannel, iter(), eID) -- iter() = rest of the message
  228.                     end
  229.                 end
  230.  
  231.             -- Reply to GPS ping if we know our full coords
  232.             elseif z and sChannel == gps.CHANNEL_GPS and sMessage == "PING" then
  233.                 modem.transmit(sReplyChannel, gps.CHANNEL_GPS, textutils.serialize({x,y,z}))
  234.             end
  235.         end,
  236.  
  237.     key =
  238.         function (keycode)
  239.             if acceptInput then
  240.                 if (keycode == 200) then -- up
  241.                     if selected > 1 then
  242.                         if selected-1 == floors.heights.y then
  243.                             if selected > 2 then selected = selected-2 end
  244.                         else
  245.                             selected = selected-1
  246.                         end
  247.                         renderFloorList()
  248.                     end
  249.                 elseif (keycode == 208) then -- down
  250.                     if selected < #floors.heights then
  251.                         if selected+1 == floors.heights.y then
  252.                             if selected < #floors.heights-1 then selected = selected+2 end
  253.                         else
  254.                             selected = selected+1
  255.                         end
  256.                         renderFloorList()
  257.                     end
  258.                 elseif (keycode == 57) then -- Space
  259.                     ignoreDetector = false
  260.                     acceptInput = false
  261.                     redstone.setBundledOutput(bundleSide, colors[elevatorWire])
  262.                     transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CALL")
  263.                     term.clear()
  264.                     writeToPos(20,8,"Cart called")
  265.                 elseif keycode == 28 and selected ~= floors.heights.y then -- enter
  266.                     transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ACTIVATE\030"..floors.heights[selected])
  267.                     redstone.setBundledOutput(bundleSide, colors[boardingWire])
  268.                     acceptInput = false
  269.                     term.clear()
  270.                     writeToPos(20,8,"Bon Voyage")
  271.                     writeToPos(20,10,"Press Esc")
  272.                     sleep(1)
  273.                     redstone.setBundledOutput(bundleSide, 0)
  274.                     departedTimer = os.startTimer(2)
  275.                 elseif keycode == 22 then -- U for update menu
  276.                     updateMenu()
  277.                     renderFloorList()
  278.                 end
  279.             end
  280.         end,
  281.  
  282.     timer =
  283.         function (timerID)
  284.             if timerID == newFloorTimer then
  285.                 newFloorTimer = nil
  286.                 sortReverse(floors)
  287.                 local file = io.open("elevator.cfg", "w")
  288.                 file:write(modemSide.."\n")
  289.                 file:write(elevID.."\n")
  290.                 file:write(coords.."\n")
  291.                 file:write(serialise(floors).."\n")
  292.                 file:write(bundledConnector)
  293.                 file:close()
  294.                 renderFloorList(true)
  295.             elseif timerID == departedTimer then
  296.                 departedTimer = nil
  297.                 displayBusy()
  298.             elseif timerID == ignoreUpdateTimer then
  299.                 ignoreUpdates = false
  300.             end
  301.         end,
  302.  
  303.     redstone =
  304.         function()
  305.             if ignoreDetector == false and colors.test(redstone.getBundledInput(bundleSide), colors[detetctorWire]) then
  306.                 ignoreDetector = true
  307.                 transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CLEAR")
  308.                 redstone.setBundledOutput(bundleSide, 0)
  309.                 renderFloorList(true)
  310.             end
  311.         end,
  312.    
  313.     handle =
  314.         function (self, f, ...)
  315.             f = self[f]
  316.             if type(f) == "function" then return f(...) end
  317.         end
  318. }
  319.  
  320. function displayBusy()
  321.     term.clear()
  322.     writeToPos(19,7,"Elevator busy")
  323.     writeToPos(20,9,"Please wait")
  324. end
  325.  
  326. function renderFloorList(reset)
  327.     if reset then selected = floors.heights.y end
  328.     term.clear()
  329.     if updateAvailable then
  330.         writeToPos(30,1,"New version available!")
  331.         writeToPos(33,2,"Press U for options")
  332.     end
  333.     local startIndex, line = 1, 1
  334.     if selected > 9 then
  335.         startIndex = selected - 8
  336.     else
  337.         line = 10 - selected
  338.     end
  339.    
  340.     for i=startIndex,#floors.heights do
  341.         term.setCursorPos(13-(tostring(floors.heights[i]):len()),line)
  342.         term.write(floors.heights[i]..": "..floors[floors.heights[i]])
  343.         line = line + 1
  344.         if line == 17 then break end -- If we get to the bottom of the screen, stop printing floors
  345.     end
  346.  
  347.     if selected ~= floors.heights.y then
  348.         midMarkL, midMarkR = ">", "<"
  349.         line = 9 - (selected - floors.heights.y) -- Eg we're on 20, selected is 19: 19 is now at line 9, 20 is at 10 which is 9 - (-1)
  350.         writeToPos(8,line,"-")
  351.         writeToPos(42,line,"-")
  352.     else
  353.         midMarkL, midMarkR = "-", "-"
  354.     end
  355.     writeToPos(8,9,midMarkL)
  356.     writeToPos(42,9,midMarkR)
  357.  
  358.     writeToPos(2,18,"Up/Down arrow keys to select destination")
  359.     writeToPos(2,19,"Press Enter to activate         Space to call cart")
  360.     acceptInput = true;
  361. end -- renderFloorList()
  362.  
  363. function initialise()
  364.     response = http.get("http://pastebin.com/raw.php?i=r3mt8mDD")
  365.     if response then
  366.         latestVersion = response.readLine()
  367.         response.close()
  368.         if latestVersion ~= version then
  369.             updateAvailable = true
  370.         end
  371.     end
  372.  
  373.     CHANNEL_ELEVATOR = 34080
  374.     acceptInput, ignoreDetector, newFloorTimer, departedTimer, ignoreUpdates, ignoreUpdateTimer = false, true, nil, nil, false, nil
  375.     dir = shell.getRunningProgram(); i = string.find(dir, "/")
  376.     if i then
  377.         config = dir:sub(1,i).."/elevator.cfg"
  378.     else
  379.         config = "elevator.cfg"
  380.     end
  381.     file = assert(io.open(config, "r"), "Failed to open "..config.."\nRun setup.lua first")
  382.     modemSide,elevID,coords,floors,bundledConnector = file:read("*l"),file:read("*l"),file:read("*l"),file:read("*l"),file:read("*l");file:close()
  383.  
  384.     if (bundledConnector == nil) then
  385.         term.clear()
  386.         writeToPos(3, 4, "Are you using the Bundled Connector")
  387.         writeToPos(6, 6, "from the Extra Peripherals mod? [Y/n] ")
  388.         bundledConnector = string.lower(string.sub(io.read(),1,1))
  389.         if (bundledConnector=='y') then
  390.             bundledConnector = 'bundledConnector:yes'
  391.         else
  392.             bundledConnector = 'bundledConnector:no'
  393.         end
  394.         local file = io.open("elevator.cfg", "w")
  395.         file:write(modemSide.."\n")
  396.         file:write(elevID.."\n")
  397.         file:write(coords.."\n")
  398.         file:write(floors.."\n")
  399.         file:write(bundledConnector)
  400.         file:close()
  401.     end
  402.  
  403.     iter = string.gmatch(coords, "([^\031]+)") -- create iterator
  404.     x,y,z = tonumber(iter()),tonumber(iter()),tonumber(iter());iter = nil
  405.     modem = peripheral.wrap(modemSide)
  406.  
  407.     if (bundledConnector=='bundledConnector:yes') then
  408.         redstone = peripheral.wrap(bundleSide)
  409.         rs = redstone
  410.     end
  411.  
  412.     if z ~= nil then
  413.         print("Starting GPS host")
  414.         if not modem.isOpen(gps.CHANNEL_GPS) then
  415.             print("Opening GPS channel on "..modemSide.." modem")
  416.             modem.open(gps.CHANNEL_GPS)
  417.         end
  418.     else
  419.         y = x; x = nil
  420.     end
  421.  
  422.     floors = unserialise(floors)
  423.     sortReverse(floors)
  424.  
  425.     -- Announce self to other floors
  426.     modem.open(os.getComputerID())
  427.     transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ANNOUNCE\030"..y.."\031"..floors[y])
  428.  
  429.     selected = floors.heights.y
  430.  
  431.     if #floors.heights == 1 then
  432.         -- This floor only knows about it's self
  433.         term.clear()
  434.         writeToPos(7,4,"No other floors discovered")
  435.         writeToPos(7,6,"Once the elevator program is started")
  436.         writeToPos(7,7,"on other floors, they will appear here.")
  437.     else
  438.         renderFloorList()
  439.     end
  440.  
  441.     while true do
  442.         eventHandlers:handle(os.pullEvent())
  443.     end
  444. end
  445.  
  446. assert(pcall(initialise), "An error occured. Please run setup again (pastebin get VYwxa3su setup)")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement