ComputerCraft Archive

Ultimate Door Lock - PDA Opened Doors!

pocket networking unknown forum

Description

Now, you may be thinking, "oeed, why are you of all people making a door lock - something normally made by first-time users." Well, the answer is quite simple really, Pocket Computers now have wireles...

Installation

Copy one of these commands into your ComputerCraft terminal:

Pastebin:pastebin get 4QeAEiTM ultimate_door_lock_-_pda_opened_doors!
wget:wget https://pastebin.com/raw/4QeAEiTM ultimate_door_lock_-_pda_opened_doors!
Archive:wget https://cc.shobie.xyz/cc/get/pb-4QeAEiTM ultimate_door_lock_-_pda_opened_doors!
Quick Install: wget https://cc.shobie.xyz/cc/get/pb-4QeAEiTM Ultimate Door Lock - PDA Opened Doors!

Usage

Run the program after downloading

Tags

networkingforumpocket-programs

Source

View Original Source

Code Preview

tArgs = {...}

if OneOS then
	--running under OneOS
	OneOS.ToolBarColour = colours.white
	OneOS.ToolBarTextColour = colours.grey
end

local _w, _h = term.getSize()

local round = function(num, idp)
	local mult = 10^(idp or 0)
	return math.floor(num * mult + 0.5) / mult
end

InterfaceElements = {}

Drawing = {
	
	Screen = {
		Width = _w,
		Height = _h
	},

	DrawCharacters = function (x, y, characters, textColour,bgColour)
		Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour)
	end,
	
	DrawBlankArea = function (x, y, w, h, colour)
		Drawing.DrawArea (x, y, w, h, " ", 1, colour)
	end,

	DrawArea = function (x, y, w, h, character, textColour, bgColour)
		--width must be greater than 1, other wise we get a stack overflow
		if w < 0 then
			w = w * -1
		elseif w == 0 then
			w = 1
		end

		for ix = 1, w do
			local currX = x + ix - 1
			for iy = 1, h do
				local currY = y + iy - 1
				Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour)
			end
		end
	end,

	DrawImage = function(_x,_y,tImage, w, h)
		if tImage then
			for y = 1, h do
				if not tImage[y] then
					break
				end
				for x = 1, w do
					if not tImage[y][x] then
						break
					end
					local bgColour = tImage[y][x]
		            local textColour = tImage.textcol[y][x] or colours.white
		            local char = tImage.text[y][x]
		            Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour)
				end
			end
		elseif w and h then
			Drawing.DrawBlankArea(x, y, w, h, colours.green)
		end
	end,
	--using .nft
	LoadImage = function(path)
		local image = {
			text = {},
			textcol = {}
		}
		local fs = fs
		if OneOS then
			fs = OneOS.FS
		end
		if fs.exists(path) then
			local _open = io.open
			if OneOS then
				_open = OneOS.IO.open
			end
	        local file = _open(path, "r")
	        local sLine = file:read()
	        local num = 1
	        while sLine do  
	                table.insert(image, num, {})
	                table.insert(image.text, num, {})
	                table.insert(image.textcol, num, {})
	                                            
	                --As we're no longer 1-1, we keep track of what index to write to
	                local writeIndex = 1
	                --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
	                local bgNext, fgNext = false, false
	                --The current background and foreground colours
	                local currBG, currFG = nil,nil
	                for i=1,#sLine do
	                        local nextChar = string.sub(sLine, i, i)
	                        if nextChar:byte() == 30 then
                                bgNext = true
	                        elseif nextChar:byte() == 31 then
                                fgNext = true
	                        elseif bgNext then
                                currBG = Drawing.GetColour(nextChar)
                                bgNext = false
	                        elseif fgNext then
                                currFG = Drawing.GetColour(nextChar)
                                fgNext = false
	                        else
                                if nextChar ~= " " and currFG == nil then
                                       currFG = colours.white
                                end
                                image[num][writeIndex] = currBG
                                image.textcol[num][writeIndex] = currFG
                                image.text[num][writeIndex] = nextChar
                                writeIndex = writeIndex + 1
	                        end
	                end
	                num = num+1
	                sLine = file:read()
	        end
	        file:close()
		end
	 	return image
	end,

	DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour)
		w = w or Drawing.Screen.Width
		h = h or Drawing.Screen.Height
		x = x or 0
		y = y or 0
		x = math.ceil((w - #characters) / 2) + x
		y = math.floor(h / 2) + y

		Drawing.DrawCharacters(x, y, characters, textColour, bgColour)
	end,

	GetColour = function(hex)
		if hex == ' ' then
			return colours.transparent
		end
	    local value = tonumber(hex, 16)
	    if not value then return nil end
	    value = math.pow(2,value)
	    return value
	end,

	Clear = function (_colour)
		_colour = _colour or colours.black
		Drawing.ClearBuffer()
		Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour)
	end,

	Buffer = {},
	BackBuffer = {},

	DrawBuffer = function()
		for y,row in pairs(Drawing.Buffer) do
			for x,pixel in pairs(row) do
				local shouldDraw = true
				local hasBackBuffer = true
				if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then
					hasBackBuffer = false
				end
				if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then
					shouldDraw = false
				end
				if shouldDraw then
					term.setBackgroundColour(pixel[3])
					term.setTextColour(pixel[2])
					term.setCursorPos(x, y)
					term.write(pixel[1])
				end
			end
		end
		Drawing.BackBuffer = Drawing.Buffer
		Drawing.Buffer = {}
		term.setCursorPos(1,1)
	end,

	ClearBuffer = function()
		Drawing.Buffer = {}
	end,

	WriteStringToBuffer = function (x, y, characters, textColour,bgColour)
		for i = 1, #characters do
   			local character = characters:sub(i,i)
   			Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour)
		end
	end,

	WriteToBuffer = function(x, y, character, textColour,bgColour)
		x = round(x)
		y = round(y)
		if bgColour == colours.transparent then
			Drawing.Buffer[y] = Drawing.Buffer[y] or {}
			Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black}
			Drawing.Buffer[y][x][1] = character
			Drawing.Buffer[y][x][2] = textColour
		else
			Drawing.Buffer[y] = Drawing.Buffer[y] or {}
			Drawing.Buffer[y][x] = {character, textColour, bgColour}
		end
	end,
}

