Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Elevator Control by Hamish Robertson
- Aliases: OminousPenguin, MC username: Peren
- Thanks to Jonnathon Clayton for identifying a replacement for Rednet Cables (Project Red)
- Licence: Creative Commons Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/)
- This is a LUA program for ComputerCraft, a Minecraft mod by Daniel Ratcliffe aka dan200 (http://www.computercraft.info/)
- This program is for controlling elevators from the Railcraft mod (http://www.railcraft.info/)
- For help and feedback please use this forum thread: http://www.computercraft.info/forums2/index.php?/topic/1302-railcraft-mod-elevator-control/
- The following four variables can be used to specify details of the wiring you have used:
- --]]
- bundleSide = "bottom" -- The side of the computer to which the bundled cable is attached
- elevatorWire = "purple" -- Colour of insulated wire from bundled cable to back of elevator track. Purple = 10th color on rednet cable
- detetctorWire = "white" -- Colour of insulated wire from bundled cable to cart detector. White = default color on rednet cable
- boardingWire = "lime" -- Colour of insulated wire from bundled cable to boarding rail. Lime = 5th color on rednet cable
- --[[
- Use Ctrl+T to terminate the program.
- You do not need to change anything below this point.
- You are permitted to modify and or build upon this work.
- If you make your own version publicly available, then please also publish it at the above forum thread. Thank you and happy coding!
- --]]
- version = "2.3.1"
- function writeToPos(x, y, str)
- term.setCursorPos(x, y)
- term.write(str)
- end
- function updateMenu()
- term.clear()
- local selected = 1
- writeToPos(4, 2, "Current version: "..version)
- writeToPos(5, 3, "Latest version: "..latestVersion)
- writeToPos(4, 5, "Please select one of the following options:")
- writeToPos(6, 7, "[x] Update all elevators within range")
- writeToPos(6, 8, " x Update this elevator")
- writeToPos(1, 19, "Arrow keys change selection Backspace to go back")
- local function changeSelection(previousSelection)
- writeToPos(6,6+previousSelection," x ")
- writeToPos(6,6+selected,"[x]")
- end
- local keyHandlers = {
- [200] = function () -- up arrow
- if selected == 2 then
- selected = 1
- changeSelection(2)
- end
- end,
- [208] = function () -- down arrow
- if selected == 1 then
- selected = 2
- changeSelection(1)
- end
- end,
- [28] = function () -- enter/return
- transmit(CHANNEL_ELEVATOR, os.getComputerID(), "UPDATE", (selected == 1))
- doUpdate()
- end,
- [14] = function () -- backspace
- return true
- end
- }
- while true do
- _, keycode = os.pullEvent("key")
- f = keyHandlers[keycode]
- if type(f) == "function" then
- if f() then return end
- end
- end
- end -- updateMenu()
- function doUpdate()
- if updateAvailable then
- if shell.run("pastebin", "get", "iJWyUQVr", "elevator-main-update.lua") then
- fs.delete("elevator-main.lua")
- fs.move("elevator-main-update.lua", "elevator-main.lua")
- os.reboot()
- end
- end
- end
- function unserialise(s)
- local t = {}
- t.heights = {}
- mt = {
- -- 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.
- -- 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.
- __newindex = function(t,k,v)
- t.heights[#t.heights+1] = k -- k is the height coordinate of the floor
- rawset(t,k,v)
- end
- }
- setmetatable(t, mt)
- for u in string.gmatch(s, "[^\030]+") do
- local iter = string.gmatch(u, "([^\031]+)")
- t[tonumber(iter())] = iter()
- end
- return t
- end
- function serialise(t)
- local s = ""
- for i=1,#t.heights do
- s = s..t.heights[i].."\031"..t[t.heights[i]].."\030"
- end
- return s
- end
- function addFloor(sMessage)
- local iter = string.gmatch(sMessage, "([^\031]+)")
- local y,label = tonumber(iter()),iter()
- floors[y] = label
- newFloorTimer = os.startTimer(3)
- end
- function sortReverse(t)
- table.sort(t.heights, function (a,b) return (a > b) end)
- for i=1,#t.heights do
- if t.heights[i] == y then
- t.heights.y = i; break -- t.heights.y stores the index of the current floor in the t.heights table
- -- t.heights[t.heights.y] would give the y coordinate of the current floor
- -- t[t.heights[t.heights.y]] would give the text label of the current floor
- end
- end
- end
- function transmit(sChannel, sReplyChannel, sMessage, tBroadcast)
- -- tBroadcast optionaly specifies that "ALL" should be used instead of the elevator ID
- -- Messages are sent in this format: "ELEV" elevID/"ALL" messageID [messageBody]
- modem.close(sChannel)
- modem.transmit(sChannel, sReplyChannel, "ELEV\030"..(tBroadcast and "ALL" or elevID).."\030"..sMessage)
- modem.open(sChannel)
- end
- function pause()
- print ("Press any key to continue...")
- os.pullEvent("key")
- end
- eventHandlers = {
- modemFunctions = {
- ["DISCOVER"] = function (sReplyChannel)
- -- Respond to elevator ID discovery request
- transmit(sReplyChannel, CHANNEL_ELEVATOR, "ID") -- No need for broadcast flag as the code in setup.lua doesn't filter based on elevID
- end,
- --["ID"] This function doesn't exist here as these messages are only for the setup program
- ["ANNOUNCE"] = function (sReplyChannel, sMessage)
- -- Received new floor announcement broadcast
- -- Add floor to local table
- addFloor(sMessage)
- -- Send reply of own y and label
- transmit(sReplyChannel, CHANNEL_ELEVATOR, "REPLY\030"..y.."\031"..floors[y])
- end,
- ["REPLY"] = function (_, sMessage)
- -- Received a reply to own announcement
- addFloor(sMessage)
- end,
- ["ACTIVATE"] = function (sReplyChannel, sMessage)
- -- Elevator activation message from another floor
- acceptInput = false
- -- Check if this floor is the destination
- if tonumber(sMessage) == y then
- ignoreDetector = false
- redstone.setBundledOutput(bundleSide, colors[elevatorWire])
- term.clear()
- writeToPos(19,7,"Incoming cart")
- writeToPos(15,9,"Please clear the track")
- else
- displayBusy()
- end
- end,
- ["CALL"] = function ()
- -- Received cart call message
- acceptInput = false
- displayBusy()
- -- Pulse the boarding rail to send any cart that might be on it
- redstone.setBundledOutput(bundleSide, colors[boardingWire])
- sleep(1)
- redstone.setBundledOutput(bundleSide, 0)
- end,
- ["CLEAR"] = function ()
- -- Recaived notifcation that a cart has arrived at another floor (and so the elevator is probably clear for use)
- if departedTimer then departedTimer = nil end
- acceptInput = true
- renderFloorList(true)
- end,
- ["UPDATE"] = function (sReplyChannel, _, eID)
- if not ignoreUpdates then
- ignoreUpdates = true
- ignoreUpdateTimer = os.startTimer(10)
- transmit(CHANNEL_ELEVATOR, sReplyChannel, "UPDATE", eID == "ALL")
- doUpdate()
- end
- end
- },
- modem_message =
- function (_, sChannel, sReplyChannel, sMessage)
- if sChannel == CHANNEL_ELEVATOR or sChannel == os.getComputerID() then
- local iter = string.gmatch(sMessage, "([^\030]+)")
- if iter() ~= "ELEV" then return end -- Right channel but not meant for us
- local eID = iter()
- if eID == elevID or eID == "ALL" then -- Correct elevID or message is for all elevators
- local f = eventHandlers.modemFunctions[iter()]
- if type(f) == "function" then
- return f(sReplyChannel, iter(), eID) -- iter() = rest of the message
- end
- end
- -- Reply to GPS ping if we know our full coords
- elseif z and sChannel == gps.CHANNEL_GPS and sMessage == "PING" then
- modem.transmit(sReplyChannel, gps.CHANNEL_GPS, textutils.serialize({x,y,z}))
- end
- end,
- key =
- function (keycode)
- if acceptInput then
- if (keycode == 200) then -- up
- if selected > 1 then
- if selected-1 == floors.heights.y then
- if selected > 2 then selected = selected-2 end
- else
- selected = selected-1
- end
- renderFloorList()
- end
- elseif (keycode == 208) then -- down
- if selected < #floors.heights then
- if selected+1 == floors.heights.y then
- if selected < #floors.heights-1 then selected = selected+2 end
- else
- selected = selected+1
- end
- renderFloorList()
- end
- elseif (keycode == 57) then -- Space
- ignoreDetector = false
- acceptInput = false
- redstone.setBundledOutput(bundleSide, colors[elevatorWire])
- transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CALL")
- term.clear()
- writeToPos(20,8,"Cart called")
- elseif keycode == 28 and selected ~= floors.heights.y then -- enter
- transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ACTIVATE\030"..floors.heights[selected])
- redstone.setBundledOutput(bundleSide, colors[boardingWire])
- acceptInput = false
- term.clear()
- writeToPos(20,8,"Bon Voyage")
- writeToPos(20,10,"Press Esc")
- sleep(1)
- redstone.setBundledOutput(bundleSide, 0)
- departedTimer = os.startTimer(2)
- elseif keycode == 22 then -- U for update menu
- updateMenu()
- renderFloorList()
- end
- end
- end,
- timer =
- function (timerID)
- if timerID == newFloorTimer then
- newFloorTimer = nil
- sortReverse(floors)
- local file = io.open("elevator.cfg", "w")
- file:write(modemSide.."\n")
- file:write(elevID.."\n")
- file:write(coords.."\n")
- file:write(serialise(floors).."\n")
- file:write(bundledConnector)
- file:close()
- renderFloorList(true)
- elseif timerID == departedTimer then
- departedTimer = nil
- displayBusy()
- elseif timerID == ignoreUpdateTimer then
- ignoreUpdates = false
- end
- end,
- redstone =
- function()
- if ignoreDetector == false and colors.test(redstone.getBundledInput(bundleSide), colors[detetctorWire]) then
- ignoreDetector = true
- transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CLEAR")
- redstone.setBundledOutput(bundleSide, 0)
- renderFloorList(true)
- end
- end,
- handle =
- function (self, f, ...)
- f = self[f]
- if type(f) == "function" then return f(...) end
- end
- }
- function displayBusy()
- term.clear()
- writeToPos(19,7,"Elevator busy")
- writeToPos(20,9,"Please wait")
- end
- function renderFloorList(reset)
- if reset then selected = floors.heights.y end
- term.clear()
- if updateAvailable then
- writeToPos(30,1,"New version available!")
- writeToPos(33,2,"Press U for options")
- end
- local startIndex, line = 1, 1
- if selected > 9 then
- startIndex = selected - 8
- else
- line = 10 - selected
- end
- for i=startIndex,#floors.heights do
- term.setCursorPos(13-(tostring(floors.heights[i]):len()),line)
- term.write(floors.heights[i]..": "..floors[floors.heights[i]])
- line = line + 1
- if line == 17 then break end -- If we get to the bottom of the screen, stop printing floors
- end
- if selected ~= floors.heights.y then
- midMarkL, midMarkR = ">", "<"
- 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)
- writeToPos(8,line,"-")
- writeToPos(42,line,"-")
- else
- midMarkL, midMarkR = "-", "-"
- end
- writeToPos(8,9,midMarkL)
- writeToPos(42,9,midMarkR)
- writeToPos(2,18,"Up/Down arrow keys to select destination")
- writeToPos(2,19,"Press Enter to activate Space to call cart")
- acceptInput = true;
- end -- renderFloorList()
- function initialise()
- response = http.get("http://pastebin.com/raw.php?i=r3mt8mDD")
- if response then
- latestVersion = response.readLine()
- response.close()
- if latestVersion ~= version then
- updateAvailable = true
- end
- end
- CHANNEL_ELEVATOR = 34080
- acceptInput, ignoreDetector, newFloorTimer, departedTimer, ignoreUpdates, ignoreUpdateTimer = false, true, nil, nil, false, nil
- dir = shell.getRunningProgram(); i = string.find(dir, "/")
- if i then
- config = dir:sub(1,i).."/elevator.cfg"
- else
- config = "elevator.cfg"
- end
- file = assert(io.open(config, "r"), "Failed to open "..config.."\nRun setup.lua first")
- modemSide,elevID,coords,floors,bundledConnector = file:read("*l"),file:read("*l"),file:read("*l"),file:read("*l"),file:read("*l");file:close()
- if (bundledConnector == nil) then
- term.clear()
- writeToPos(3, 4, "Are you using the Bundled Connector")
- writeToPos(6, 6, "from the Extra Peripherals mod? [Y/n] ")
- bundledConnector = string.lower(string.sub(io.read(),1,1))
- if (bundledConnector=='y') then
- bundledConnector = 'bundledConnector:yes'
- else
- bundledConnector = 'bundledConnector:no'
- end
- local file = io.open("elevator.cfg", "w")
- file:write(modemSide.."\n")
- file:write(elevID.."\n")
- file:write(coords.."\n")
- file:write(floors.."\n")
- file:write(bundledConnector)
- file:close()
- end
- iter = string.gmatch(coords, "([^\031]+)") -- create iterator
- x,y,z = tonumber(iter()),tonumber(iter()),tonumber(iter());iter = nil
- modem = peripheral.wrap(modemSide)
- if (bundledConnector=='bundledConnector:yes') then
- redstone = peripheral.wrap(bundleSide)
- rs = redstone
- end
- if z ~= nil then
- print("Starting GPS host")
- if not modem.isOpen(gps.CHANNEL_GPS) then
- print("Opening GPS channel on "..modemSide.." modem")
- modem.open(gps.CHANNEL_GPS)
- end
- else
- y = x; x = nil
- end
- floors = unserialise(floors)
- sortReverse(floors)
- -- Announce self to other floors
- modem.open(os.getComputerID())
- transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ANNOUNCE\030"..y.."\031"..floors[y])
- selected = floors.heights.y
- if #floors.heights == 1 then
- -- This floor only knows about it's self
- term.clear()
- writeToPos(7,4,"No other floors discovered")
- writeToPos(7,6,"Once the elevator program is started")
- writeToPos(7,7,"on other floors, they will appear here.")
- else
- renderFloorList()
- end
- while true do
- eventHandlers:handle(os.pullEvent())
- end
- end
- assert(pcall(initialise), "An error occured. Please run setup again (pastebin get VYwxa3su setup)")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement