View difference between Paste ID: hD5y6uRx and 4QeAEiTM
SHOW: | | - or go back to the newest paste.
1
tArgs = {...}
2
3
if OneOS then
4
	--running under OneOS
5
	OneOS.ToolBarColour = colours.white
6
	OneOS.ToolBarTextColour = colours.grey
7
end
8
9
local _w, _h = term.getSize()
10
11
local round = function(num, idp)
12
	local mult = 10^(idp or 0)
13
	return math.floor(num * mult + 0.5) / mult
14
end
15
16
InterfaceElements = {}
17
18
Drawing = {
19
	
20
	Screen = {
21
		Width = _w,
22
		Height = _h
23
	},
24
25
	DrawCharacters = function (x, y, characters, textColour,bgColour)
26
		Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour)
27
	end,
28
	
29
	DrawBlankArea = function (x, y, w, h, colour)
30
		Drawing.DrawArea (x, y, w, h, " ", 1, colour)
31
	end,
32
33
	DrawArea = function (x, y, w, h, character, textColour, bgColour)
34
		--width must be greater than 1, other wise we get a stack overflow
35
		if w < 0 then
36
			w = w * -1
37
		elseif w == 0 then
38
			w = 1
39
		end
40
41
		for ix = 1, w do
42
			local currX = x + ix - 1
43
			for iy = 1, h do
44
				local currY = y + iy - 1
45
				Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour)
46
			end
47
		end
48
	end,
49
50
	DrawImage = function(_x,_y,tImage, w, h)
51
		if tImage then
52
			for y = 1, h do
53
				if not tImage[y] then
54
					break
55
				end
56
				for x = 1, w do
57
					if not tImage[y][x] then
58
						break
59
					end
60
					local bgColour = tImage[y][x]
61
		            local textColour = tImage.textcol[y][x] or colours.white
62
		            local char = tImage.text[y][x]
63
		            Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour)
64
				end
65
			end
66
		elseif w and h then
67
			Drawing.DrawBlankArea(x, y, w, h, colours.green)
68
		end
69
	end,
70
	--using .nft
71
	LoadImage = function(path)
72
		local image = {
73
			text = {},
74
			textcol = {}
75
		}
76
		local fs = fs
77
		if OneOS then
78
			fs = OneOS.FS
79
		end
80
		if fs.exists(path) then
81
			local _open = io.open
82
			if OneOS then
83
				_open = OneOS.IO.open
84
			end
85
	        local file = _open(path, "r")
86
	        local sLine = file:read()
87
	        local num = 1
88
	        while sLine do  
89
	                table.insert(image, num, {})
90
	                table.insert(image.text, num, {})
91
	                table.insert(image.textcol, num, {})
92
	                                            
93
	                --As we're no longer 1-1, we keep track of what index to write to
94
	                local writeIndex = 1
95
	                --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
96
	                local bgNext, fgNext = false, false
97
	                --The current background and foreground colours
98
	                local currBG, currFG = nil,nil
99
	                for i=1,#sLine do
100
	                        local nextChar = string.sub(sLine, i, i)
101
	                        if nextChar:byte() == 30 then
102
                                bgNext = true
103
	                        elseif nextChar:byte() == 31 then
104
                                fgNext = true
105
	                        elseif bgNext then
106
                                currBG = Drawing.GetColour(nextChar)
107
                                bgNext = false
108
	                        elseif fgNext then
109
                                currFG = Drawing.GetColour(nextChar)
110
                                fgNext = false
111
	                        else
112
                                if nextChar ~= " " and currFG == nil then
113
                                       currFG = colours.white
114
                                end
115
                                image[num][writeIndex] = currBG
116
                                image.textcol[num][writeIndex] = currFG
117
                                image.text[num][writeIndex] = nextChar
118
                                writeIndex = writeIndex + 1
119
	                        end
120
	                end
121
	                num = num+1
122
	                sLine = file:read()
123
	        end
124
	        file:close()
125
		end
126
	 	return image
127
	end,
128
129
	DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour)
130
		w = w or Drawing.Screen.Width
131
		h = h or Drawing.Screen.Height
132
		x = x or 0
133
		y = y or 0
134
		x = math.ceil((w - #characters) / 2) + x
135
		y = math.floor(h / 2) + y
136
137
		Drawing.DrawCharacters(x, y, characters, textColour, bgColour)
138
	end,
139
140
	GetColour = function(hex)
141
		if hex == ' ' then
142
			return colours.transparent
143
		end
144
	    local value = tonumber(hex, 16)
145
	    if not value then return nil end
146
	    value = math.pow(2,value)
147
	    return value
148
	end,
149
150
	Clear = function (_colour)
151
		_colour = _colour or colours.black
152
		Drawing.ClearBuffer()
153
		Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour)
154
	end,
155
156
	Buffer = {},
157
	BackBuffer = {},
158
159
	DrawBuffer = function()
160
		for y,row in pairs(Drawing.Buffer) do
161
			for x,pixel in pairs(row) do
162
				local shouldDraw = true
163
				local hasBackBuffer = true
164
				if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then
165
					hasBackBuffer = false
166
				end
167
				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
168
					shouldDraw = false
169
				end
170
				if shouldDraw then
171
					term.setBackgroundColour(pixel[3])
172
					term.setTextColour(pixel[2])
173
					term.setCursorPos(x, y)
174
					term.write(pixel[1])
175
				end
176
			end
177
		end
178
		Drawing.BackBuffer = Drawing.Buffer
179
		Drawing.Buffer = {}
180
		term.setCursorPos(1,1)
181
	end,
182
183
	ClearBuffer = function()
184
		Drawing.Buffer = {}
185
	end,
186
187
	WriteStringToBuffer = function (x, y, characters, textColour,bgColour)
188
		for i = 1, #characters do
189
   			local character = characters:sub(i,i)
190
   			Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour)