Current = {
	Document = nil,
	TextInput = nil,
	CursorPos = {1,1},
	CursorColour = colours.black,
	Selection = {8, 36},
	Window = nil,
	HeaderText = '',
	StatusText = '',
	StatusColour = colours.grey,
	StatusScreen = true,
	ButtonOne = nil,
	ButtonTwo = nil,
	Locked = false,
	Page = '',
	PageControls = {}
}

isRunning = true

Events = {}

Button = {
	X = 1,
	Y = 1,
	Width = 0,
	Height = 0,
	BackgroundColour = colours.lightGrey,
	TextColour = colours.white,
	ActiveBackgroundColour = colours.lightGrey,
	Text = "",
	Parent = nil,
	_Click = nil,
	Toggle = nil,

	AbsolutePosition = function(self)
		return self.Parent:AbsolutePosition()
	end,

	Draw = function(self)
		local bg = self.BackgroundColour
		local tc = self.TextColour
		if type(bg) == 'function' then
			bg = bg()
		end

		if self.Toggle then
			tc = colours.white
			bg = self.ActiveBackgroundColour
		end

		local pos = GetAbsolutePosition(self)
		Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg)
		Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg)
	end,

	Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour)
		local new = {}    -- the new instance
		setmetatable( new, {__index = self} )
		height = height or 1
		new.Width = width or #text + 2
		new.Height = height
		new.Y = y
		new.X = x
		new.Text = text or ""
		new.BackgroundColour = backgroundColour or colours.lightGrey
		new.TextColour = textColour or colours.white
		new.ActiveBackgroundColour = activeBackgroundColour or colours.lightBlue
		new.Parent = parent
		new._Click = click
		new.Toggle = toggle
		return new
	end,

	Click = function(self, side, x, y)
		if self._Click then
			if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then
				self.Toggle = not self.Toggle
				Draw()
			end
			return true
		else
			return false
		end
	end
}

Label = {
	X = 1,
	Y = 1,
	Width = 0,
	Height = 0,
	BackgroundColour = colours.lightGrey,
	TextColour = colours.white,
	Text = "",
	Parent = nil,

	AbsolutePosition = function(self)
		return self.Parent:AbsolutePosition()
	end,

	Draw = function(self)
		local bg = self.BackgroundColour
		local tc = self.TextColour

		if self.Toggle then
			tc = UIColours.MenuBarActive
			bg = self.ActiveBackgroundColour
		end

		local pos = GetAbsolutePosition(self)
		Drawing.DrawCharacters(pos.X, pos.Y, self.Text, self.TextColour, self.BackgroundColour)
	end,

	Initialise = function(self, x, y, text, textColour, backgroundColour, parent)
		local new = {}    -- the new instance
		setmetatable( new, {__index = self} )
		height = height or 1
		new.Width = width or #text + 2
		new.Height = height
		new.Y = y
		new.X = x
		new.Text = text or ""
		new.BackgroundColour = backgroundColour or colours.white
		new.TextColour = textColour or colours.black
		new.Parent = parent
		return new
	end,

	Click = function(self, side, x, y)
		return false
	end
}

