SHARE
TWEET

ElevatorControl Door 1.01

Rihlsul Mar 7th, 2013 452 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- Todo
  2.         -- handle no door
  3.         -- ensure we handle no display
  4.        
  5. local version = 1.01
  6.  
  7. local DoorConfig = {}
  8. local DoorScreen = {}
  9. local doorSettingsFile = "Door.Settings"
  10. local cabinPresent = false
  11.  
  12. local displayHandle = nil
  13.  
  14. --local floorMap = { }
  15.  
  16. local KeepWaiting = true
  17.  
  18.  
  19. -- ######################################################
  20. -- ##  Load the API                                    ##
  21. -- ######################################################
  22.  
  23. if fs.exists("/rom/apis/ECAPI") then --Check to make sure that the entered API exists
  24.   os.loadAPI("/rom/apis/ECAPI")
  25.   print("ECAPI loaded.")
  26. else
  27.   print("That API does not exist!")
  28. end
  29.  
  30. -- ######################################################
  31. -- ##  Setup Configuration                             ##
  32. -- ######################################################
  33.  
  34. local DefaultConfig = {}
  35. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Version",version,"Please do not change this.")
  36. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Network","","Elevator network name:")
  37. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Modem","","Which side is the modem?",'side')
  38. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Call","","Which side waits for call input?",'side')
  39. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Detect","","Which side detects the Cabin?",'side')
  40. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Display","","Which side is the Display monitor?",'side')
  41. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Door","","Which side controls the Door?",'side')
  42. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Debug","error","What level of debug output?")
  43. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Floor","","Which floor is this?",'numeric')
  44. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"Description","","What description to display?")
  45. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"FloorPW","","Floor Password:",'pass')
  46. DefaultConfig = ECAPI.addConfigOption(DefaultConfig,"ConfigPW","","Config. Password:",'pass')   -- note, this is per machine
  47.  
  48. local function networkName()
  49.         return ECAPI.getConfigOption(DoorConfig,"Network")
  50. end
  51.  
  52. local function modemSide()
  53.         return ECAPI.getConfigOption(DoorConfig,"Modem")
  54. end
  55.  
  56. local function callSide()
  57.         return ECAPI.getConfigOption(DoorConfig,"Call")
  58. end
  59.  
  60. local function detectSide()
  61.         return ECAPI.getConfigOption(DoorConfig,"Detect")
  62. end
  63.  
  64. local function doorSide()
  65.         return ECAPI.getConfigOption(DoorConfig,"Door")
  66. end
  67.  
  68. local function displaySide()
  69.         return ECAPI.getConfigOption(DoorConfig,"Display")
  70. end
  71.  
  72. local function whichFloor()
  73.         return ECAPI.getConfigOption(DoorConfig,"Floor")
  74. end
  75.  
  76. local function floorDesc()
  77.         return ECAPI.getConfigOption(DoorConfig,"Description")
  78. end
  79.  
  80.  
  81. -- ######################################################
  82. -- ##  Terminal Functions                              ##
  83. -- ######################################################
  84.  
  85. local function termClear()
  86.         ECAPI.printDoorTemplate(DoorConfig)
  87.         DoorScreen = ECAPI.setupScreen(DoorScreen)
  88.         term.setCursorPos(3,18)
  89.         write("Command: ")
  90. end
  91.  
  92.  
  93. local function justPrintInfo(DoorScreen,moduleID,sourceID,floorID,newLine)
  94.         local t = "["..ECAPI.padLeft(moduleID,6," ").."]"
  95.         t = t .. "["..ECAPI.padLeft(sourceID,4," ")..":"
  96.         t = t .. ECAPI.padLeft(floorID,2," ").."]: " .. newLine
  97.        
  98.         if (ECAPI.getConfigOption(DoorConfig,"Debug") == "info") then
  99.                 DoorScreen = ECAPI.justPrintLine(DoorScreen,t)
  100.         end
  101.         return DoorScreen
  102. end
  103.  
  104. local function justPrintLine(newLine)
  105.         DoorScreen = ECAPI.justPrintLine(DoorScreen,newLine)
  106. end
  107.  
  108.  
  109. -- functions?
  110.  
  111. local function registerFloor()
  112.         -- Now that we're started, notify the network controller
  113.         ECAPI.openModem(modemSide())
  114.         --password support:
  115.         local pass = ECAPI.getConfigOption(DoorConfig,"FloorPW")
  116.         if (pass == nil) then pass = "" else pass = ECAPI.encrypt(pass,networkName()) end
  117.         -- Descriptions are flagged with a * if it's password protected
  118.         ECAPI.broadcast(networkName(),"addFloor|"..whichFloor().."|"..floorDesc().."|"..pass)
  119. end
  120.  
  121. local function powerToggle(whichSide,stateStr)
  122.         local state = false
  123.         if string.lower(stateStr) == "true" then
  124.                 state = true
  125.         end
  126.         redstone.setOutput(whichSide,state)
  127. end
  128.  
  129. local function displayFloor(text)
  130.         peripheral.call(displaySide(), "setCursorPos", 1, 1)
  131.         peripheral.call(displaySide(), "setTextScale", 2.2)
  132.         peripheral.call(displaySide(), "write", ECAPI.padLeft(text,3," "))
  133. end
  134.  
  135. local function displayUp(text)
  136.         peripheral.call(displaySide(), "setTextScale", 2.2)
  137.         peripheral.call(displaySide(), "setCursorPos", 3, 2)
  138.         peripheral.call(displaySide(), "write", text)
  139. end
  140.  
  141. local function displayDown(text)
  142.         peripheral.call(displaySide(), "setTextScale", 2.2)
  143.         peripheral.call(displaySide(), "setCursorPos", 3, 2)
  144.         peripheral.call(displaySide(), "write", text)
  145. end
  146.  
  147. local function reconfig(args)
  148.         if (ECAPI.checkPassword(DoorScreen,DoorConfig,"ConfigPW")) then
  149.                 local what = args[1]
  150.                 if (what == nil) or (what == "") then
  151.                         DoorConfig = DefaultConfig
  152.                         createNewSettings()
  153.                 else
  154.                         what = ECAPI.caseCheck(DoorConfig,what)
  155.                         if (args[2] == nil) then
  156.                                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen, DoorConfig,what)
  157.                         else
  158.                                 DoorConfig = ECAPI.setConfigOption(DoorConfig,what,table.concat(args," ",2))
  159.                         end
  160.                         -- Make sure to SAVE changes
  161.                         ECAPI.table_save(DoorConfig,doorSettingsFile)
  162.                        
  163.                         -- notify the network's Motor controller
  164.                         registerFloor()
  165.  
  166.                         -- Check if the cabin is present at the end of startup
  167.                         if (redstone.getInput(callSide()) == true) then
  168.                                 cabinPresent = true
  169.                         end
  170.                        
  171.                 end
  172.         else
  173.                 justPrintLine("Invalid password.")
  174.         end
  175. end
  176.  
  177. local function sendFloorPW(sendID)
  178.         ECAPI.send(sendID,'floorpw|'..ECAPI.encrypt(ECAPI.getConfigOption(DoorConfig,'FloorPW'),networkName()))
  179. end
  180.  
  181. -- Control functions
  182.  
  183. local function openDoor()
  184.         --ECAPI.printLine("open door")
  185.         redstone.setOutput(doorSide(),false)
  186. end
  187.  
  188. local function closeDoor()
  189.         --ECAPI.printLine("close door")
  190.         redstone.setOutput(doorSide(),true)
  191. end
  192.  
  193.  
  194. -- ######################################################
  195. -- ##  Settings Functions                              ##
  196. -- ######################################################
  197.  
  198. local function createNewSettings()
  199.         DoorConfig = DefaultConfig
  200.         termClear()
  201.         --DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen)
  202.  
  203.         -- Confirm settings with the user, then save to file
  204.         DoorScreen = ECAPI.addPrintLine(DoorScreen,"No settings file found.  Setup method:")
  205.         justPrintLine(" 1) Normal Configuration")
  206.         justPrintLine(" 2) Import from Floor")
  207.         local setupMethod = ECAPI.getInput("Which: ")
  208.         if (setupMethod == nil) or (setupMethod == "") then
  209.                 setupMethod = 1
  210.         end
  211.         setupMethod = tonumber(setupMethod)
  212.        
  213.         if setupMethod == 1 then
  214.  
  215.                 -- To ensure this code plays nice with multiple elevators, Unique network names are required (as best as can)
  216.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Network")
  217.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Network")
  218.                
  219.                 -- Confirm modem side
  220.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Modem")
  221.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Modem")
  222.                
  223.                 -- Confirm call input side
  224.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Call")
  225.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Call")
  226.                
  227.                 -- Confirm detect input side
  228.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Detect")
  229.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Detect")
  230.  
  231.                 -- Confirm display output side
  232.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Display")
  233.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Display")
  234.                
  235.                 -- Confirm door output side
  236.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Door")
  237.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Door")
  238.  
  239.                 -- Confirm floor #
  240.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Floor")
  241.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Floor")
  242.  
  243.                 -- Confirm floor description
  244.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Description")
  245.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Description")
  246.  
  247.                 -- Debug output level
  248.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Debug")
  249.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Debug")
  250.         else
  251.                 -- Still need network name, modem side, and new floor #
  252.  
  253.                 -- To ensure this code plays nice with multiple elevators, Unique network names are required (as best as can)
  254.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Network")
  255.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Network")
  256.                 -- Confirm modem side
  257.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Modem")
  258.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Modem")
  259.                 -- Confirm floor #
  260.                 DoorScreen, DoorConfig = ECAPI.getConfigFromUser(DoorScreen,DoorConfig,"Floor")
  261.                 DoorScreen = ECAPI.printConfig(DoorConfig,DoorScreen,"Floor")
  262.                
  263.                 -- Now request the info from which floor?
  264.                 local sourceFloor = ECAPI.getInput("Source floor? ")
  265.                 ECAPI.openModem(modemSide())
  266.                 justPrintLine("Broadcasting: setupcopy|"..sourceFloor)
  267.                 ECAPI.broadcast(networkName(),"setupcopy|"..sourceFloor)
  268.                
  269.                 -- wait until we get the right rednet event
  270.                 local setupCopied = false
  271.                 local newSetup = { }
  272.                 repeat
  273.                         local sendID, rawmessage, distance = rednet.receive()
  274.                         local packet = ECAPI.explode("|",rawmessage)
  275.                         if packet[1] == networkName() then
  276.                                 if string.lower(packet[2]) ==     'setupdata'       then
  277.                                         newSetup = textutils.unserialize(packet[3])
  278.                                         setupCopied = true
  279.                                 end
  280.                         end
  281.                 until setupCopied == true
  282.  
  283.                 -- copy the data
  284.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Call",ECAPI.getConfigOption(newSetup,"Call"))
  285.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Detect",ECAPI.getConfigOption(newSetup,"Detect"))
  286.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Display",ECAPI.getConfigOption(newSetup,"Display"))
  287.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Door",ECAPI.getConfigOption(newSetup,"Door"))
  288.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Debug",ECAPI.getConfigOption(newSetup,"Debug"))
  289.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"Description",ECAPI.getConfigOption(newSetup,"Description"))
  290.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"FloorPW",ECAPI.getConfigOption(newSetup,"FloorPW"))
  291.                 DoorConfig = ECAPI.setConfigOption(DoorConfig,"ConfigPW",ECAPI.getConfigOption(newSetup,"ConfigPW"))
  292.         end
  293.         -- Write to file
  294.         ECAPI.table_save(DoorConfig,doorSettingsFile)
  295.         termClear()
  296.         if (setupMethod == 2) then justPrintLine("Make sure to review CONFIG settings.") end
  297. end
  298.  
  299. local function startupConfirmation()
  300.         -- Check if there's a settings file
  301.         if fs.exists(doorSettingsFile) then
  302.                 termClear()
  303.                 DoorConfig = ECAPI.table_load(doorSettingsFile)
  304.                 ECAPI.verifyConfig(DefaultConfig,DoorConfig)
  305.                 termClear()
  306.                 DoorScreen = ECAPI.justPrintLine(DoorScreen,"Settings loaded.")
  307.                 os.sleep(0.5)
  308.         else
  309.                 createNewSettings()
  310.         end
  311.         -- notify the network's Motor controller
  312.        
  313.         registerFloor()
  314.         -- Check if the cabin is present at the end of startup
  315.         if (redstone.getInput(callSide()) == true) then
  316.                 cabinPresent = true
  317.         end
  318.        
  319.         -- Close all doors by default
  320.         closeDoor()
  321. end
  322.  
  323.  
  324.  
  325. -- ######################################################
  326. -- ##  Input Actions                                   ##
  327. -- ######################################################
  328.  
  329. -- MAX Topic summary: 36 characters
  330. --                    |-----------------------------------|
  331. local helpTopics = {
  332.         ["reboot"]              = { "Restart this terminal." ,},
  333.         ["clear"]               = { "Refresh the terminal screen." ,},
  334.         ["config"]              = { "Prints the current settings. Will not print",
  335.                                                 "any set passwords." ,},
  336.         ["reconfig"]    = { "With no parameters, re-run the whole setup.",
  337.                                                 "RECONFIG [option] to change a single setting",
  338.                                                 "Note: Reconfig can be password protected via",
  339.                                                 "the ConfigPW setting. If this password is",
  340.                                                 "lost, you will need server admin help or be",
  341.                                                 "forced to recreate the terminal." ,},
  342.         ["broadcast"]   = { "Manually broadcast <message>",},
  343.         ["send"]                = { "Manually send <id> <message>",},
  344.         ["power"]               = { "Manually toggle power <side>",},
  345. }
  346.  
  347. local function printHelp(topic)
  348.         justPrintLine("Help on '"..string.upper(topic).."':")
  349.         local t = helpTopics[topic]
  350.         for _, text in ipairs(t) do
  351.                 justPrintLine("  "..text)
  352.         end
  353. end
  354.  
  355. local commands = {
  356.         ["help"] = function (x)
  357.                         if (x[1] ~= nil) then
  358.                                 printHelp(x[1])
  359.                         else
  360.                                 justPrintLine("Please use HELP [COMMAND]. ? for Command List")
  361.                         end
  362.                 end,
  363.         ["reboot"] = function (x) os.reboot() end,
  364.         ["clear"] = function (x) termClear() end,
  365.         ["broadcast"] = function (x)
  366.                         DoorScreen = justPrintInfo(DoorScreen,"REDNET",nil,nil,"Broadcast: "..table.concat(x," ",2))
  367.                         ECAPI.broadcast(networkName(),table.concat(x," "))
  368.                 end,
  369.         ["send"] = function (x)
  370.                         DoorScreen = justPrintInfo(DoorScreen,"REDNET",nil,nil,"Send("..tonumber(x[1]).."): "..table.concat(x," ",2))
  371.                         ECAPI.send(networkName(),tonumber(x[1]),table.concat(x," ",2))
  372.                 end,
  373.        
  374.         ["config"] = function (x)
  375.                         justPrintLine("Current Configuration:")
  376.                         justPrintLine("-------------------------------")
  377.                         ECAPI.printConfig(DoorConfig,DoorScreen)
  378.                 end,
  379.         ["reconfig"] = function (x) reconfig(x) end,
  380.  
  381.         ["power"] = function (x) powerToggle(x[1], x[2]) end,
  382. }
  383.  
  384.  
  385. local function printCommands()
  386.         local col = 1
  387.         local row = {}
  388.         for named, _ in pairs(commands) do
  389.                 if (col < 5) then row[col] = string.upper(named) end
  390.                 col = col + 1
  391.                 if (col == 5) then
  392.                         justPrintLine(ECAPI.padLeft(row[1],12," ")..ECAPI.padLeft(row[2],12," ")..ECAPI.padLeft(row[3],12," ")..ECAPI.padLeft(row[4],11," "))
  393.                         row = {}
  394.                         col = 1
  395.                 end
  396.         end
  397.         if (col > 1) then
  398.                 -- handles 'remainders'
  399.                 justPrintLine(ECAPI.padLeft(row[1],12," ")..ECAPI.padLeft(row[2],12," ")..ECAPI.padLeft(row[3],12," ")..ECAPI.padLeft(row[4],11," "))
  400.         end
  401. end
  402.  
  403. local function processCommands(input)  
  404.         local arg = ECAPI.explode(" ",input)
  405.         local command = arg[1]
  406.         table.remove(arg,1)
  407.         if ECAPI.tableContainsKey(commands, command) then
  408.                 commands[command](arg)
  409.         else
  410.                 justPrintLine("Unknown command '"..command.."'. Commands are:")
  411.                 printCommands()
  412.         end
  413. end
  414.  
  415.  
  416.  
  417. -- ######################################################      
  418. -- ##  Handler Functions                               ##
  419. -- ######################################################
  420.  
  421. -- User input listener
  422. local function userConsole()
  423.         while KeepWaiting do
  424.                 processCommands(ECAPI.getInput("Command:"))
  425.         end
  426. end
  427.  
  428.  
  429. -- Redstone listener
  430. local function redstoneListener()
  431.         while KeepWaiting do
  432.                 event, param1, param2 = os.pullEvent("redstone")
  433.                
  434.                 -- The players have pushed a button
  435.                 if event == "redstone" and redstone.getInput(callSide()) == true then
  436.                         --ECAPI.printLine("Call detection")
  437.                         ECAPI.broadcast(networkName(),"call")
  438.                 end
  439.                
  440.                 -- if the cabin was not present, and we get a redstone on detect side, it just arrived
  441.                 if (event == "redstone") and (redstone.getInput(detectSide()) == true) then
  442.                         --ECAPI.printLine("Cabin detection")
  443.                         cabinPresent = true
  444.                         ECAPI.broadcast(networkName(),"arrival|"..whichFloor())
  445.                         displayFloor(whichFloor())
  446.                 end
  447.                
  448.                 -- if the redstone on the detect side shuts off, the cabin just departed
  449.                 if (event == "redstone") and (redstone.getInput(detectSide()) == false) then
  450.                         --ECAPI.printLine("Cabin departure")
  451.                         cabinPresent = false
  452.                         ECAPI.broadcast(networkName(),"departure")
  453.                 end
  454.         end
  455. end
  456.  
  457. -- Rednet listener
  458. local function rednetListener()
  459.         ECAPI.openModem(modemSide())
  460.         while KeepWaiting do
  461.                 local sendID, rawmessage, distance = rednet.receive()
  462.                 local packet = ECAPI.explode("|",rawmessage)
  463.                 if packet[1] == networkName() then
  464.                         --ECAPI.printLine("Message received ["..sendID.."]: "..table.concat(packet,"|",2))
  465.                        
  466.                         if string.lower(packet[2]) ==     'controlreset'       then
  467.                                 -- If the control computer issues a reset broadcast, re-register with it.
  468.                                 os.sleep(math.random(2.0,4.0))
  469.                                 --ECAPI.printLine("Re-registering")
  470.                                 registerFloor()
  471.                                 if (redstone.getInput(detectSide()) == true) then
  472.                                         -- also call out if the cabin is at this level
  473.                                         ECAPI.broadcast(networkName(),"arrival")
  474.                                 end
  475.                                
  476.                         elseif string.lower(packet[2]) == 'opendoor'           then
  477.                                 --ECAPI.printLine("open door request "..ECAPI.padLeft(packet[3],5,' ').."?"..os.getComputerID())
  478.                                 if packet[3] == tostring(os.getComputerID()) then
  479.                                         openDoor()
  480.                                 else
  481.                                         closeDoor()
  482.                                 end
  483.                        
  484.                         elseif string.lower(packet[2]) == 'closedoor'          then
  485.                                 closeDoor()
  486.                         elseif string.lower(packet[2]) == 'arrival'           then
  487.                                 displayFloor(packet[3])
  488.                         elseif string.lower(packet[2]) == 'displayup'           then
  489.                                 displayUp(packet[3])
  490.                         elseif string.lower(packet[2]) == 'displaydown'           then
  491.                                 displayDown(packet[3])
  492.                         elseif string.lower(packet[2]) == 'locate'           then
  493.                                 if (redstone.getInput(detectSide()) == true) then
  494.                                         -- also call out if the cabin is at this level
  495.                                         ECAPI.broadcast(networkName(),"arrival")
  496.                                 end
  497.                         elseif string.lower(packet[2]) == 'reboot'           then
  498.                                 os.reboot()
  499.                         elseif string.lower(packet[2]) == 'sendpw'           then
  500.                                 sendFloorPW(sendID)
  501.                         elseif string.lower(packet[2]) == 'setupcopy'           then
  502.                                 if tonumber(packet[3]) == whichFloor() then
  503.                                         ECAPI.send(networkName(),sendID,'setupdata|'..textutils.serialize(DoorConfig))
  504.                                 end
  505.                         end
  506.                 end
  507.         end
  508. end
  509.  
  510. -- ######################################################
  511. -- ##  Actual Run Program                              ##
  512. -- ######################################################
  513.  
  514. startupConfirmation()
  515. parallel.waitForAll(userConsole,redstoneListener,rednetListener)
  516. --while KeepWaiting do
  517. --      ECAPI.printLine("")
  518. --      processCommands(ECAPI.getInput("Operation:"))
  519. --end
  520.  
  521. --startupConfirmation()
  522. --openModem()
  523. --broadcast("addFloor|1")
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Not a member of Pastebin yet?
Sign Up, it unlocks many cool features!
 
Top