191
		end
192
	end,
193
194
	WriteToBuffer = function(x, y, character, textColour,bgColour)
195
		x = round(x)
196
		y = round(y)
197
		if bgColour == colours.transparent then
198
			Drawing.Buffer[y] = Drawing.Buffer[y] or {}
199
			Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black}
200
			Drawing.Buffer[y][x][1] = character
201
			Drawing.Buffer[y][x][2] = textColour
202
		else
203
			Drawing.Buffer[y] = Drawing.Buffer[y] or {}
204
			Drawing.Buffer[y][x] = {character, textColour, bgColour}
205
		end
206
	end,
207
}
208
209
Current = {
210
	Document = nil,
211
	TextInput = nil,
212
	CursorPos = {1,1},
213
	CursorColour = colours.black,
214
	Selection = {8, 36},
215
	Window = nil,
216
	HeaderText = '',
217
	StatusText = '',
218
	StatusColour = colours.grey,
219
	StatusScreen = true,
220
	ButtonOne = nil,
221
	ButtonTwo = nil,
222
	Locked = false,
223
	Page = '',
224
	PageControls = {}
225
}
226
227
isRunning = true
228
229
Events = {}
230
231
Button = {
232
	X = 1,
233
	Y = 1,
234
	Width = 0,
235
	Height = 0,
236
	BackgroundColour = colours.lightGrey,
237
	TextColour = colours.white,
238
	ActiveBackgroundColour = colours.lightGrey,
239
	Text = "",
240
	Parent = nil,
241
	_Click = nil,
242
	Toggle = nil,
243
244
	AbsolutePosition = function(self)
245
		return self.Parent:AbsolutePosition()
246
	end,
247
248
	Draw = function(self)
249
		local bg = self.BackgroundColour
250
		local tc = self.TextColour
251
		if type(bg) == 'function' then
252
			bg = bg()
253
		end
254
255
		if self.Toggle then
256
			tc = colours.white
257
			bg = self.ActiveBackgroundColour
258
		end
259
260
		local pos = GetAbsolutePosition(self)
261
		Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg)
262
		Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg)
263
	end,
264
265
	Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour)
266
		local new = {}    -- the new instance
267
		setmetatable( new, {__index = self} )
268
		height = height or 1
269
		new.Width = width or #text + 2
270
		new.Height = height
271
		new.Y = y
272
		new.X = x
273
		new.Text = text or ""
274
		new.BackgroundColour = backgroundColour or colours.lightGrey
275
		new.TextColour = textColour or colours.white
276
		new.ActiveBackgroundColour = activeBackgroundColour or colours.lightBlue
277
		new.Parent = parent
278
		new._Click = click
279
		new.Toggle = toggle
280
		return new
281
	end,
282
283
	Click = function(self, side, x, y)
284
		if self._Click then
285
			if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then
286
				self.Toggle = not self.Toggle
287
				Draw()
288
			end
289
			return true
290
		else
291
			return false
292
		end
293
	end
294
}
295
296
Label = {
297
	X = 1,
298
	Y = 1,
299
	Width = 0,
300
	Height = 0,
301
	BackgroundColour = colours.lightGrey,
302
	TextColour = colours.white,
303
	Text = "",
304
	Parent = nil,
305
306
	AbsolutePosition = function(self)
307
		return self.Parent:AbsolutePosition()
308
	end,
309
310
	Draw = function(self)
311
		local bg = self.BackgroundColour
312
		local tc = self.TextColour
313
314
		if self.Toggle then
315
			tc = UIColours.MenuBarActive
316
			bg = self.ActiveBackgroundColour
317
		end
318
319
		local pos = GetAbsolutePosition(self)
320
		Drawing.DrawCharacters(pos.X, pos.Y, self.Text, self.TextColour, self.BackgroundColour)
321
	end,
322
323
	Initialise = function(self, x, y, text, textColour, backgroundColour, parent)
324
		local new = {}    -- the new instance
325
		setmetatable( new, {__index = self} )
326
		height = height or 1
327
		new.Width = width or #text + 2
328
		new.Height = height
329
		new.Y = y
330
		new.X = x
331
		new.Text = text or ""
332
		new.BackgroundColour = backgroundColour or colours.white
333
		new.TextColour = textColour or colours.black
334
		new.Parent = parent
335
		return new
336
	end,
337
338
	Click = function(self, side, x, y)
339
		return false
340
	end
341
}
342
343
TextBox = {
344
	X = 1,
345
	Y = 1,
346
	Width = 0,
347
	Height = 0,
348
	BackgroundColour = colours.lightGrey,
349
	TextColour = colours.black,
350
	Parent = nil,
351
	TextInput = nil,
352
	Placeholder = '',
353
354
	AbsolutePosition = function(self)
355
		return self.Parent:AbsolutePosition()
356
	end,
357
358
	Draw = function(self)		
359
		local pos = GetAbsolutePosition(self)
360
		Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour)
361
		local text = self.TextInput.Value
362
		if #tostring(text) > (self.Width - 2) then
363
			text = text:sub(#text-(self.Width - 3))
364
			if Current.TextInput == self.TextInput then
365
				Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y}