TextBox = {
	X = 1,
	Y = 1,
	Width = 0,
	Height = 0,
	BackgroundColour = colours.lightGrey,
	TextColour = colours.black,
	Parent = nil,
	TextInput = nil,
	Placeholder = '',

	AbsolutePosition = function(self)
		return self.Parent:AbsolutePosition()
	end,

	Draw = function(self)		
		local pos = GetAbsolutePosition(self)
		Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour)
		local text = self.TextInput.Value
		if #tostring(text) > (self.Width - 2) then
			text = text:sub(#text-(self.Width - 3))
			if Current.TextInput == self.TextInput then
				Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y}
			end
		else
			if Current.TextInput == self.TextInput then
				Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y}
			end
		end
		
		if #tostring(text) == 0 then
			Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour)
		else
			Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour)
		end

		term.setCursorBlink(true)
		
		Current.CursorColour = self.TextColour
	end,

	Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical)
		local new = {}    -- the new instance
		setmetatable( new, {__index = self} )
		height = height or 1
		new.Width = width or #text + 2
		new.Height = height
		new.Y = y
		new.X = x
		new.TextInput = TextInput:Initialise(text or '', function(key)
			if done then
				done(key)
			end
			Draw()
		end, numerical)
		new.BackgroundColour = backgroundColour or colours.lightGrey
		new.TextColour = textColour or colours.black
		new.Parent = parent
		return new
	end,

	Click = function(self, side, x, y)
		Current.Input = self.TextInput
		self:Draw()
	end
}

TextInput = {
	Value = "",
	Change = nil,
	CursorPos = nil,
	Numerical = false,
	IsDocument = nil,

	Initialise = function(self, value, change, numerical, isDocument)
		local new = {}    -- the new instance
		setmetatable( new, {__index = self} )
		new.Value = tostring(value)
		new.Change = change
		new.CursorPos = #tostring(value)
		new.Numerical = numerical
		new.IsDocument = isDocument or false
		return new
	end,

	Insert = function(self, str)
		if self.Numerical then
			str = tostring(tonumber(str))
		end

		local selection = OrderSelection()

		if self.IsDocument and selection then
			self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2)
			self.CursorPos = selection[1]
			Current.Selection = nil
		else
			local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')

			self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1  + newLineAdjust)
			self.CursorPos = self.CursorPos + 1
		end
		
		self.Change(key)
	end,

	Extract = function(self, remove)
		local selection = OrderSelection()
		if self.IsDocument and selection then
			local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','')
			local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust)
			if remove then
				self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust)
				self.CursorPos = selection[1] - 1
				Current.Selection = nil
			end
			return str
		end
	end,

	Char = function(self, char)
		if char == 'nil' then
			return
		end
		self:Insert(char)
	end,

	Key = function(self, key)
		if key == keys.enter then
			if self.IsDocument then
				self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 )
				self.CursorPos = self.CursorPos + 1
			end
			self.Change(key)		
		elseif key == keys.left then
			-- Left
			if self.CursorPos > 0 then
				local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
				self.CursorPos = self.CursorPos - 1 - colShift
				self.Change(key)
			end
			
		elseif key == keys.right then
			-- Right				
			if self.CursorPos < string.len(self.Value) then
				local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1))
				self.CursorPos = self.CursorPos + 1 + colShift
				self.Change(key)
			end
		
		elseif key == keys.backspace then
			-- Backspace
			if self.IsDocument and Current.Selection then
				self:Extract(true)
				self.Change(key)
			elseif self.CursorPos > 0 then
				local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
				local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')

				self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust)
				self.CursorPos = self.CursorPos - 1 - colShift
				self.Change(key)
			end
		elseif key == keys.home then
			-- Home
			self.CursorPos = 0
			self.Change(key)
		elseif key == keys.delete then
			if self.IsDocument and Current.Selection then
				self:Extract(true)
				self.Change(key)
			elseif self.CursorPos < string.len(self.Value) then
				self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 )				
				self.Change(key)
			end
		elseif key == keys["end"] then
			-- End
			self.CursorPos = string.len(self.Value)
			self.Change(key)
		elseif key == keys.up and self.IsDocument then
			-- Up
			if Current.Document.CursorPos then
				local page = Current.Document.Pages[Current.Document.CursorPos.Page]
				self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true)
				self.Change(key)
			end
		elseif key == keys.down and self.IsDocument then
			-- Down
			if Current.Document.CursorPos then
				local page = Current.Document.Pages[Current.Document.CursorPos.Page]
				self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true)
				self.Change(key)
			end
		end
	end
}

