ElijahCrafter

Door

Jul 15th, 2026
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.94 KB | Source Code | 0 0
  1. -- doorlock - a touch PIN lock for an ADVANCED computer (not pocket).
  2. --
  3. -- First run: pick a PIN on the keypad; the program takes over startup
  4. -- so the computer boots locked from then on. Ctrl+T is blocked, and
  5. -- attempts/lockout survive reboots.
  6. --
  7. -- 3 wrong PINs = locked out. The unlock code is rednet-messaged to the
  8. -- pocket computer OWNER_ID (it lands in the Messages app) - the door
  9. -- computer needs a wireless modem for that. With no modem it falls
  10. -- back to a 60 second cooldown.
  11. --
  12. -- When unlocked: big OPEN DOOR button (your redstone goes in
  13. -- openDoor() below), plus Lock and Shell buttons in the corners.
  14.  
  15. -------------------------------------------------- CONFIG - edit me --
  16.  
  17. local OWNER_ID = 11      -- pocket computer that receives unlock codes
  18. local DOOR_SIDE = "bottom" -- redstone side used by the default openDoor
  19. local DOOR_TIME = 3      -- seconds the door stays powered
  20. local AUTO_LOCK = 60     -- relock after this many idle seconds (0 = never)
  21. local door = peripheral.find("redstoneIntegrator")
  22. -- The OPEN DOOR button runs this. Put YOUR redstone logic here.
  23. local function openDoor()
  24.     door.setOutput(DOOR_SIDE, true)
  25.     sleep(DOOR_TIME)
  26.     door.setOutput(DOOR_SIDE, false)
  27. end
  28.  
  29. -----------------------------------------------------------------------
  30.  
  31. local DATA = "/.doorlock"
  32.  
  33. if not term.isColor or not term.isColor() then
  34.     print("doorlock needs an advanced (gold) computer.")
  35.     return
  36. end
  37.  
  38. math.randomseed(os.epoch and os.epoch("utc") or os.time() * 1000)
  39.  
  40. local hasModem = false
  41. for _, side in ipairs(peripheral.getNames()) do
  42.     if peripheral.getType(side) == "modem" then
  43.         if pcall(rednet.open, side) then hasModem = true end
  44.     end
  45. end
  46.  
  47. local w, h = term.getSize()
  48.  
  49. local function loadState()
  50.     if fs.exists(DATA) then
  51.         local f = fs.open(DATA, "r")
  52.         local v = textutils.unserialize(f.readAll() or "")
  53.         f.close()
  54.         if type(v) == "table" then return v end
  55.     end
  56.     return {}
  57. end
  58.  
  59. local st = loadState()
  60.  
  61. local function save()
  62.     local f = fs.open(DATA, "w")
  63.     f.write(textutils.serialize(st))
  64.     f.close()
  65. end
  66.  
  67. local function hash(s)
  68.     local n = 5381
  69.     for i = 1, #s do
  70.         n = (n * 33 + s:byte(i)) % 4294967296
  71.     end
  72.     return tostring(n)
  73. end
  74.  
  75. local function fill(x, y, wd, ht, bg)
  76.     term.setBackgroundColor(bg)
  77.     for i = 0, ht - 1 do
  78.         term.setCursorPos(x, y + i)
  79.         term.write(string.rep(" ", wd))
  80.     end
  81. end
  82.  
  83. local function center(y, s, fg)
  84.     term.setCursorPos(math.floor((w - #s) / 2) + 1, y)
  85.     term.setTextColor(fg)
  86.     term.setBackgroundColor(colors.black)
  87.     term.write(s)
  88. end
  89.  
  90. local function sendCode(code)
  91.     if not hasModem then return false end
  92.     rednet.send(OWNER_ID, {
  93.         text = "3 wrong PIN tries on #" .. os.getComputerID()
  94.             .. ". Unlock code: " .. code,
  95.         label = os.getComputerLabel() or "Door Lock",
  96.     }, "pocketos")
  97.     return true
  98. end
  99.  
  100. -- GUI number pad. Returns the digits entered once OK/Enter is pressed.
  101. -- opts: hint (bottom line), onResend (adds a resend-code button).
  102. local function keypad(title, msg, opts)
  103.     opts = opts or {}
  104.     local entry = ""
  105.     local info = msg
  106.     local infoColor = colors.red
  107.     local layout = {
  108.         { "1", "2", "3" },
  109.         { "4", "5", "6" },
  110.         { "7", "8", "9" },
  111.         { "C", "0", "OK" },
  112.     }
  113.     while true do
  114.         term.setBackgroundColor(colors.black)
  115.         term.clear()
  116.         center(2, title, colors.white)
  117.         if #entry > 0 then
  118.             center(4, string.rep("\7 ", #entry), colors.blue)
  119.         else
  120.             center(4, "_ _ _", colors.gray)
  121.         end
  122.         local regions = {}
  123.         local x0 = math.floor(w / 2) - 8
  124.         for r = 1, 4 do
  125.             for cn = 1, 3 do
  126.                 local label = layout[r][cn]
  127.                 local bx = x0 + (cn - 1) * 6
  128.                 local by = 4 + r * 2
  129.                 fill(bx, by, 5, 1, label == "OK" and colors.green or colors.gray)
  130.                 term.setCursorPos(bx + math.floor((5 - #label) / 2), by)
  131.                 term.setTextColor(colors.white)
  132.                 term.write(label)
  133.                 regions[#regions + 1] = { x1 = bx, x2 = bx + 4, y = by, label = label }
  134.             end
  135.         end
  136.         if info then center(14, info, infoColor) end
  137.         if opts.onResend then
  138.             center(16, "[ Resend code ]", colors.blue)
  139.         end
  140.         center(h, opts.hint or "Typing digits works too", colors.gray)
  141.  
  142.         local function press(label)
  143.             if label == "OK" then
  144.                 if #entry >= 3 then return entry end
  145.                 info, infoColor = "At least 3 digits", colors.red
  146.             elseif label == "C" then
  147.                 entry = ""
  148.             elseif #entry < 8 then
  149.                 entry = entry .. label
  150.             end
  151.         end
  152.  
  153.         local e, a, b, c = os.pullEventRaw() -- raw: Ctrl+T is ignored
  154.         if e == "mouse_click" then
  155.             local handled = false
  156.             for _, r in ipairs(regions) do
  157.                 if c == r.y and b >= r.x1 and b <= r.x2 then
  158.                     local done = press(r.label)
  159.                     if done then return done end
  160.                     handled = true
  161.                     break
  162.                 end
  163.             end
  164.             if not handled and opts.onResend and c == 16 then
  165.                 opts.onResend()
  166.                 info, infoColor = "Code sent again!", colors.green
  167.             end
  168.         elseif e == "char" and a:match("^%d$") then
  169.             if #entry < 8 then entry = entry .. a end
  170.         elseif e == "key" then
  171.             if a == keys.enter then
  172.                 if #entry >= 3 then return entry end
  173.                 info, infoColor = "At least 3 digits", colors.red
  174.             elseif a == keys.backspace then
  175.                 entry = entry:sub(1, -2)
  176.             end
  177.         end
  178.     end
  179. end
  180.  
  181. local function cooldown(secs)
  182.     for left = secs, 1, -1 do
  183.         term.setBackgroundColor(colors.black)
  184.         term.clear()
  185.         center(8, "Too many attempts", colors.red)
  186.         center(10, "No modem to send a code.", colors.gray)
  187.         center(12, "Try again in " .. left .. "s", colors.white)
  188.         local t = os.startTimer(1)
  189.         repeat
  190.             local e, id = os.pullEventRaw()
  191.         until e == "timer" and id == t
  192.     end
  193. end
  194.  
  195. local function unlockedScreen()
  196.     while true do
  197.         term.setBackgroundColor(colors.black)
  198.         term.clear()
  199.         center(2, (os.getComputerLabel() or "Door") .. " - unlocked", colors.white)
  200.         local bw, bh = 25, 5
  201.         local bx = math.floor((w - bw) / 2) + 1
  202.         local by = math.floor((h - bh) / 2) + 1
  203.         fill(bx, by, bw, bh, colors.green)
  204.         term.setCursorPos(bx + math.floor((bw - 9) / 2), by + 2)
  205.         term.setTextColor(colors.white)
  206.         term.write("OPEN DOOR")
  207.         fill(2, h - 1, 6, 1, colors.gray)
  208.         term.setCursorPos(3, h - 1)
  209.         term.setTextColor(colors.white)
  210.         term.write("Lock")
  211.         fill(w - 8, h - 1, 7, 1, colors.gray)
  212.         term.setCursorPos(w - 7, h - 1)
  213.         term.write("Shell")
  214.  
  215.         local timer = AUTO_LOCK > 0 and os.startTimer(AUTO_LOCK) or nil
  216.         local e, a, b, c = os.pullEventRaw()
  217.         if e == "timer" and a == timer then
  218.             return "lock" -- idle too long
  219.         elseif e == "mouse_click" then
  220.             if b >= bx and b <= bx + bw - 1 and c >= by and c <= by + bh - 1 then
  221.                 fill(bx, by, bw, bh, colors.lime)
  222.                 term.setCursorPos(bx + math.floor((bw - 10) / 2), by + 2)
  223.                 term.setTextColor(colors.black)
  224.                 term.write("DOOR OPEN!")
  225.                 pcall(openDoor)
  226.             elseif c == h - 1 and b >= 2 and b <= 7 then
  227.                 return "lock"
  228.             elseif c == h - 1 and b >= w - 8 then
  229.                 return "shell"
  230.             end
  231.         end
  232.     end
  233. end
  234.  
  235. -- first-run setup
  236. if not st.pin then
  237.     local m
  238.     while true do
  239.         local a = keypad("Set up Door Lock", m, { hint = "Choose a PIN (3-8 digits)" })
  240.         local b = keypad("Set up Door Lock", nil, { hint = "Repeat the PIN" })
  241.         if a == b then
  242.             st.pin = hash(a)
  243.             st.attempts = 0
  244.             save()
  245.             break
  246.         end
  247.         m = "PINs didn't match - retry"
  248.     end
  249.     local f = fs.open("startup.lua", "w")
  250.     f.write("-- Door lock (created by doorlock)\n")
  251.     f.write("shell.run(\"doorlock.lua\")\n")
  252.     f.close()
  253. end
  254.  
  255. local msg
  256. while true do
  257.     if st.lockout then
  258.         local entered = keypad("LOCKED OUT", msg, {
  259.             hint = "Code sent to pocket #" .. OWNER_ID,
  260.             onResend = hasModem and function() sendCode(st.lockout) end or nil,
  261.         })
  262.         if entered == st.lockout then
  263.             st.lockout = nil
  264.             st.attempts = 0
  265.             save()
  266.             msg = nil
  267.             if unlockedScreen() == "shell" then break end
  268.         else
  269.             msg = "Wrong code"
  270.         end
  271.     else
  272.         local entered = keypad("\7 Locked", msg)
  273.         if hash(entered) == st.pin then
  274.             st.attempts = 0
  275.             save()
  276.             msg = nil
  277.             if unlockedScreen() == "shell" then break end
  278.         else
  279.             st.attempts = (st.attempts or 0) + 1
  280.             save()
  281.             if st.attempts >= 3 then
  282.                 if hasModem then
  283.                     st.lockout = tostring(math.random(100000, 999999))
  284.                     save()
  285.                     sendCode(st.lockout)
  286.                     msg = nil
  287.                 else
  288.                     cooldown(60)
  289.                     st.attempts = 0
  290.                     save()
  291.                     msg = nil
  292.                 end
  293.             else
  294.                 msg = "Wrong PIN - " .. (3 - st.attempts) .. " left"
  295.             end
  296.         end
  297.     end
  298. end
  299.  
  300. term.setBackgroundColor(colors.black)
  301. term.setTextColor(colors.white)
  302. term.clear()
  303. term.setCursorPos(1, 1)
  304. print("Door lock exited. Run 'reboot' to lock again.")
  305.  
Advertisement
Add Comment
Please, Sign In to add comment