366
			end
367
		else
368
			if Current.TextInput == self.TextInput then
369
				Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y}
370
			end
371
		end
372
		
373
		if #tostring(text) == 0 then
374
			Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour)
375
		else
376
			Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour)
377
		end
378
379
		term.setCursorBlink(true)
380
		
381
		Current.CursorColour = self.TextColour
382
	end,
383
384
	Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical)
385
		local new = {}    -- the new instance
386
		setmetatable( new, {__index = self} )
387
		height = height or 1
388
		new.Width = width or #text + 2
389
		new.Height = height
390
		new.Y = y
391
		new.X = x
392
		new.TextInput = TextInput:Initialise(text or '', function(key)
393
			if done then
394
				done(key)
395
			end
396
			Draw()
397
		end, numerical)
398
		new.BackgroundColour = backgroundColour or colours.lightGrey
399
		new.TextColour = textColour or colours.black
400
		new.Parent = parent
401
		return new
402
	end,
403
404
	Click = function(self, side, x, y)
405
		Current.Input = self.TextInput
406
		self:Draw()
407
	end
408
}
409
410
TextInput = {
411
	Value = "",
412
	Change = nil,
413
	CursorPos = nil,
414
	Numerical = false,
415
	IsDocument = nil,
416
417
	Initialise = function(self, value, change, numerical, isDocument)
418
		local new = {}    -- the new instance
419
		setmetatable( new, {__index = self} )
420
		new.Value = tostring(value)
421
		new.Change = change
422
		new.CursorPos = #tostring(value)
423
		new.Numerical = numerical
424
		new.IsDocument = isDocument or false
425
		return new
426
	end,
427
428
	Insert = function(self, str)
429
		if self.Numerical then
430
			str = tostring(tonumber(str))
431
		end
432
433
		local selection = OrderSelection()
434
435
		if self.IsDocument and selection then
436
			self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2)
437
			self.CursorPos = selection[1]
438
			Current.Selection = nil
439
		else
440
			local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')
441
442
			self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1  + newLineAdjust)
443
			self.CursorPos = self.CursorPos + 1
444
		end
445
		
446
		self.Change(key)
447
	end,
448
449
	Extract = function(self, remove)
450
		local selection = OrderSelection()
451
		if self.IsDocument and selection then
452
			local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','')
453
			local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust)
454
			if remove then
455
				self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust)
456
				self.CursorPos = selection[1] - 1
457
				Current.Selection = nil
458
			end
459
			return str
460
		end
461
	end,
462
463
	Char = function(self, char)
464
		if char == 'nil' then
465
			return
466
		end
467
		self:Insert(char)
468
	end,
469
470
	Key = function(self, key)
471
		if key == keys.enter then
472
			if self.IsDocument then
473
				self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 )
474
				self.CursorPos = self.CursorPos + 1
475
			end
476
			self.Change(key)		
477
		elseif key == keys.left then
478
			-- Left
479
			if self.CursorPos > 0 then
480
				local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
481
				self.CursorPos = self.CursorPos - 1 - colShift
482
				self.Change(key)
483
			end
484
			
485
		elseif key == keys.right then
486
			-- Right				
487
			if self.CursorPos < string.len(self.Value) then
488
				local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1))
489
				self.CursorPos = self.CursorPos + 1 + colShift
490
				self.Change(key)
491
			end
492
		
493
		elseif key == keys.backspace then
494
			-- Backspace
495
			if self.IsDocument and Current.Selection then
496
				self:Extract(true)
497
				self.Change(key)
498
			elseif self.CursorPos > 0 then
499
				local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
500
				local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')
501
502
				self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust)
503
				self.CursorPos = self.CursorPos - 1 - colShift
504
				self.Change(key)
505
			end
506
		elseif key == keys.home then
507
			-- Home
508
			self.CursorPos = 0
509
			self.Change(key)
510
		elseif key == keys.delete then
511
			if self.IsDocument and Current.Selection then
512
				self:Extract(true)
513
				self.Change(key)
514
			elseif self.CursorPos < string.len(self.Value) then
515
				self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 )				
516
				self.Change(key)
517
			end
518
		elseif key == keys["end"] then
519
			-- End
520
			self.CursorPos = string.len(self.Value)
521
			self.Change(key)
522
		elseif key == keys.up and self.IsDocument then
523
			-- Up
524
			if Current.Document.CursorPos then
525
				local page = Current.Document.Pages[Current.Document.CursorPos.Page]
526
				self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true)
527
				self.Change(key)
528
			end
529
		elseif key == keys.down and self.IsDocument then
530
			-- Down
531
			if Current.Document.CursorPos then
532
				local page = Current.Document.Pages[Current.Document.CursorPos.Page]
533
				self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true)
534
				self.Change(key)
535
			end
536
		end
537
	end
538
}
539
540
local Capitalise = function(str)
541
	return str:sub(1, 1):upper() .. str:sub(2, -1)
542
end
543
544
545
local getNames = peripheral.getNames or function()
546
    local tResults = {}
547
    for n,sSide in ipairs( rs.getSides() ) do
548
        if peripheral.isPresent( sSide ) then
549
            table.insert( tResults, sSide )
550
            local isWireless = false
551
            if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
552
                isWireless = true
553
            end     
554
            if peripheral.getType( sSide ) == "modem" and not isWireless then
555
                local tRemote = peripheral.call( sSide, "getNamesRemote" )