local Capitalise = function(str)
	return str:sub(1, 1):upper() .. str:sub(2, -1)
end


local getNames = peripheral.getNames or function()
    local tResults = {}
    for n,sSide in ipairs( rs.getSides() ) do
        if peripheral.isPresent( sSide ) then
            table.insert( tResults, sSide )
            local isWireless = false
            if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
                isWireless = true
            end     
            if peripheral.getType( sSide ) == "modem" and not isWireless then
                local tRemote = peripheral.call( sSide, "getNamesRemote" )
                for n,sName in ipairs( tRemote ) do
                    table.insert( tResults, sName )
                end
            end
        end
    end
    return tResults
end

Peripheral = {
    GetPeripheral = function(_type)
        for i, p in ipairs(Peripheral.GetPeripherals()) do
            if p.Type == _type then
                return p
            end
        end
    end,

    Call = function(type, ...)
        local tArgs = {...}
        local p = Peripheral.GetPeripheral(type)
        peripheral.call(p.Side, unpack(tArgs))
    end,

    GetPeripherals = function(filterType)
        local peripherals = {}
        for i, side in ipairs(getNames()) do
            local name = peripheral.getType(side):gsub("^%l", string.upper)
            local code = string.upper(side:sub(1,1))
            if side:find('_') then
                code = side:sub(side:find('_')+1)
            end

            local dupe = false
            for i, v in ipairs(peripherals) do
                if v[1] == name .. ' ' .. code then
                    dupe = true
                end
            end

            if not dupe then
                local _type = peripheral.getType(side)
                local isWireless = false
                if _type == 'modem' then
                    if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
                        isWireless = true
                    end     
                    if isWireless then
                        _type = 'wireless_modem'
                        name = 'W '..name
                    end
                end
                if not filterType or _type == filterType then
                    table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless})
                end
            end
        end
        return peripherals
    end,

    PresentNamed = function(name)
        return peripheral.isPresent(name)
    end,

    CallType = function(type, ...)
        local tArgs = {...}
        local p = Peripheral.GetPeripheral(type)
        return peripheral.call(p.Side, unpack(tArgs))
    end,

    CallNamed = function(name, ...)
        local tArgs = {...}
        return peripheral.call(name, unpack(tArgs))
    end
}

Wireless = {
	Channels = {
		UltimateDoorlockPing = 4210,
		UltimateDoorlockRequest = 4211,
		UltimateDoorlockRequestReply = 4212,
	},

	isOpen = function(channel)
		return Peripheral.CallType('wireless_modem', 'isOpen', channel)
	end,

	Open = function(channel)
		if not Wireless.isOpen(channel) then
			Peripheral.CallType('wireless_modem', 'open', channel)
		end
	end,

	close = function(channel)
		Peripheral.CallType('wireless_modem', 'close', channel)
	end,

	closeAll = function()
		Peripheral.CallType('wireless_modem', 'closeAll')
	end,

	transmit = function(channel, replyChannel, message)
		Peripheral.CallType('wireless_modem', 'transmit', channel, replyChannel, textutils.serialize(message))
	end,

	Present = function()
		if Peripheral.GetPeripheral('wireless_modem') == nil then
			return false
		else
			return true
		end
	end,

	FormatMessage = function(message, messageID, destinationID)
		return {
			content = textutils.serialize(message),
			senderID = os.getComputerID(),
			senderName = os.getComputerLabel(),
			channel = channel,
			replyChannel = reply,
			messageID = messageID or math.random(10000),
			destinationID = destinationID
		}
	end,

	Timeout = function(func, time)
		time = time or 1
		parallel.waitForAny(func, function()
			sleep(time)
			--log('Timeout!'..time)
		end)
	end,

	RecieveMessage = function(_channel, messageID, timeout)
		open(_channel)
		local done = false
		local event, side, channel, replyChannel, message = nil
		Timeout(function()
			while not done do
				event, side, channel, replyChannel, message = os.pullEvent('modem_message')
				if channel ~= _channel then
					event, side, channel, replyChannel, message = nil
				else
					message = textutils.unserialize(message)
					message.content = textutils.unserialize(message.content)
					if messageID and messageID ~= message.messageID or (message.destinationID ~= nil and message.destinationID ~= os.getComputerID()) then
						event, side, channel, replyChannel, message = nil
					else
						done = true
					end
				end
			end
		end,
		timeout)
		return event, side, channel, replyChannel, message
	end,

	Initialise = function()
		if Wireless.Present() then
			for i, c in pairs(Wireless.Channels) do
				Wireless.Open(c)
			end
		end
	end,

	HandleMessage = function(event, side, channel, replyChannel, message, distance)
		message = textutils.unserialize(message)
		message.content = textutils.unserialize(message.content)

		if channel == Wireless.Channels.Ping then
			if message.content == 'Ping!' then
				SendMessage(replyChannel, 'Pong!', nil, message.messageID)
			end
		elseif message.destinationID ~= nil and message.destinationID ~= os.getComputerID() then
		elseif Wireless.Responder then
			Wireless.Responder(event, side, channel, replyChannel, message, distance)
		end
	end,

	SendMessage = function(channel, message, reply, messageID, destinationID)
		reply = reply or channel + 1
		Wireless.Open(channel)
		Wireless.Open(reply)
		local _message = Wireless.FormatMessage(message, messageID, destinationID)
		Wireless.transmit(channel, reply, _message)
		return _message
	end,

	Ping = function()
		local message = SendMessage(Channels.Ping, 'Ping!', Channels.PingReply)
		RecieveMessage(Channels.PingReply, message.messageID)
	end
}

function GetAbsolutePosition(object)
	local obj = object
	local i = 0
	local x = 1
	local y = 1
	while true do
		x = x + obj.X - 1
		y = y + obj.Y - 1

		if not obj.Parent then
			return {X = x, Y = y}
		end

		obj = obj.Parent

		if i > 32 then
			return {X = 1, Y = 1}
		end

		i = i + 1
	end

end

function Draw()
	Drawing.Clear(colours.white)

	if Current.StatusScreen then
		Drawing.DrawCharactersCenter(1, -2, nil, nil, Current.HeaderText, colours.blue, colours.white)
		Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by oeed', colours.lightGrey, colours.white)
		Drawing.DrawCharactersCenter(1, 1, nil, nil, Current.StatusText, Current.StatusColour, colours.white)
	end

	if Current.ButtonOne then
		Current.ButtonOne:Draw()
	end

	if Current.ButtonTwo then
		Current.ButtonTwo:Draw()
	end

	for i, v in ipairs(Current.PageControls) do
		v:Draw()
	end

	Drawing.DrawBuffer()

	if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then
		term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2])
		term.setCursorBlink(true)
		term.setTextColour(Current.CursorColour)
	else
		term.setCursorBlink(false)
	end
end
MainDraw = Draw

function GenerateFingerprint()
    local str = ""
    for _ = 1, 256 do
        local char = math.random(32, 126)
        --if char == 96 then char = math.random(32, 95) end
        str = str .. string.char(char)
    end
    return str
end

function MakeFingerprint()
	local h = fs.open('.fingerprint', 'w')
	if h then
		h.write(GenerateFingerprint())
	end
	h.close()
	Current.Fingerprint = str
end

local drawTimer = nil
function SetText(header, status, colour, isReset)
	if header then
		Current.HeaderText = header
	end
	if status then
		Current.StatusText = status
	end
	if colour then
		Current.StatusColour = colour
	end
	Draw()
	if not isReset then
		statusResetTimer = os.startTimer(2)
	end
end

function ResetStatus()
	if pocket then
		if Current.Locked then
			SetText('Ultimate Door Lock', 'Add Wireless Modem to PDA', colours.red, true)
		else
			SetText('Ultimate Door Lock', 'Ready', colours.grey, true)
		end
	else
		if Current.Locked then
			SetText('Ultimate Door Lock', ' Attach a Wireless Modem then reboot', colours.red, true)
		else
			SetText('Ultimate Door Lock', 'Ready', colours.grey, true)
		end
	end
end

function ResetPage()
	Wireless.Responder = function()end
	pingTimer = nil
	Current.PageControls = nil
	Current.StatusScreen = false
	Current.ButtonOne = nil
	Current.ButtonTwo = nil
	Current.PageControls = {}
	CloseDoor()
end

function PocketInitialise()
	Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
	if not Wireless.Present() then
		Current.Locked = true
		ResetStatus()
		return
	end
	Wireless.Initialise()
	ResetStatus()
	if fs.exists('.fingerprint') then
		local h = fs.open('.fingerprint', 'r')
		if h then
			Current.Fingerprint = h.readAll()
		else
			MakeFingerprint()
		end
		h.close()
	else
		MakeFingerprint()
	end

	Wireless.Responder = function(event, side, channel, replyChannel, message, distance)
		if channel == Wireless.Channels.UltimateDoorlockPing then
			Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequest, Current.Fingerprint, Wireless.Channels.UltimateDoorlockRequestReply, nil, message.senderID)
		elseif channel == Wireless.Channels.UltimateDoorlockRequestReply then
			if message.content == true then
				SetText(nil, 'Opening Door', colours.green)
			else
				SetText(nil, ' Access Denied', colours.red)
			end
		end
	end