556
                for n,sName in ipairs( tRemote ) do
557
                    table.insert( tResults, sName )
558
                end
559
            end
560
        end
561
    end
562
    return tResults
563
end
564
565
Peripheral = {
566
    GetPeripheral = function(_type)
567
        for i, p in ipairs(Peripheral.GetPeripherals()) do
568
            if p.Type == _type then
569
                return p
570
            end
571
        end
572
    end,
573
574
    Call = function(type, ...)
575
        local tArgs = {...}
576
        local p = Peripheral.GetPeripheral(type)
577
        peripheral.call(p.Side, unpack(tArgs))
578
    end,
579
580
    GetPeripherals = function(filterType)
581
        local peripherals = {}
582
        for i, side in ipairs(getNames()) do
583
            local name = peripheral.getType(side):gsub("^%l", string.upper)
584
            local code = string.upper(side:sub(1,1))
585
            if side:find('_') then
586
                code = side:sub(side:find('_')+1)
587
            end
588
589
            local dupe = false
590
            for i, v in ipairs(peripherals) do
591
                if v[1] == name .. ' ' .. code then
592
                    dupe = true
593
                end
594
            end
595
596
            if not dupe then
597
                local _type = peripheral.getType(side)
598
                local isWireless = false
599
                if _type == 'modem' then
600
                    if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
601
                        isWireless = true
602
                    end     
603
                    if isWireless then
604
                        _type = 'wireless_modem'
605
                        name = 'W '..name
606
                    end
607
                end
608
                if not filterType or _type == filterType then
609
                    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})
610
                end
611
            end
612
        end
613
        return peripherals
614
    end,
615
616
    PresentNamed = function(name)
617
        return peripheral.isPresent(name)
618
    end,
619
620
    CallType = function(type, ...)
621
        local tArgs = {...}
622
        local p = Peripheral.GetPeripheral(type)
623
        return peripheral.call(p.Side, unpack(tArgs))
624
    end,
625
626
    CallNamed = function(name, ...)
627
        local tArgs = {...}
628
        return peripheral.call(name, unpack(tArgs))
629
    end
630
}
631
632
Wireless = {
633
	Channels = {
634
		UltimateDoorlockPing = 4210,
635
		UltimateDoorlockRequest = 4211,
636
		UltimateDoorlockRequestReply = 4212,
637
	},
638
639
	isOpen = function(channel)
640
		return Peripheral.CallType('wireless_modem', 'isOpen', channel)
641
	end,
642
643
	Open = function(channel)
644
		if not Wireless.isOpen(channel) then
645
			Peripheral.CallType('wireless_modem', 'open', channel)
646
		end
647
	end,
648
649
	close = function(channel)
650
		Peripheral.CallType('wireless_modem', 'close', channel)
651
	end,
652
653
	closeAll = function()
654
		Peripheral.CallType('wireless_modem', 'closeAll')
655
	end,
656
657
	transmit = function(channel, replyChannel, message)
658
		Peripheral.CallType('wireless_modem', 'transmit', channel, replyChannel, textutils.serialize(message))
659
	end,
660
661
	Present = function()
662
		if Peripheral.GetPeripheral('wireless_modem') == nil then
663
			return false
664
		else
665
			return true
666
		end
667
	end,
668
669
	FormatMessage = function(message, messageID, destinationID)
670
		return {
671
			content = textutils.serialize(message),
672
			senderID = os.getComputerID(),
673
			senderName = os.getComputerLabel(),
674
			channel = channel,
675
			replyChannel = reply,
676
			messageID = messageID or math.random(10000),
677
			destinationID = destinationID
678
		}
679
	end,
680
681
	Timeout = function(func, time)
682
		time = time or 1
683
		parallel.waitForAny(func, function()
684
			sleep(time)
685
			--log('Timeout!'..time)
686
		end)
687
	end,
688
689
	RecieveMessage = function(_channel, messageID, timeout)
690
		open(_channel)
691
		local done = false
692
		local event, side, channel, replyChannel, message = nil
693
		Timeout(function()
694
			while not done do
695
				event, side, channel, replyChannel, message = os.pullEvent('modem_message')
696
				if channel ~= _channel then
697
					event, side, channel, replyChannel, message = nil
698
				else
699
					message = textutils.unserialize(message)
700
					message.content = textutils.unserialize(message.content)
701
					if messageID and messageID ~= message.messageID or (message.destinationID ~= nil and message.destinationID ~= os.getComputerID()) then
702
						event, side, channel, replyChannel, message = nil
703
					else
704
						done = true
705
					end
706
				end
707
			end
708
		end,
709
		timeout)
710
		return event, side, channel, replyChannel, message
711
	end,
712
713
	Initialise = function()
714
		if Wireless.Present() then
715
			for i, c in pairs(Wireless.Channels) do
716
				Wireless.Open(c)
717
			end
718
		end
719
	end,
720
721
	HandleMessage = function(event, side, channel, replyChannel, message, distance)
722
		message = textutils.unserialize(message)
723
		message.content = textutils.unserialize(message.content)
724
725
		if channel == Wireless.Channels.Ping then
726
			if message.content == 'Ping!' then
727
				SendMessage(replyChannel, 'Pong!', nil, message.messageID)
728
			end
729
		elseif message.destinationID ~= nil and message.destinationID ~= os.getComputerID() then
730
		elseif Wireless.Responder then
731
			Wireless.Responder(event, side, channel, replyChannel, message, distance)
732
		end
733
	end,
734
735
	SendMessage = function(channel, message, reply, messageID, destinationID)
736
		reply = reply or channel + 1
737
		Wireless.Open(channel)
738
		Wireless.Open(reply)
739
		local _message = Wireless.FormatMessage(message, messageID, destinationID)
740
		Wireless.transmit(channel, reply, _message)
741
		return _message
742
	end,
743
744
	Ping = function()
745
		local message = SendMessage(Channels.Ping, 'Ping!', Channels.PingReply)
746
		RecieveMessage(Channels.PingReply, message.messageID)
747
	end
748
}
749
750
function GetAbsolutePosition(object)
751
	local obj = object
752
	local i = 0
753
	local x = 1
754
	local y = 1
755
	while true do
756
		x = x + obj.X - 1
757
		y = y + obj.Y - 1
758
759
		if not obj.Parent then
760
			return {X = x, Y = y}
761
		end
762
763
		obj = obj.Parent
764
765
		if i > 32 then
766
			return {X = 1, Y = 1}
767
		end
768
769
		i = i + 1
770
	end
771
772
end
773
774
function Draw()
775
	Drawing.Clear(colours.white)
776
777
	if Current.StatusScreen then
778
		Drawing.DrawCharactersCenter(1, -2, nil, nil, Current.HeaderText, colours.blue, colours.white)
779
		Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by oeed', colours.lightGrey, colours.white)
780
		Drawing.DrawCharactersCenter(1, 1, nil, nil, Current.StatusText, Current.StatusColour, colours.white)
781
	end
782
783
	if Current.ButtonOne then
784
		Current.ButtonOne:Draw()
785
	end
786
787
	if Current.ButtonTwo then
788
		Current.ButtonTwo:Draw()
789
	end
790
791
	for i, v in ipairs(Current.PageControls) do
792
		v:Draw()
793
	end
794
795
	Drawing.DrawBuffer()
796
797
	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
798
		term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2])
799
		term.setCursorBlink(true)
800
		term.setTextColour(Current.CursorColour)
801
	else
802
		term.setCursorBlink(false)
803
	end
804
end
805
MainDraw = Draw
806
807
function GenerateFingerprint()
808
    local str = ""
809
    for _ = 1, 256 do
810
        local char = math.random(32, 126)
811
        --if char == 96 then char = math.random(32, 95) end
812
        str = str .. string.char(char)
813
    end
814
    return str
815
end
816
817
function MakeFingerprint()
818
	local h = fs.open('.fingerprint', 'w')
819
	if h then
820
		h.write(GenerateFingerprint())
821
	end
822
	h.close()
823
	Current.Fingerprint = str
824
end
825
826
local drawTimer = nil
827
function SetText(header, status, colour, isReset)
828
	if header then
829
		Current.HeaderText = header
830
	end
831
	if status then
832
		Current.StatusText = status
833
	end
834
	if colour then
835
		Current.StatusColour = colour
836
	end
837
	Draw()
838
	if not isReset then
839
		statusResetTimer = os.startTimer(2)
840
	end
841
end
842
843
function ResetStatus()
844
	if pocket then
845
		if Current.Locked then
846
			SetText('Ultimate Door Lock', 'Add Wireless Modem to PDA', colours.red, true)
847
		else
848
			SetText('Ultimate Door Lock', 'Ready', colours.grey, true)
849
		end
850
	else
851
		if Current.Locked then
852
			SetText('Ultimate Door Lock', ' Attach a Wireless Modem then reboot', colours.red, true)
853
		else
854
			SetText('Ultimate Door Lock', 'Ready', colours.grey, true)
855
		end
856
	end
857
end
858
859
function ResetPage()
860
	Wireless.Responder = function()end
861
	pingTimer = nil
862
	Current.PageControls = nil
863
	Current.StatusScreen = false
864
	Current.ButtonOne = nil
865
	Current.ButtonTwo = nil
866
	Current.PageControls = {}
867
	CloseDoor()
868
end
869
870
function PocketInitialise()
871
	Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
872
	if not Wireless.Present() then
873
		Current.Locked = true
874
		ResetStatus()
875
		return
876
	end
877
	Wireless.Initialise()
878
	ResetStatus()
879
	if fs.exists('.fingerprint') then
880
		local h = fs.open('.fingerprint', 'r')
881
		if h then
882
			Current.Fingerprint = h.readAll()
883
		else
884
			MakeFingerprint()
885
		end
886
		h.close()
887
	else
888
		MakeFingerprint()
889
	end
890
891
	Wireless.Responder = function(event, side, channel, replyChannel, message, distance)
892
		if channel == Wireless.Channels.UltimateDoorlockPing then
893
			Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequest, Current.Fingerprint, Wireless.Channels.UltimateDoorlockRequestReply, nil, message.senderID)
894
		elseif channel == Wireless.Channels.UltimateDoorlockRequestReply then
895
			if message.content == true then
896
				SetText(nil, 'Opening Door', colours.green)
897
			else
898
				SetText(nil, ' Access Denied', colours.red)
899
			end
900
		end
901
	end
902
end
903
904
function FingerprintIsOnWhitelist(fingerprint)
905
	if Current.Settings.Whitelist then
906
		for i, f in ipairs(Current.Settings.Whitelist) do
907
			if f == fingerprint then
908
				return true
909
			end
910
		end
911
	end
912
	return false
913
end
914
915
function SaveSettings()
916
	Current.Settings = Current.Settings or {}
917
	local h = fs.open('.settings', 'w')