end

function FingerprintIsOnWhitelist(fingerprint)
	if Current.Settings.Whitelist then
		for i, f in ipairs(Current.Settings.Whitelist) do
			if f == fingerprint then
				return true
			end
		end
	end
	return false
end

function SaveSettings()
	Current.Settings = Current.Settings or {}
	local h = fs.open('.settings', 'w')
	if h then
		h.write(textutils.serialize(Current.Settings))
	end
	h.close()	
end

local closeDoorTimer = nil
function OpenDoor()
	if Current.Settings and Current.Settings.RedstoneSide then
		SetText(nil, 'Opening Door', colours.green)
		redstone.setOutput(Current.Settings.RedstoneSide, true)
		closeDoorTimer = os.startTimer(0.6)
	end
end

function CloseDoor()
	if Current.Settings and Current.Settings.RedstoneSide then
		if redstone.getOutput(Current.Settings.RedstoneSide) then
			SetText(nil, 'Closing Door', colours.orange)
			redstone.setOutput(Current.Settings.RedstoneSide, false)
		end
	end
end

DefaultSettings = {
	Whitelist = {},
	RedstoneSide = 'back',
	Distance = 10
}

function RegisterPDA(event, drive)
	if disk.hasData(drive) then
		local _fs = fs
		if OneOS then
			_fs = OneOS.FS
		end
		local path = disk.getMountPath(drive)
		local addStartup = true
		if _fs.exists(path..'/System/') then
			path = path..'/System/'
			addStartup = false
		end
		local fingerprint = nil
		if _fs.exists(path..'/.fingerprint') then
			local h = _fs.open(path..'/.fingerprint', 'r')
			if h then
				local str = h.readAll()
				if #str == 256 then
					fingerprint = str
				end
			end
			h.close()
		end
		if not fingerprint then
			fingerprint = GenerateFingerprint()
			local h = _fs.open(path..'/.fingerprint', 'w')
			h.write(fingerprint)
			h.close()
			if addStartup then
				local h = fs.open(shell.getRunningProgram(), 'r')
				local startup = h.readAll()
				h.close()
				local h = _fs.open(path..'/startup', 'w')
				h.write(startup)
				h.close()
			end
		end
		if not FingerprintIsOnWhitelist(fingerprint) then
			table.insert(Current.Settings.Whitelist, fingerprint)
			SaveSettings()
		end
		disk.eject(drive)
		SetText(nil, 'Registered Pocket Computer', colours.green)
	end
end