918
	if h then
919
		h.write(textutils.serialize(Current.Settings))
920
	end
921
	h.close()	
922
end
923
924
local closeDoorTimer = nil
925
function OpenDoor()
926
	if Current.Settings and Current.Settings.RedstoneSide then
927
		SetText(nil, 'Opening Door', colours.green)
928
		redstone.setOutput(Current.Settings.RedstoneSide, true)
929
		closeDoorTimer = os.startTimer(0.6)
930
	end
931
end
932
933
function CloseDoor()
934
	if Current.Settings and Current.Settings.RedstoneSide then
935
		if redstone.getOutput(Current.Settings.RedstoneSide) then
936
			SetText(nil, 'Closing Door', colours.orange)
937
			redstone.setOutput(Current.Settings.RedstoneSide, false)
938
		end
939
	end
940
end
941
942
DefaultSettings = {
943
	Whitelist = {},
944
	RedstoneSide = 'back',
945
	Distance = 10
946
}
947
948
function RegisterPDA(event, drive)
949
	if disk.hasData(drive) then
950
		local _fs = fs
951
		if OneOS then
952
			_fs = OneOS.FS
953
		end
954
		local path = disk.getMountPath(drive)
955
		local addStartup = true
956
		if _fs.exists(path..'/System/') then
957
			path = path..'/System/'
958
			addStartup = false
959
		end
960
		local fingerprint = nil
961
		if _fs.exists(path..'/.fingerprint') then
962
			local h = _fs.open(path..'/.fingerprint', 'r')
963
			if h then
964
				local str = h.readAll()
965
				if #str == 256 then
966
					fingerprint = str
967
				end
968
			end
969
			h.close()
970
		end
971
		if not fingerprint then
972
			fingerprint = GenerateFingerprint()
973
			local h = _fs.open(path..'/.fingerprint', 'w')
974
			h.write(fingerprint)
975
			h.close()
976
			if addStartup then
977
				local h = fs.open(shell.getRunningProgram(), 'r')
978
				local startup = h.readAll()
979
				h.close()
980
				local h = _fs.open(path..'/startup', 'w')
981
				h.write(startup)
982
				h.close()
983
			end
984
		end
985
		if not FingerprintIsOnWhitelist(fingerprint) then
986
			table.insert(Current.Settings.Whitelist, fingerprint)
987
			SaveSettings()
988
		end
989
		disk.eject(drive)
990
		SetText(nil, 'Registered Pocket Computer', colours.green)
991
	end
992
end
993
994
function HostSetup()
995
	ResetPage()
996
	Current.Page = 'HostSetup'
997
	Current.ButtonTwo = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostStatusPage, 'Save', colours.black)
998
	if not Current.Settings then
999
		Current.Settings = DefaultSettings
1000
	end
1001
1002
	local sideButtons = {}
1003
	local function resetSideToggle(self)
1004
		for i, v in ipairs(sideButtons) do
1005
			if v.Toggle ~= nil then
1006
				v.Toggle = false
1007
			end
1008
		end
1009
		Current.Settings.RedstoneSide = self.Text:lower()
1010
		SaveSettings()
1011
	end
1012
1013
	table.insert(Current.PageControls, Label:Initialise(2, 2, 'Redstone Side'))
1014
	sideButtons = {
1015
		Button:Initialise(2, 4, nil, nil, nil, nil, resetSideToggle, 'Back', colours.black, false, colours.green),
1016
		Button:Initialise(9, 4, nil, nil, nil, nil, resetSideToggle, 'Front', colours.black, false, colours.green),
1017
		Button:Initialise(2, 6, nil, nil, nil, nil, resetSideToggle, 'Left', colours.black, false, colours.green),
1018
		Button:Initialise(9, 6, nil, nil, nil, nil, resetSideToggle, 'Right', colours.black, false, colours.green),
1019
		Button:Initialise(2, 8, nil, nil, nil, nil, resetSideToggle, 'Top', colours.black, false, colours.green),
1020
		Button:Initialise(8, 8, nil, nil, nil, nil, resetSideToggle, 'Bottom', colours.black, false, colours.green)
1021
	}
1022
  	for i, v in ipairs(sideButtons) do
1023
  		if v.Text:lower() == Current.Settings.RedstoneSide then
1024
  			v.Toggle = true
1025
  		end
1026
  		table.insert(Current.PageControls, v)
1027
  	end
1028
1029
	local distanceButtons = {}
1030
	local function resetDistanceToggle(self)
1031
		for i, v in ipairs(distanceButtons) do
1032
			if v.Toggle ~= nil then
1033
				v.Toggle = false
1034
			end
1035
		end
1036
  		if self.Text == 'Small' then
1037
  			Current.Settings.Distance = 5
1038
  		elseif self.Text == 'Normal' then
1039
  			Current.Settings.Distance = 10
1040
  		elseif self.Text == 'Far' then
1041
  			Current.Settings.Distance = 15
1042
  		end
1043
		SaveSettings()
1044
	end
1045
1046
	table.insert(Current.PageControls, Label:Initialise(23, 2, 'Opening Distance'))
1047
	distanceButtons = {
1048
		Button:Initialise(23, 4, nil, nil, nil, nil, resetDistanceToggle, 'Small', colours.black, false, colours.green),
1049
		Button:Initialise(31, 4, nil, nil, nil, nil, resetDistanceToggle, 'Normal', colours.black, false, colours.green),
1050
		Button:Initialise(40, 4, nil, nil, nil, nil, resetDistanceToggle, 'Far', colours.black, false, colours.green)
1051
	}
1052
  	for i, v in ipairs(distanceButtons) do
1053
  		if v.Text == 'Small' and Current.Settings.Distance == 5 then
1054
  			v.Toggle = true
1055
  		elseif v.Text == 'Normal' and Current.Settings.Distance == 10 then
1056
  			v.Toggle = true
1057
  		elseif v.Text == 'Far' and Current.Settings.Distance == 15 then
1058
  			v.Toggle = true
1059
  		end
1060
  		table.insert(Current.PageControls, v)
1061
  	end
1062
1063
	table.insert(Current.PageControls, Label:Initialise(2, 10, 'Registered PDAs: '..#Current.Settings.Whitelist))
1064
	table.insert(Current.PageControls, Button:Initialise(2, 12, nil, nil, nil, nil, function()Current.Settings.Whitelist = {}HostSetup()end, 'Unregister All', colours.black))
1065
1066
  	
1067
  	table.insert(Current.PageControls, Label:Initialise(23, 6, 'Help', colours.black))
1068
  	local helpLines = {
1069
  		Label:Initialise(23, 8, 'To register a new PDA simply', colours.black),
1070
  		Label:Initialise(23, 9, 'place a Disk Drive next to', colours.black),
1071
  		Label:Initialise(23, 10, 'the computer, then put the', colours.black),
1072
  		Label:Initialise(23, 11, 'PDA in the Drive, it will', colours.black),
1073
  		Label:Initialise(23, 12, 'register automatically. If', colours.black),
1074
  		Label:Initialise(23, 13, 'it worked it will eject.', colours.black),
1075
  		Label:Initialise(23, 15, 'Make sure you hide this', colours.red),
1076
  		Label:Initialise(23, 16, 'computer away from the', colours.red),
1077
  		Label:Initialise(23, 17, 'door! (other people)', colours.red)
1078
  	}
1079
  	for i, v in ipairs(helpLines) do
1080
  		table.insert(Current.PageControls, v)
1081
  	end
1082
1083
1084
	table.insert(Current.PageControls, Button:Initialise(2, 14, nil, nil, nil, nil, function()
1085
	  	for i = 1, 6 do
1086
	  		helpLines[i].TextColour = colours.green
1087
	  	end
1088
	end, 'Register New PDA', colours.black))
1089
1090
end
1091
1092
function HostStatusPage()
1093
	ResetPage()
1094
	Current.Page = 'HostStatus'
1095
	Current.StatusScreen = true
1096
	Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
1097
	Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostSetup, 'Settings/Help', colours.black)
1098
1099
	Wireless.Responder = function(event, side, channel, replyChannel, message, distance)
1100
		if channel == Wireless.Channels.UltimateDoorlockRequest and distance < Current.Settings.Distance then
1101
			if FingerprintIsOnWhitelist(message.content) then
1102
				OpenDoor()
1103
				Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, true)
1104
			else
1105
				Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, false)
1106
			end
1107
		end
1108
	end
1109
1110
	PingPocketComputers()
1111
end
1112
1113
function HostInitialise()
1114
	if not Wireless.Present() then
1115
		Current.Locked = true
1116
		Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black)
1117
		Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, function()os.reboot()end, 'Reboot', colours.black)
1118
		ResetStatus()
1119
		return
1120
	end
1121
	Wireless.Initialise()
1122
	ResetStatus()
1123
	if fs.exists('.settings') then
1124
		local h = fs.open('.settings', 'r')
1125
		if h then
1126
			Current.Settings = textutils.unserialize(h.readAll())
1127
		end
1128
		h.close()
1129
		HostStatusPage()		
1130
	else
1131
		HostSetup()
1132
	end
1133
	if OneOS then
1134
		OneOS.CanClose = function()
1135
			CloseDoor()
1136
			return true
1137
		end
1138
	end
1139
end
1140
1141
local pingTimer = nil
1142
function PingPocketComputers()
1143
	Wireless.SendMessage(Wireless.Channels.UltimateDoorlockPing, 'Ping!', Wireless.Channels.UltimateDoorlockRequest)
1144
	pingTimer = os.startTimer(0.5)
1145
end
1146
1147
function Initialise(arg)
1148
	EventRegister('mouse_click', TryClick)
1149
	EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end)
1150
	EventRegister('mouse_scroll', Scroll)
1151
	EventRegister('key', HandleKey)
1152
	EventRegister('char', HandleKey)
1153
	EventRegister('timer', Timer)
1154
	EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end)
1155
	EventRegister('modem_message', Wireless.HandleMessage)
1156
	EventRegister('disk', RegisterPDA)
1157
1158
	if OneOS then
1159
		OneOS.RequestRunAtStartup()
1160
	end
1161
1162
	if pocket then
1163
		PocketInitialise()
1164
	else
1165
		HostInitialise()
1166
	end
1167
1168
1169
	Draw()
1170
1171
	EventHandler()
1172
end
1173
1174
function Timer(event, timer)
1175
	if timer == pingTimer then
1176
		PingPocketComputers()
1177
	elseif timer == closeDoorTimer then
1178
		CloseDoor()
1179
	elseif timer == statusResetTimer then
1180
		ResetStatus()
1181
	end
1182
end
1183
1184
local ignoreNextChar = false
1185
function HandleKey(...)
1186
	local args = {...}
1187
	local event = args[1]
1188
	local keychar = args[2]