function HostSetup()
	ResetPage()
	Current.Page = 'HostSetup'
	Current.ButtonTwo = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostStatusPage, 'Save', colours.black)
	if not Current.Settings then
		Current.Settings = DefaultSettings
	end

	local sideButtons = {}
	local function resetSideToggle(self)
		for i, v in ipairs(sideButtons) do
			if v.Toggle ~= nil then
				v.Toggle = false
			end
		end
		Current.Settings.RedstoneSide = self.Text:lower()
		SaveSettings()
	end

	table.insert(Current.PageControls, Label:Initialise(2, 2, 'Redstone Side'))
	sideButtons = {
		Button:Initialise(2, 4, nil, nil, nil, nil, resetSideToggle, 'Back', colours.black, false, colours.green),
		Button:Initialise(9, 4, nil, nil, nil, nil, resetSideToggle, 'Front', colours.black, false, colours.green),
		Button:Initialise(2, 6, nil, nil, nil, nil, resetSideToggle, 'Left', colours.black, false, colours.green),
		Button:Initialise(9, 6, nil, nil, nil, nil, resetSideToggle, 'Right', colours.black, false, colours.green),
		Button:Initialise(2, 8, nil, nil, nil, nil, resetSideToggle, 'Top', colours.black, false, colours.green),
		Button:Initialise(8, 8, nil, nil, nil, nil, resetSideToggle, 'Bottom', colours.black, false, colours.green)
	}
  	for i, v in ipairs(sideButtons) do
  		if v.Text:lower() == Current.Settings.RedstoneSide then
  			v.Toggle = true
  		end
  		table.insert(Current.PageControls, v)
  	end

	local distanceButtons = {}
	local function resetDistanceToggle(self)
		for i, v in ipairs(distanceButtons) do
			if v.Toggle ~= nil then
				v.Toggle = false
			end
		end
  		if self.Text == 'Small' then
  			Current.Settings.Distance = 5
  		elseif self.Text == 'Normal' then
  			Current.Settings.Distance = 10
  		elseif self.Text == 'Far' then
  			Current.Settings.Distance = 15
  		end
		SaveSettings()
	end

	table.insert(Current.PageControls, Label:Initialise(23, 2, 'Opening Distance'))
	distanceButtons = {
		Button:Initialise(23, 4, nil, nil, nil, nil, resetDistanceToggle, 'Small', colours.black, false, colours.green),
		Button:Initialise(31, 4, nil, nil, nil, nil, resetDistanceToggle, 'Normal', colours.black, false, colours.green),
		Button:Initialise(40, 4, nil, nil, nil, nil, resetDistanceToggle, 'Far', colours.black, false, colours.green)
	}
  	for i, v in ipairs(distanceButtons) do
  		if v.Text == 'Small' and Current.Settings.Distance == 5 then
  			v.Toggle = true
  		elseif v.Text == 'Normal' and Current.Settings.Distance == 10 then
  			v.Toggle = true
  		elseif v.Text == 'Far' and Current.Settings.Distance == 15 then
  			v.Toggle = true
  		end
  		table.insert(Current.PageControls, v)
  	end

	table.insert(Current.PageControls, Label:Initialise(2, 10, 'Registered PDAs: '..#Current.Settings.Whitelist))
	table.insert(Current.PageControls, Button:Initialise(2, 12, nil, nil, nil, nil, function()Current.Settings.Whitelist = {}HostSetup()end, 'Unregister All', colours.black))

  	
  	table.insert(Current.PageControls, Label:Initialise(23, 6, 'Help', colours.black))
  	local helpLines = {
  		Label:Initialise(23, 8, 'To register a new PDA simply', colours.black),
  		Label:Initialise(23, 9, 'place a Disk Drive next to', colours.black),
  		Label:Initialise(23, 10, 'the computer, then put the', colours.black),
  		Label:Initialise(23, 11, 'PDA in the Drive, it will', colours.black),
  		Label:Initialise(23, 12, 'register automatically. If', colours.black),
  		Label:Initialise(23, 13, 'it worked it will eject.', colours.black),
  		Label:Initialise(23, 15, 'Make sure you hide this', colours.red),
  		Label:Initialise(23, 16, 'computer away from the', colours.red),
  		Label:Initialise(23, 17, 'door! (other people)', colours.red)
  	}
  	for i, v in ipairs(helpLines) do
  		table.insert(Current.PageControls, v)
  	end


	table.insert(Current.PageControls, Button:Initialise(2, 14, nil, nil, nil, nil, function()
	  	for i = 1, 6 do
	  		helpLines[i].TextColour = colours.green
	  	end
	end, 'Register New PDA', colours.black))

end

function HostStatusPage()
	ResetPage()
	Current.Page = 'HostStatus'
	Current.StatusScreen = true
	Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
	Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostSetup, 'Settings/Help', colours.black)

	Wireless.Responder = function(event, side, channel, replyChannel, message, distance)
		if channel == Wireless.Channels.UltimateDoorlockRequest and distance < Current.Settings.Distance then
			if FingerprintIsOnWhitelist(message.content) then
				OpenDoor()
				Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, true)
			else
				Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, false)
			end
		end
	end

	PingPocketComputers()
end

function HostInitialise()
	if not Wireless.Present() then
		Current.Locked = true
		Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
		Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, function()os.reboot()end, 'Reboot', colours.black)
		ResetStatus()
		return
	end
	Wireless.Initialise()
	ResetStatus()
	if fs.exists('.settings') then
		local h = fs.open('.settings', 'r')
		if h then
			Current.Settings = textutils.unserialize(h.readAll())
		end
		h.close()
		HostStatusPage()		
	else
		HostSetup()
	end
	if OneOS then
		OneOS.CanClose = function()
			CloseDoor()
			return true
		end
	end
end

local pingTimer = nil
function PingPocketComputers()
	Wireless.SendMessage(Wireless.Channels.UltimateDoorlockPing, 'Ping!', Wireless.Channels.UltimateDoorlockRequest)
	pingTimer = os.startTimer(0.5)
end

function Initialise(arg)
	EventRegister('mouse_click', TryClick)
	EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end)
	EventRegister('mouse_scroll', Scroll)
	EventRegister('key', HandleKey)
	EventRegister('char', HandleKey)
	EventRegister('timer', Timer)
	EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end)
	EventRegister('modem_message', Wireless.HandleMessage)
	EventRegister('disk', RegisterPDA)

	if OneOS then
		OneOS.RequestRunAtStartup()
	end

	if pocket then
		PocketInitialise()
	else
		HostInitialise()
	end


	Draw()

	EventHandler()
end