1189
	--[[
1190
																							--Mac left command character
1191
	if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then
1192
		isControlPushed = true
1193
		controlPushedTimer = os.startTimer(0.5)
1194
	elseif isControlPushed then
1195
		if event == 'key' then
1196
			if CheckKeyboardShortcut(keychar) then
1197
				isControlPushed = false
1198
				ignoreNextChar = true
1199
			end
1200
		end
1201
	elseif ignoreNextChar then
1202
		ignoreNextChar = false
1203
	elseif Current.TextInput then
1204
		if event == 'char' then
1205
			Current.TextInput:Char(keychar)
1206
		elseif event == 'key' then
1207
			Current.TextInput:Key(keychar)
1208
		end
1209
	end
1210
	]]--
1211
end
1212
1213
--[[
1214
	Check if the given object falls under the click coordinates
1215
]]--
1216
function CheckClick(object, x, y)
1217
	if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then
1218
		return true
1219
	end
1220
end
1221
1222
--[[
1223
	Attempt to clicka given object
1224
]]--
1225
function DoClick(object, side, x, y, drag)
1226
	local obj = GetAbsolutePosition(object)
1227
	obj.Width = object.Width
1228
	obj.Height = object.Height
1229
	if object and CheckClick(obj, x, y) then
1230
		return object:Click(side, x - object.X + 1, y - object.Y + 1, drag)
1231
	end	
1232
end
1233
1234
--[[
1235
	Try to click at the given coordinates
1236
]]--
1237
function TryClick(event, side, x, y, drag)
1238
	if Current.ButtonOne then
1239
		if DoClick(Current.ButtonOne, side, x, y, drag) then
1240
			Draw()
1241
			return
1242
		end
1243
	end
1244
1245
	if Current.ButtonTwo then
1246
		if DoClick(Current.ButtonTwo, side, x, y, drag) then
1247
			Draw()
1248
			return
1249
		end
1250
	end
1251
1252
	for i, v in ipairs(Current.PageControls) do
1253
		if DoClick(v, side, x, y, drag) then
1254
			Draw()
1255
			return
1256
		end
1257
	end
1258
1259
	Draw()
1260
end
1261
1262
function Scroll(event, direction, x, y)
1263
	if Current.Window and Current.Window.OpenButton then
1264
		Current.Document.Scroll = Current.Document.Scroll + direction
1265
		if Current.Window.Scroll < 0 then
1266
			Current.Window.Scroll = 0
1267
		elseif Current.Window.Scroll > Current.Window.MaxScroll then
1268
			Current.Window.Scroll = Current.Window.MaxScroll
1269
		end
1270
		Draw()
1271
	elseif Current.ScrollBar then
1272
		if Current.ScrollBar:DoScroll(direction*2) then
1273
			Draw()
1274
		end
1275
	end
1276
end
1277
1278
--[[
1279
	Registers functions to run on certain events
1280
]]--
1281
function EventRegister(event, func)
1282
	if not Events[event] then
1283
		Events[event] = {}
1284
	end
1285
1286
	table.insert(Events[event], func)
1287
end
1288
1289
--[[
1290
	The main loop event handler, runs registered event functinos
1291
]]--
1292
function EventHandler()
1293
	while isRunning do
1294
		local event, arg1, arg2, arg3, arg4, arg5, arg6 = os.pullEventRaw()
1295
		if Events[event] then
1296
			for i, e in ipairs(Events[event]) do
1297
				e(event, arg1, arg2, arg3, arg4, arg5, arg6)
1298
			end
1299
		end
1300
	end
1301
end
1302
1303
function Quit()
1304
	isRunning = false
1305
	term.setCursorPos(1,1)
1306
	term.setBackgroundColour(colours.black)
1307
	term.setTextColour(colours.white)
1308
	term.clear()
1309
	if OneOS then
1310
		OneOS.Close()
1311
	end
1312
end
1313
1314
if not term.current then -- if not 1.6
1315
	print('Because it requires pocket computers, Ultimate Door Lock requires ComputerCraft 1.6. Please update to 1.6 to use Ultimate Door Lock.')
1316
elseif not (OneOS and pocket) and term.isColor and term.isColor() then
1317
	-- If the program crashes close the door and reboot
1318
	local _, err = pcall(Initialise)
1319
	if err then
1320
		CloseDoor()
1321
		term.setCursorPos(1,1)
1322
		term.setBackgroundColour(colours.black)
1323
		term.setTextColour(colours.white)
1324
		term.clear()
1325
		print('Ultimate Door Lock has crashed')
1326
		print('To maintain security, the computer will reboot.')
1327
		print('If you are seeing this alot try turning off all Pocket Computers or reinstall.')
1328
		print()
1329
		print('Error:')
1330
		printError(err)
1331
		sleep(5)
1332
		os.reboot()
1333
	end
1334
elseif OneOS and pocket then
1335
	term.setCursorPos(1,3)
1336
	term.setBackgroundColour(colours.white)
1337
	term.setTextColour(colours.blue)
1338
	term.clear()
1339
	print('OneOS already acts as a door key. Simply place your PDA in the door\'s disk drive to register it.')
1340
	print()
1341
	print('To setup a door, run this program on an advanced computer (non-pocket).')
1342
	print()
1343
	print('Click anywhere to quit')
1344
	os.pullEvent('mouse_click')
1345
	Quit()
1346
else
1347
	print('Ultimate Door Lock requires an advanced (gold) computer or pocket computer.')
1348
end