function Timer(event, timer)
	if timer == pingTimer then
		PingPocketComputers()
	elseif timer == closeDoorTimer then
		CloseDoor()
	elseif timer == statusResetTimer then
		ResetStatus()
	end
end

local ignoreNextChar = false
function HandleKey(...)
	local args = {...}
	local event = args[1]
	local keychar = args[2]
	--[[
																							--Mac left command character
	if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then
		isControlPushed = true
		controlPushedTimer = os.startTimer(0.5)
	elseif isControlPushed then
		if event == 'key' then
			if CheckKeyboardShortcut(keychar) then
				isControlPushed = false
				ignoreNextChar = true
			end
		end
	elseif ignoreNextChar then
		ignoreNextChar = false
	elseif Current.TextInput then
		if event == 'char' then
			Current.TextInput:Char(keychar)
		elseif event == 'key' then
			Current.TextInput:Key(keychar)
		end
	end
	]]--
end

--[[
	Check if the given object falls under the click coordinates
]]--
function CheckClick(object, x, y)
	if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then
		return true
	end
end

--[[
	Attempt to clicka given object
]]--
function DoClick(object, side, x, y, drag)
	local obj = GetAbsolutePosition(object)
	obj.Width = object.Width
	obj.Height = object.Height
	if object and CheckClick(obj, x, y) then
		return object:Click(side, x - object.X + 1, y - object.Y + 1, drag)
	end	
end

--[[
	Try to click at the given coordinates
]]--
function TryClick(event, side, x, y, drag)
	if Current.ButtonOne then
		if DoClick(Current.ButtonOne, side, x, y, drag) then
			Draw()
			return
		end
	end

	if Current.ButtonTwo then
		if DoClick(Current.ButtonTwo, side, x, y, drag) then
			Draw()
			return
		end
	end

	for i, v in ipairs(Current.PageControls) do
		if DoClick(v, side, x, y, drag) then
			Draw()
			return
		end
	end

	Draw()
end

function Scroll(event, direction, x, y)
	if Current.Window and Current.Window.OpenButton then
		Current.Document.Scroll = Current.Document.Scroll + direction
		if Current.Window.Scroll < 0 then
			Current.Window.Scroll = 0
		elseif Current.Window.Scroll > Current.Window.MaxScroll then
			Current.Window.Scroll = Current.Window.MaxScroll
		end
		Draw()
	elseif Current.ScrollBar then
		if Current.ScrollBar:DoScroll(direction*2) then
			Draw()
		end
	end
end

--[[
	Registers functions to run on certain events
]]--
function EventRegister(event, func)
	if not Events[event] then
		Events[event] = {}
	end

	table.insert(Events[event], func)
end

--[[
	The main loop event handler, runs registered event functinos
]]--
function EventHandler()
	while isRunning do
		local event, arg1, arg2, arg3, arg4, arg5, arg6 = os.pullEventRaw()
		if Events[event] then
			for i, e in ipairs(Events[event]) do
				e(event, arg1, arg2, arg3, arg4, arg5, arg6)
			end
		end
	end
end

function Quit()
	isRunning = false
	term.setCursorPos(1,1)
	term.setBackgroundColour(colours.black)
	term.setTextColour(colours.white)
	term.clear()
	if OneOS then
		OneOS.Close()
	end
end

if not term.current then -- if not 1.6
	print('Because it requires pocket computers, Ultimate Door Lock requires ComputerCraft 1.6. Please update to 1.6 to use Ultimate Door Lock.')
elseif not (OneOS and pocket) and term.isColor and term.isColor() then
	-- If the program crashes close the door and reboot
	local _, err = pcall(Initialise)
	if err then
		CloseDoor()
		term.setCursorPos(1,1)
		term.setBackgroundColour(colours.black)
		term.setTextColour(colours.white)
		term.clear()
		print('Ultimate Door Lock has crashed')
		print('To maintain security, the computer will reboot.')
		print('If you are seeing this alot try turning off all Pocket Computers or reinstall.')
		print()
		print('Error:')
		printError(err)
		sleep(5)
		os.reboot()
	end
elseif OneOS and pocket then
	term.setCursorPos(1,3)
	term.setBackgroundColour(colours.white)
	term.setTextColour(colours.blue)
	term.clear()
	print('OneOS already acts as a door key. Simply place your PDA in the door\'s disk drive to register it.')
	print()
	print('To setup a door, run this program on an advanced computer (non-pocket).')
	print()
	print('Click anywhere to quit')
	os.pullEvent('mouse_click')
	Quit()
else
	print('Ultimate Door Lock requires an advanced (gold) computer or pocket computer.')
end