· 5 years ago · Feb 06, 2020, 09:48 AM
1-- ###############################################
2-- # SGCX #
3-- # #
4-- # 12.2014 by: IlynPayne #
5-- ###############################################
6
7--[[
8 ## Description ##
9 Allows to control SGCraft's Stargates.
10
11 ## Data storage format ##
12 Main configuration file
13 /etc/sgcx.cfg {
14 version = 2, -- data storage format version
15 address = "string", -- Stargate component address
16 port = 0, -- remote iris authentication port
17 autoIris = false, -- automatic iris status
18 codes = { -- remote iris authentication codes
19 "string",
20 ...
21 },
22 groups = { -- order of groups (names of group files)
23 "string",
24 ...
25 }
26 }
27
28 Files containing address groups
29 /etc/sgcx.d/* {
30 version = 2, -- data storage version
31 name = "string", -- group name
32 group = {
33 [1] = { -- address entry
34 name = "string",
35 world = "string",
36 address = "string",
37 },
38 ...
39 }
40 }
41]]
42
43local version = "0.6.4"
44local dataStorageVersion = 2
45local startArgs = {...}
46
47if startArgs[1] == "version_check" then return version end
48
49local computer = require("computer")
50local component = require("component")
51local event = require("event")
52local fs = require("filesystem")
53local shell = require("shell")
54local term = require("term")
55local gml = require("gml")
56local s1 = require("support_v1")
57local colorGrid = require("color_grid")
58
59local serial = require("serialization")
60local gpu = component.gpu
61local res = {gpu.getResolution()}
62if res[1] ~= 160 or res[2] ~= 50 then
63 io.stderr:write("Application requires 3rd tier GPU and monitor in order to work")
64 return
65end
66if not component.isAvailable("modem") then
67 io.stderr:write("This application requires modem component to be installed")
68 return
69end
70local modem = component.modem
71
72local gui = nil
73local darkStyle = nil
74local element = {}
75local sg = nil
76local irisTimeout = 11
77local sgChunk = {}
78
79-- Loaded configuration data
80local data = {
81 config = {}, -- script-wide configuration
82 groups = {}, -- address groups
83 activeGroup = nil
84}
85
86local tmp = {}
87local dialDialog = false
88local countdownTimer = nil
89local timerEneriga = nil
90local irisTime = 0
91local irisTimer = nil
92local timeToClose = 0
93
94-- Constants required for address calculation
95local SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
96local NUM_SYMBOLS = SYMBOLS:len()
97local MIN_COORD = -139967
98local MAX_COORD = 139967
99local MC = 279937
100local PC = 93563
101local QC = 153742
102
103local function hasArg(arg)
104 for i, v in pairs(startArgs) do
105 if v == arg then return true end
106 end
107 return false
108end
109
110local function chooseInterface()
111 if not hasArg("init") then return "" end
112 print(">> Choose new stargate interface address (type q to quit) <<")
113 local componentList = component.list('stargate')
114 local list = {}
115 for address, _ in pairs(componentList) do
116 table.insert(list, address)
117 end
118 if #list == 0 then
119 io.stderr:write("Error: there is no connected stargate interfaces. Connect a stargate and rerun this program.")
120 return nil
121 end
122 for i, address in pairs(list) do
123 print(string.format("%d. %s", i, address))
124 end
125 print()
126 local selection = nil
127 while selection == nil do
128 io.write("Choose the interface: ")
129 selection = io.read()
130 if selection:sub(1, 1) == "q" then return nil end
131 selection = tonumber(selection)
132 if selection ~= nil then
133 selection = round(selection)
134 if selection < 1 or selection > #list then selection = nil end
135 end
136 if selection ~= nil then
137 return list[selection]
138 end
139 end
140end
141
142local function saveConfig()
143 local file = io.open("/etc/sgcx.cfg", "w")
144 file:write(serial.serialize(data.config))
145 file:close()
146
147 if not fs.isDirectory("/etc/sgcx.d") then
148 fs.makeDirectory("/etc/sgcx.d")
149 end
150
151 -- remove unused group files
152 for f in fs.list("/etc/sgcx.d") do
153 if f:sub(-1) ~= "/" then
154 local found = false
155 for _, g in pairs(data.config.groups) do
156 if g == f then
157 found = true
158 break
159 end
160 end
161 if not found then
162 fs.remove(fs.concat("/etc/sgcx.d", f))
163 end
164 end
165 end
166
167 -- save the rest of groups
168 for i, g in pairs(data.config.groups) do
169 local file = io.open(fs.concat("/etc/sgcx.d", g), "w")
170 file:write(serial.serialize(data.groups[i]))
171 file:close()
172 end
173end
174
175-- Storage format version 2
176local function migrateStorageTo2()
177 local oldFile = io.open("/etc/sg.cfg", "r")
178 local oldData = serial.unserialize(oldFile:read())
179 oldFile:close()
180
181 data.config.version = 2
182 data.config.address = oldData.address
183 data.config.port = oldData.port
184 data.config.portStatus = oldData.portStatus
185 data.config.codes = {tostring(oldData.irisCode)}
186 data.config.autoIris = oldData.autoIris
187 data.config.groups = {"default.cfg"}
188
189 data.groups[1] = {
190 version = 2,
191 name = "default",
192 group = {}
193 }
194 for _, oldEntry in pairs(oldData.list) do
195 table.insert(data.groups[1].group, oldEntry)
196 end
197end
198
199local function loadConfig(overrideAddress)
200 if fs.exists("/etc/sg.cfg") and not fs.exists("/etc/sgcx.cfg") then
201 migrateStorageTo2()
202 saveConfig()
203 end
204
205 if not fs.exists("/etc/sgcx.cfg") then
206 if not overrideAddress then
207 io.stderr:write("Configuration file not found. In order to create one, type 'sgcx init'")
208 return false
209 end
210 data.config.address = overrideAddress
211 sg = component.proxy(overrideAddress)
212 if not sg then
213 io.stderr:write("Wrong stargate interface address. Type 'sgcx init' to choose a new one.")
214 return false
215 elseif sg.type ~= "stargate" then
216 io.stderr:write("Chosen interface doesn't belong to a stargate interface.")
217 return false
218 end
219 else
220 local file = io.open("/etc/sgcx.cfg", "r")
221 data.config = serial.unserialize(file:read()) or {}
222 if overrideAddress then data.config.address = overrideAddress end
223 file:close()
224
225 sg = component.proxy(data.config.address or "")
226 if not sg then
227 io.stderr:write("Stargate component not found. Check connection with Stargate interface or run 'sgcx init' command to choose new interface.")
228 return false
229 elseif sg.type ~= "stargate" then
230 io.stderr:write("Given interface address doesn't belong to a stargate interface.")
231 return false
232 end
233 end
234
235 -- default values
236 data.config.groups = data.config.groups or {}
237 data.config.port = data.config.port or math.random(10000, 50000)
238 data.config.iris = data.config.iris or {math.random(1000, 9999)}
239 if data.config.portStatus then modem.open(data.config.port) end
240 data.config.codes = data.config.codes or {math.random(1000,9999)}
241
242 -- first load defined groups
243 local loadedGroups = {}
244 data.groups = {}
245 for _, g in pairs(data.config.groups) do
246 local path = fs.concat("/etc/sgcx.d", g)
247 if fs.exists(path) then
248 local groupFile = io.open(path, "r")
249 local status, fileData = pcall(serial.unserialize, groupFile:read())
250 groupFile:close()
251 if not status and not hasArg("force_load") then
252 print(">> ERROR <<")
253 print("Couldn't load file '" .. path .. "' due to a parsing error")
254 print("File has invalid format. Delete it manually or run script with the 'force_load' parameter to ignore invalid files.")
255 print("Warning! Corrupted data will be lost!")
256 print("Example: sgcx force_load")
257 require("os").exit()
258 elseif not status and hasArg("force_load") then
259 fs.remove(path)
260 print("Removed invalid file " .. path)
261 else
262 table.insert(data.groups, fileData)
263 table.insert(loadedGroups, g)
264 end
265 end
266 end
267 data.config.groups = loadedGroups
268
269 -- load new groups
270 for file in fs.list("/etc/sgcx.d") do
271 if file:sub(-1) ~= "/" then
272 local loaded = false
273 for _, name in pairs(data.config.groups) do
274 if name == file then
275 loaded = true
276 break
277 end
278 end
279 if not loaded then
280 local groupFile = io.open(file, "r")
281 table.insert(data.groups, serial.unserialize(groupFile:read()))
282 groupFile:close()
283 table.insert(data.config.groups, file)
284 end
285 end
286 end
287
288 -- normalize ordering in groups
289 for _, group in pairs(data.groups) do
290 local normalized = {}
291 for _, list in pairs(group.group) do
292 table.insert(normalized, list)
293 end
294 group.group = normalized
295 end
296
297 if #data.groups == 0 then
298 local default = {
299 name = "default",
300 version = dataStorageVersion,
301 group = {}
302 }
303 data.groups = {default}
304 data.config.groups = {"default.cfg"}
305 end
306 data.activeGroup = data.groups[1]
307
308 return true
309end
310
311local function GMLcontains(element,x,y)
312 local ex, ey, ew, eh = element.posX, element.posY, element.width, element.height
313 return x >= ex and x <= ex + ew - 1 and y >= ey and y <= ey + eh - 1
314end
315
316function GMLgetAppliedStyles(element)
317 local styleRoot=element.style
318 assert(styleRoot)
319
320 local depth, state, class, elementType = element.renderTarget.getDepth(), element.state or "*", element.class or "*", element.type
321
322 local nodes = {styleRoot}
323 local function filterDown(nodes, key)
324 local newNodes = {}
325 for i = 1, #nodes do
326 if key ~= "*" and nodes[i][key] then
327 newNodes[#newNodes + 1] = nodes[i][key]
328 end
329 if nodes[i]["*"] then
330 newNodes[#newNodes + 1] = nodes[i]["*"]
331 end
332 end
333 return newNodes
334 end
335 nodes = filterDown(nodes, depth)
336 nodes = filterDown(nodes, state)
337 nodes = filterDown(nodes, class)
338 nodes = filterDown(nodes, elementType)
339 return nodes
340end
341
342function GMLextractProperty(element, styles, property)
343 if element[property] then
344 return element[property]
345 end
346 for j = 1, #styles do
347 local v = styles[j][property]
348 if v ~= nil then
349 return v
350 end
351 end
352end
353
354function GMLmessageBox(message, buttons)
355 local buttons = buttons or {"OK"}
356 local choice
357 local lines = {}
358 message:gsub("([^\n]+)", function(line) lines[#lines+1] = line end)
359 local i = 1
360 while i <= #lines do
361 if #lines[i] > 26 then
362 local s, rs = lines[i], lines[i]:reverse()
363 local pos =- 26
364 local prev = 1
365 while #s > prev + 25 do
366 local space = rs:find(" ", pos)
367 if space then
368 table.insert(lines, i, s:sub(prev, #s-space))
369 prev = #s - space + 2
370 pos =- (#s - space + 28)
371 else
372 table.insert(lines, i, s:sub(prev, prev+25))
373 prev = prev + 26
374 pos = pos - 26
375 end
376 i = i + 1
377 end
378 lines[i] = s:sub(prev)
379 end
380 i = i + 1
381 end
382
383 local gui = gml.create("center", "center", 30, 6 + #lines, gpu)
384 local labels = {}
385 for i = 1, #lines do
386 labels[i] = gui:addLabel(2, 1 + i, 26, lines[i])
387 end
388 local buttonObjs = {}
389 local xpos = 2
390 for i = 1, #buttons do
391 if i == #buttons then xpos =- 2 end
392 buttonObjs[i]=gui:addButton(xpos, -2, #buttons[i] + 2, 1, buttons[i], function() choice = buttons[i] gui.close() end)
393 xpos = xpos + #buttons[i] + 3
394 end
395
396 gui:changeFocusTo(buttonObjs[#buttonObjs])
397 gui:run()
398 return choice
399end
400
401local function addBar(x, y, length, isHorizontal)
402 local bar = {
403 visible = false,
404 hidden = false,
405 gui = gui,
406 style = gui.style,
407 focusable = false,
408 type = "label",
409 renderTarget = gui.renderTarget,
410 horizontal = isHorizontal
411 }
412 bar.posX = x
413 bar.posY = y
414 bar.width = isHorizontal and length or 1
415 bar.height = isHorizontal and 1 or length
416 bar.contains = GMLcontains
417 bar.isHidden = function() return false end
418 bar.draw = function(t)
419 t.renderTarget.setBackground(tmp.GMLbgcolor)
420 t.renderTarget.setForeground(0xffffff)
421 if t.horizontal then
422 t.renderTarget.set(t.posX + 1, t.posY + 1, string.rep(require("unicode").char(0x2550), t.width))
423 else
424 local uni = require("unicode")
425 for i = 1, t.height do
426 t.renderTarget.set(t.posX + 1, t.posY + i, uni.char(0x2551))
427 end
428 end
429 end
430 gui:addComponent(bar)
431 return bar
432end
433
434local function addTitle()
435 local grid = colorGrid.grid({
436 ["#"] = 0xff6600
437 })
438 grid:line(" ### #### ### ## ##")
439 grid:line("# # # ## ## ")
440 grid:line(" ### # ### # ### ")
441 grid:line(" # # # # ## ## ")
442 grid:line(" ### #### ### ## ##")
443 return grid:generateComponent(gui, 3, 3)
444end
445
446local function addStargate(cx, cy)
447 local stargate = {
448 visible = false,
449 hidden = false,
450 gui = gui,
451 style = gui.style,
452 focusable = false,
453 type = "label",
454 renderTarget = gpu,
455 horizontal = isHorizontal,
456 posX = 95,
457 posY = 25,
458 width = 40,
459 height = 20,
460 color = 0x333333,
461 symbolIndex = 0
462 }
463 stargate.contains = GMLcontains
464 stargate.isHidden = function() return false end
465 stargate.draw = function(t)
466 if not t.visible then
467 local subdraw = function(x, y, vx, vy)
468 for i = 0, 3 do
469 t.renderTarget.set(x, y + 6 * vy + i * vy, ' ')
470 t.renderTarget.set(x + 1 * vx, y + 6 * vy + i * vy, ' ')
471 end
472 t.renderTarget.set(x + 1 * vx, y + 5 * vy, ' ')
473 t.renderTarget.set(x + 2 * vx, y + 5 * vy, ' ')
474 t.renderTarget.set(x + 2 * vx, y + 4 * vy, ' ')
475 t.renderTarget.set(x + 3 * vx, y + 4 * vy, ' ')
476 t.renderTarget.set(x + 3 * vx, y + 3 * vy, ' ')
477 t.renderTarget.set(x + 4 * vx, y + 3 * vy, ' ')
478 t.renderTarget.set(x + 5 * vx, y + 3 * vy, ' ')
479 t.renderTarget.set(x + 5 * vx, y + 2 * vy, ' ')
480 t.renderTarget.set(x + 6 * vx, y + 2 * vy, ' ')
481 t.renderTarget.set(x + 7 * vx, y + 2 * vy, ' ')
482 for i = 0, 4 do
483 t.renderTarget.set(x + 7 * vx + i * vx, y + 1 * vy, ' ')
484 end
485 for i = 0, 8 do
486 t.renderTarget.set(x + 11 * vx + i * vx, y, ' ')
487 end
488 end
489 t.renderTarget.setBackground(0x333333)
490 subdraw(t.posX, t.posY, 1, 1)
491 subdraw(t.posX + t.width - 1, t.posY, -1, 1)
492 subdraw(t.posX, t.posY + t.height - 1, 1, -1)
493 subdraw(t.posX + t.width - 1, t.posY + t.height - 1, -1, -1)
494 t.visible = true
495 end
496 if sg.irisState() == "Closed" then
497 t:fill(0xb4b4b4)
498 elseif sg.stargateState() == "Connected" then
499 t:fill(0x4086FF)
500 else
501 t:fill(tmp.GMLbgcolor)
502 end
503 end
504 stargate.fill = function(t, hex)
505 local subfill = function(sy, vy)
506 t.renderTarget.fill(t.posX + 12, sy, 16, 1, ' ')
507 t.renderTarget.fill(t.posX + 8, sy + 1 * vy, 24, 1, ' ')
508 t.renderTarget.fill(t.posX + 6, sy + 2 * vy, 28, 1, ' ')
509 t.renderTarget.fill(t.posX + 4, sy + 3 * vy, 32, 1, ' ')
510 t.renderTarget.fill(t.posX + 3, sy + 4 * vy, 34, 1, ' ')
511 end
512 t.renderTarget.setBackground(hex)
513 subfill(t.posY + 1, 1)
514 t.renderTarget.fill(t.posX + 2, t.posY + 6, 36, 8, ' ')
515 subfill(t.posY + t.height - 2, -1)
516 end
517 stargate.lockSymbol = function(t, number)
518 if (number == 1 and t.symbolIndex == 0) or number == 0 then
519 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
520 t.renderTarget.fill(t.posX + 2, t.posY + 15, 2, 1, ' ')
521 t.renderTarget.fill(t.posX + 3, t.posY + 16, 2, 1, ' ')
522 t.symbolIndex = 1
523 end
524 if (number == 2 and t.symbolIndex == 1) or number == 0 then
525 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
526 t.renderTarget.fill(t.posX, t.posY + 8, 2, 2, ' ')
527 t.symbolIndex = 2
528 end
529 if (number == 3 and t.symbolIndex == 2) or number == 0 then
530 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
531 t.renderTarget.fill(t.posX + 5, t.posY + 2, 3, 1, ' ')
532 t.renderTarget.fill(t.posX + 4, t.posY + 3, 2, 1, ' ')
533 t.symbolIndex = 3
534 end
535 if (number == 4 and t.symbolIndex == 3) or number == 0 then
536 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
537 t.renderTarget.fill(t.posX + 18, t.posY, 4, 1, ' ')
538 t.symbolIndex = 4
539 end
540 if (number == 5 and t.symbolIndex == 4) or number == 0 then
541 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
542 t.renderTarget.fill(t.posX + 32, t.posY + 2, 3, 1, ' ')
543 t.renderTarget.fill(t.posX + 34, t.posY + 3, 2, 1, ' ')
544 t.symbolIndex = 5
545 end
546 if (number == 6 and t.symbolIndex == 5) or number == 0 then
547 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
548 t.renderTarget.fill(t.posX + 38, t.posY + 8, 2, 2, ' ')
549 t.symbolIndex = 6
550 end
551 if (number == 7 and t.symbolIndex == 6) or number == 0 then
552 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
553 t.renderTarget.fill(t.posX + 35, t.posY + 16, 2, 1, ' ')
554 t.renderTarget.fill(t.posX + 36, t.posY + 15, 2, 1, ' ')
555 t.symbolIndex = 7
556 end
557 if (number == 8 and t.symbolIndex == 7) or number == 0 then
558 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
559 t.renderTarget.fill(t.posX + 24, t.posY + 19, 4, 1, ' ')
560 t.symbolIndex = 8
561 end
562 if (number == 9 and t.symbolIndex == 8) or number == 0 then
563 t.renderTarget.setBackground(number == 0 and t.color or 0xff6600)
564 t.renderTarget.fill(t.posX + 12, t.posY + 19, 4, 1, ' ')
565 t.symbolIndex = 9
566 end
567 if number == 0 then t.symbolIndex = 0 end
568 end
569 gui:addComponent(stargate)
570 return stargate
571end
572
573local function separateAddress(addr)
574 return string.sub(addr, 1, 4) .. "-" .. string.sub(addr, 5, 7) .. "-" .. string.sub(addr, 8, 9)
575end
576
577local function translateResponse(res)
578 if res == "bad arguments #1 (string expected, got no value)" then
579 return "Malformed stargate address"
580 end
581 return res
582end
583
584local function round(num, idp)
585 local mult = 10 ^ (idp or 0)
586 return math.floor(num * mult + 0.5) / mult
587end
588
589local function getEnergy()
590 local percent = tostring(math.floor(100 * (sg.energyAvailable() * 80 / 4000000) + 0.5) - 1) .. "%"
591 eu = string.reverse(tostring(round(sg.energyAvailable(), 0) * 80))
592 eu2 = ""
593 for i=1, string.len(eu) do
594 if i % 3 == 0 and i ~= 0 then
595 eu2 = eu2 .. string.sub(eu, i, i) .. " "
596 else
597 eu2 = eu2 .. string.sub(eu, i, i)
598 end
599 end
600 eu2 = string.reverse(eu2)
601 return percent .. " / " .. eu2 .. " RF"
602end
603
604local function energyRefresh()
605 element.energy["text"] = "Energy: " .. getEnergy()
606 element.energy:draw()
607end
608
609local function irisTimerFunction()
610 if irisTime < 0 then
611 sg.closeIris()
612 event.cancel(irisTimer)
613 irisTimer = nil
614 else
615 irisTime = irisTime - 1
616 end
617end
618
619local function modifyList(action)
620 local function isSelected()
621 return element.list.selectedLabel ~= nil
622 end
623 if action == "add" then
624 if element.name["text"] == "" or element.world["text"] == "" or element.address["text"] == "" then
625 GMLmessageBox("Fill all fields", {"OK"})
626 elseif element.name["text"]:len() > 20 or element.world["text"]:len() > 20 then
627 GMLmessageBox("Names cannot be longer than 20 characters", {"OK"})
628 elseif not sg.energyToDial(element.address["text"]) then
629 GMLmessageBox("Address is incorrect or does not exist", {"OK"})
630 else
631 for _, v in pairs(data.activeGroup.group) do
632 if v.name == element.name["text"] then
633 GMLmessageBox("Address with given name is already on the list", {"OK"})
634 return
635 elseif v.address == element.address then
636 GMLmessageBox("Given address is already on the list under the name " .. v.name, {"OK"})
637 return
638 end
639 end
640 local l = {
641 name = element.name["text"],
642 world = element.world["text"],
643 address = element.address["text"]:upper()
644 }
645 table.insert(data.activeGroup.group, l)
646 element.list:refreshList()
647 saveConfig()
648 GMLmessageBox("Address has been added to the list", {"OK"})
649 end
650 elseif action == "modify" and isSelected() then
651 if element.name["text"] == "" or element.world["text"] == "" or element.address["text"] == "" then
652 GMLmessageBox("Fill all fields", {"OK"})
653 elseif element.name["text"]:len() > 20 or element.world["text"]:len() > 20 then
654 GMLmessageBox("Names cannot be longer than 20 characters", {"OK"})
655 elseif not sg.energyToDial(element.address["text"]) then
656 GMLmessageBox("Address is incorrect or does not exist", {"OK"})
657 else
658 if GMLmessageBox("Are you sure you want to modify this entry", {"Yes", "No"}) == "Yes" then
659 local selected = element.list:getSelected()
660 for a, v in pairs(data.activeGroup.group) do
661 if selected == tostring(a) .. ". " .. v.name .. " (" .. v.world .. ")" then
662 v.name = element.name["text"]
663 v.world = element.world["text"]
664 v.address = element.address["text"]:upper()
665 element.list:refreshList()
666 saveConfig()
667 GMLmessageBox("The entry has been modified", {"OK"})
668 break
669 end
670 end
671 end
672 end
673 elseif action == "remove" and isSelected() then
674 if GMLmessageBox("Are you sure you want to remove selected entry?", {"Yes", "No"}) == "Yes" then
675 local selected = element.list:getSelected()
676 for k, v in pairs(data.activeGroup.group) do
677 if selected == tostring(k) .. ". " .. v.name .. " (" .. v.world .. ")" then
678 table.remove(data.activeGroup.group, k)
679 element.list:refreshList()
680 saveConfig()
681 GMLmessageBox("The entry has been removed", {"OK"})
682 break
683 end
684 end
685 end
686 elseif action == "up" and isSelected() then
687 if element.list.selectedLabel > 1 then
688 local ag = data.activeGroup.group
689 local i = element.list.selectedLabel
690 local tmp = ag[i]
691 ag[i] = ag[i - 1]
692 ag[i - 1] = tmp
693 element.list:refreshList()
694 element.list:select(i - 1)
695 saveConfig()
696 end
697 elseif action == "down" and isSelected() then
698 if element.list.selectedLabel < #data.activeGroup.group then
699 local ag = data.activeGroup.group
700 local i = element.list.selectedLabel
701 local tmp = ag[i]
702 ag[i] = ag[i + 1]
703 ag[i + 1] = tmp
704 element.list:refreshList()
705 element.list:select(i + 1)
706 saveConfig()
707 end
708 elseif action == "to" and isSelected() then
709 local selectedGroup = nil
710 local options = {}
711 for _, g in pairs(data.groups) do
712 if g.name ~= data.activeGroup.name then
713 table.insert(options, g.name)
714 end
715 end
716 local text = "Move the address '" .. data.activeGroup.group[element.list.selectedLabel].name .. "' to the group:"
717 local mgui = gml.create("center", "center", 64, 25)
718 mgui.style = darkStyle
719 mgui:addLabel("center", 1, 17, "Move the address")
720 mgui:addLabel(3, 19, 15, "Selected group:")
721 local selected = mgui:addLabel(20, 19, 30, "")
722 mgui:addLabel(3, 3, text:len(), text)
723 local listbox = mgui:addListBox(9, 4, 44, 14, options)
724 listbox.onChange = function(lb, prev, index)
725 selectedGroup = options[index]
726 selected.text = selectedGroup
727 selected:draw()
728 end
729 mgui:addButton(48, 23, 12, 1, "Cancel", function() mgui:close() end)
730 mgui:addButton(32, 23, 12, 1, "Move", function()
731 if selectedGroup == nil then
732 GMLmessageBox("No target group was selected")
733 else
734 local targetGroup = nil
735 for _, g in pairs(data.groups) do
736 if g.name == selectedGroup then
737 targetGroup = g
738 break
739 end
740 end
741 if targetGroup ~= nil then
742 local entry = table.remove(data.activeGroup.group, element.list.selectedLabel)
743 table.insert(targetGroup.group, entry)
744 element.list:refreshList()
745 saveConfig()
746 end
747 mgui:close()
748 end
749 end)
750 mgui:run()
751 end
752end
753
754local function manageGroups()
755 local ggui = gml.create("center", "center", 70, 31)
756 local groupsUpdated = false
757 ggui.style = darkStyle
758
759 ggui:addLabel("center", 1, 13, "Edit groups")
760 ggui:addButton(56, 29, 10, 1, "Close", function () ggui:close() end)
761
762 ggui:addLabel(39, 4, 6, "Name:")
763 local name = ggui:addTextField(39, 5, 25)
764 ggui:addLabel(39, 7, 14, "Address count:")
765 local addressCount = ggui:addLabel(54, 7, 3, "")
766
767 -- Listbox
768 local list = {}
769 local function refreshList()
770 list = {}
771 for _, group in pairs(data.groups) do
772 local found = 0
773 for _, n in pairs(list) do
774 if n == group.name then
775 found = found + 1
776 end
777 end
778 if found > 0 then
779 group.name = group.name .. "_" .. tostring(found)
780 groupsUpdated = true
781 end
782
783 table.insert(list, group.name)
784 end
785 end
786 refreshList()
787 local listbox = ggui:addListBox(3, 4, 30, 20, list)
788 listbox.onChange = function (lb, prev, index)
789 name.text = data.groups[index].name
790 name:draw()
791 addressCount.text = tostring(#data.groups[index].group)
792 addressCount:draw()
793 end
794 ----------------------------
795
796 -- Buttons
797 local function updated()
798 groupsUpdated = true
799 refreshList()
800 listbox:updateList(list)
801 listbox:draw()
802 end
803 local function groupExists(name)
804 for _, g in pairs(data.groups) do
805 if g.name == name then return true end
806 end
807 return false
808 end
809 local function sanitizeGroupFile(name)
810 local sanitized = ""
811 for i = 1, name:len() do
812 local code = string.byte(name:sub(i, i))
813 if code >= 65 and code <= 90 or code >= 97 and code <= 122 or code >= 48 and code <= 57 then
814 sanitized = sanitized .. name:sub(i, i)
815 end
816 end
817 return sanitized .. ".cfg"
818 end
819 ggui:addButton(3, 25, 12, 2, "Add", function ()
820 if name.text:len() == 0 then
821 GMLmessageBox("Group name cannot be empty")
822 elseif name.text:len() > 20 then
823 GMLmessageBox("Group name cannot be longer than 20 characters")
824 elseif groupExists(name.text) then
825 GMLmessageBox("Group with the same name already exists")
826 elseif #data.groups > 25 then
827 GMLmessageBox("Maximum number of groups (25) has been reached")
828 else
829 local group = {
830 name = name.text,
831 version = dataStorageVersion,
832 group = {}
833 }
834 table.insert(data.groups, group)
835 table.insert(data.config.groups, sanitizeGroupFile(name.text))
836 updated()
837 end
838 end)
839 ggui:addButton(21, 25, 12, 2, "Delete", function ()
840 if not listbox.selectedLabel then return end
841 if #data.groups < 2 then
842 GMLmessageBox("Cannot delete this group: at least one must exist")
843 return
844 end
845 local toRemove = data.groups[listbox.selectedLabel]
846 if #toRemove.group > 0 then
847 if GMLmessageBox("Group is not empty. Do you want to delete it with all contained addresses?", {"Yes", "No"}) == "No" then return end
848 end
849 table.remove(data.groups, listbox.selectedLabel)
850 table.remove(data.config.groups, listbox.selectedLabel)
851 updated()
852 end)
853 ggui:addButton(54, 25, 12, 2, "Update", function ()
854 if not listbox.selectedLabel then return end
855 if name.text:len() == 0 then
856 GMLmessageBox("Group name cannot be empty")
857 elseif name.text:len() > 20 then
858 GMLmessageBox("Group name cannot be longer than 20 characters")
859 else
860 local group = data.groups[listbox.selectedLabel]
861 group.name = name.text
862 list[listbox.selectedLabel] = name.text
863 listbox:updateList(list)
864 data.config.groups[listbox.selectedLabel] = sanitizeGroupFile(name.text)
865 groupsUpdated = true
866 end
867 end)
868 ggui:addButton(3, 28, 12, 1, "Move up", function ()
869 local i = listbox.selectedLabel
870 if not i or i < 2 then return end
871 local tmp = data.groups[i]
872 data.groups[i] = data.groups[i - 1]
873 data.groups[i - 1] = tmp
874 listbox:select(i - 1)
875 tmp = data.config.groups[i]
876 data.config.groups[i] = data.config.groups[i - 1]
877 data.config.groups[i - 1] = tmp
878 updated()
879 end)
880 ggui:addButton(21, 28, 12, 1, "Move down", function ()
881 local i = listbox.selectedLabel
882 if not i or i == #data.groups then return end
883 local tmp = data.groups[i]
884 data.groups[i] = data.groups[i + 1]
885 data.groups[i + 1] = tmp
886 listbox:select(i + 1)
887 tmp = data.cnofig.groups[i]
888 data.config.groups[i] = data.config.groups[i + 1]
889 data.config.groups[i + 1] = tmp
890 udpated()
891 end)
892 ----------------------------
893
894 ggui:run()
895 if groupsUpdated then
896 element.list:refreshList()
897 element.groupSelector:refresh()
898 saveConfig()
899 end
900end
901
902local function dial()
903 if sg.stargateState() == "Idle" then
904 local status, response = sg.dial(element.address["text"])
905 if status then
906 element.connectionType["text"] = "Outgoing connection"
907 element.connectionType:show()
908 local timeout = tonumber(element.time["text"])
909 if timeout and timeout >= 10 and timeout <= 300 then
910 timeToClose = timeout
911 else
912 timeToClose = 300
913 element.time["text"] = ""
914 element.time:draw()
915 end
916 else
917 GMLmessageBox(translateResponse(response), {"OK"})
918 end
919 elseif sg.stargateState() == "Connected" or sg.stargateState() == "Dialing" then
920 sg.disconnect()
921 countdownTimer:stop()
922 elseif sg.stargateState() ~= "Offline" then
923 GMLmessageBox("Stargate is busy", {"OK"})
924 end
925end
926
927local function hash(i1, i2, i3)
928 local r = (((i1 + 1) * i2) % i3) - 1
929 return r
930end
931
932local function chunkInRange(c)
933 return c >= MIN_COORD and c <= MAX_COORD
934end
935
936local function getSymbols(i1, i2)
937 local ret = ""
938 while i2 > 0 do
939 i2 = i2 - 1
940 i = (i1 % NUM_SYMBOLS) + 1
941 ret = ret .. SYMBOLS:sub(i, i)
942 i1 = math.floor(i1 / NUM_SYMBOLS)
943 end
944
945 return ret:reverse()
946end
947
948local function getSymbolsValue(s)
949 l = 0
950 for x = 1, s:len() do
951 local index = 0
952 for i = 1, NUM_SYMBOLS do
953 if SYMBOLS:sub(i, i) == s:sub(x, x) then
954 index = i - 1
955 break
956 end
957 end
958 l = (l * NUM_SYMBOLS) + index
959 end
960
961 return l
962end
963
964local function interleave(i1, i2)
965 l1 = 1
966 l2 = 0
967 while i1 > 0 or i2 > 0 do
968 l2 = l2 + (l1 * (i1 % 6))
969 i1 = math.floor(i1 / 6)
970 l1 = l1 * 6
971 l2 = l2 + (l1 * (i2 % 6))
972 i2 = math.floor(i2 / 6)
973 l1 = l1 * 6
974 end
975
976 return l2
977end
978
979local function fromBaseSix(s)
980 local ret = 0
981 for i = 1, s:len() do
982 local f = tonumber(s:sub(i, i))
983 ret = ret + f * math.pow(6, i - 1)
984 end
985
986 return ret
987end
988
989local function uninterleave(i)
990 local i1 = ''
991 local i2 = ''
992 while i > 0 do
993 i1 = i1 .. tostring(i % 6)
994 i = math.floor(i / 6)
995 i2 = i2 .. tostring(i % 6)
996 i = math.floor(i / 6)
997 end
998
999 i1 = fromBaseSix(i1)
1000 i2 = fromBaseSix(i2)
1001 return {i1, i2}
1002end
1003
1004local function chunkToAddress(cx, cz)
1005 if not chunkInRange(cx) then
1006 GMLmessageBox("The X coordinate is beyond the addressation range", {"OK"})
1007 return
1008 elseif not chunkInRange(cz) then
1009 GMLmessageBox("The Z coordinate is beyond the addressation range", {"OK"})
1010 return
1011 end
1012
1013 local l = interleave(hash(cx - MIN_COORD, PC, MC), hash(cz - MIN_COORD, PC, MC))
1014 return getSymbols(l, 7)
1015end
1016
1017local function addressToChunk(address)
1018 local l = getSymbolsValue(address:sub(1, 7))
1019 if not l then return end
1020 local a = uninterleave(l)
1021 local i = MIN_COORD + hash(a[1], QC, MC)
1022 local j = MIN_COORD + hash(a[2], QC, MC)
1023 return {i, j}
1024end
1025
1026local function computeDistance(addressTarget, chunkTarget)
1027 if not chunkTarget then
1028 chunkTarget = addressToChunk(addressTarget)
1029 if not chunkTarget then return end
1030 end
1031
1032 local dx = math.abs(sgChunk[1] - chunkTarget[1])
1033 local dz = math.abs(sgChunk[2] - chunkTarget[2])
1034 local dist = math.sqrt(dx * dx + dz * dz)
1035 dist = math.floor(dist * 160) / 10
1036 return dist
1037end
1038
1039local function clearAddress(addr)
1040 if not addr or addr:len() < 7 then return nil end
1041 local clear = ""
1042 for i = 1, 7 do
1043 local c = string.byte(addr:sub(i, i))
1044 if (c >= 65 and c <= 90) or (c >= 48 and c <= 57) then
1045 clear = clear .. addr:sub(i, i)
1046 elseif c >= 97 and c <= 122 then
1047 clear = clear .. string.char(c - 32)
1048 else
1049 return nil
1050 end
1051 end
1052 return clear
1053end
1054
1055local function coordsCalculator()
1056 local cgui = gml.create("center", "center", 60, 21)
1057 cgui.style = darkStyle
1058 cgui:addButton(42, 18, 12, 1, "Close", function() cgui:close() end)
1059 cgui:addLabel(3, 2, 4, "X:")
1060 cgui:addLabel(3, 5, 4, "Z:")
1061 cgui:addLabel(3, 8, 10, "Chunk X:")
1062 cgui:addLabel(3, 11, 10, "Chunk Z:")
1063 local posX = cgui:addTextField(3, 3, 14)
1064 local posZ = cgui:addTextField(3, 6, 14)
1065 local posCX = cgui:addTextField(3, 9, 14)
1066 local posCZ = cgui:addTextField(3, 12, 14)
1067 cgui:addLabel(38, 5, 10, "Address:")
1068 local address = cgui:addTextField(38, 6, 17)
1069 cgui:addButton(38, 8, 17, 1, "Copy from list", function()
1070 address.text = element.address.text
1071 address:draw()
1072 end)
1073 cgui:addLabel(38, 10, 13, "Distance:")
1074 local distance = cgui:addLabel(38, 11, 17, "0")
1075 cgui:addLabel(3, 15, 56, "* World symbols aren't generated deterministically")
1076 local function updateDistance(cx, cz)
1077 local dist = computeDistance(nil, {cx, cz})
1078 distance.text = tostring(dist)
1079 distance:draw()
1080 end
1081 local function updateAddress(cx, cz)
1082 local addr = chunkToAddress(cx, cz)
1083 if addr then
1084 address.text = addr
1085 address:draw()
1086 end
1087 updateDistance(cx, cz)
1088 end
1089 cgui:addButton(22, 5, 11, 2, "--->", function()
1090 if posX.text:len() > 0 and posZ.text:len() > 0 then
1091 local npx = tonumber(posX.text)
1092 local npz = tonumber(posZ.text)
1093 if not npx then
1094 GMLmessageBox("The X coordinate must be a number", {"OK"})
1095 elseif not npz then
1096 GMLmessageBox("The Z coordinate must be a number", {"OK"})
1097 else
1098 updateAddress(math.floor(npx / 16), math.floor(npz / 16))
1099 end
1100 elseif posCX.text:len() > 0 and posCZ.text:len() > 0 then
1101 local ncx = tonumber(posCX.text)
1102 local ncz = tonumber(posCZ.text)
1103 if not ncx then
1104 GMLmessageBox("The chunk X coordinate must be a number", {"OK"})
1105 elseif not ncz then
1106 GMLmessageBox("The chunk Z coordinate must be a number", {"OK"})
1107 else
1108 updateAddress(ncx, ncz)
1109 end
1110 else
1111 GMLmessageBox("Enter coordinates of a block or chunk", {"OK"})
1112 end
1113 end)
1114 cgui:addButton(22, 10, 11, 2, "<---", function()
1115 if address.text:len() >= 7 then
1116 local cleared = clearAddress(address.text)
1117 if not cleared then
1118 GMLmessageBox("Address field contains forbidden characters", {"OK"})
1119 else
1120 local chunk = addressToChunk(cleared)
1121 posCX.text = tostring(chunk[1])
1122 posCX:draw()
1123 posCZ.text = tostring(chunk[2])
1124 posCZ:draw()
1125 posX.text = tostring(chunk[1] * 16)
1126 posX:draw()
1127 posZ.text = tostring(chunk[2] * 16)
1128 posZ:draw()
1129 updateDistance(chunk[1], chunk[2])
1130 end
1131 else
1132 GMLmessageBox("The address field must contain 7 characters", {"OK"})
1133 end
1134 end)
1135 cgui:run()
1136end
1137
1138local function channelCodeChooser(isChannel)
1139 local what = isChannel and "the channel port" or "the security code"
1140 local rangeFrom = isChannel and 10000 or 1000
1141 local rangeTo = isChannel and 50000 or 9999
1142
1143 local cgui = gml.create("center", "center", 40, 10)
1144 cgui.style = darkStyle
1145
1146 cgui:addLabel("center", 1, 25, "Choose " .. what)
1147 local number = cgui:addTextField(4, 4, 12)
1148 number.text = tostring(isChannel and data.config.port or data.config.codes[1])
1149 cgui:addButton(23, 4, 10, 1, "Random", function()
1150 number.text = tostring(math.random(rangeFrom, rangeTo))
1151 number:draw()
1152 end)
1153 cgui:addLabel(4, 5, 32, "Value boundaries: [" .. tostring(rangeFrom) .. "," .. tostring(rangeTo) .. "]")
1154
1155 cgui:addButton(6, 8, 12, 1, "OK", function()
1156 local newVal = tonumber(number.text)
1157 if number.text:len() == 0 then
1158 GMLmessageBox("Value cannot be empty")
1159 elseif not newVal then
1160 GMLmessageBox("Entered value is not a number")
1161 elseif newVal < rangeFrom or newVal > rangeTo then
1162 GMLmessageBox("Entered value is not within the boundaries")
1163 else
1164 if isChannel then
1165 local isOpen = modem.isOpen(data.config.port)
1166 if isOpen then modem.close(data.config.port) end
1167 data.config.port = newVal
1168 if isOpen then modem.open(data.config.port) end
1169 saveConfig()
1170 else
1171 -- todo: support of multiple iris codes in the future
1172 data.config.codes[1] = newVal
1173 saveConfig()
1174 cgui:close()
1175 end
1176 end
1177 end)
1178 cgui:addButton(21, 8, 12, 1, "Cancel", function()
1179 cgui:close()
1180 end)
1181 cgui:run()
1182end
1183
1184local function countdown(timer)
1185 local minutes = tostring(math.floor(timer.count / 60))
1186 local seconds = tostring(60 * ((timer.count / 60) - math.floor(timer.count / 60)))
1187 if string.len(seconds) == 1 then seconds = "0" .. seconds end
1188 element.timeout["text"] = "Remaining time: " .. minutes .. ":" .. seconds
1189 element.timeout:draw()
1190end
1191
1192local function createUI()
1193 ------------------------
1194 gui = gml.create(0, 0, res[1], res[2])
1195 gui.style = darkStyle
1196 countdownTimer = s1.timer(countdown, gui)
1197 countdownTimer.onStop = function () sg.disconnect() end
1198 ------------------------
1199
1200 -- Common elements
1201 addTitle()
1202 gui:addLabel(35, 2, 10, version)["text-color"] = 0x666666
1203 addBar(53, 1, 15, false)
1204 element.stargate = addStargate()
1205 gui:addButton("right", 1, 10, 1, "Exit", function() gui:close() end)
1206 gui:addLabel(56, 4, 22, "Address: " .. separateAddress(sg.localAddress()))
1207 element.status = gui:addLabel(56, 5, 25, "Status: " .. sg.stargateState())
1208 element.iris = gui:addLabel(56, 6, 25, "Iris: " .. sg.irisState())
1209 element.energy = gui:addLabel(56, 7, 35, "Energy: " .. getEnergy())
1210 element.distance = gui:addLabel(56, 8, 35, "Distance: ")
1211 element.connectionType = gui:addLabel(56, 11, 30, "")
1212 element.connectionType:hide()
1213 element.remoteAddress = gui:addLabel(56, 12, 32, "")
1214 element.remoteAddress:hide()
1215 element.timeout = gui:addLabel(56, 13, 30, "")
1216 element.timeout:hide()
1217 ------------------------
1218
1219 -- Address list
1220 gui:addLabel(3, 20, 16, "Address list:")
1221 local list = {}
1222 for a, v in pairs(data.activeGroup.group) do
1223 table.insert(list, tostring(a) .. ". " .. v.name .. " (" .. v.world .. ")")
1224 end
1225 element.list = gui:addListBox(3, 21, 40, 24, list)
1226 element.list.onChange = function(listBox)
1227 local sel = listBox:getSelected()
1228 if not sel then return end
1229 local index = tonumber(sel:match("^(%d+)%.%s"))
1230 if not index or not data.activeGroup.group[index] then return end
1231 element.name["text"] = data.activeGroup.group[index].name
1232 element.name:draw()
1233 element.world["text"] = data.activeGroup.group[index].world
1234 element.world:draw()
1235 element.address["text"] = data.activeGroup.group[index].address
1236 element.address:draw()
1237 local clear = clearAddress(element.address.text)
1238 if clear then
1239 element.distance.text = "Distance: " .. tostring(computeDistance(clear))
1240 element.distance:draw()
1241 end
1242 end
1243 element.list.refreshList = function ()
1244 local list = {}
1245 for a, v in pairs(data.activeGroup.group) do
1246 local f = element.addressFilter.text
1247 if not f or f:len() == 0 or v.name:find(f, nil, true) then
1248 table.insert(list, tostring(a) .. ". " .. v.name .. " (" .. v.world .. ")")
1249 end
1250 end
1251 element.list:updateList(list)
1252 end
1253 ------------------------
1254
1255 -- Address list management
1256 gui:addButton(4, 45, 10, 2, "Add", function() modifyList("add") end)
1257 gui:addButton(15, 45, 10, 2, "Delete", function() modifyList("remove") end)
1258 gui:addButton(27, 45, 15, 2, "Update", function() modifyList("modify") end)
1259 gui:addButton(4, 48, 15, 1, "Move up", function () modifyList("up") end)
1260 gui:addButton(27, 48, 15, 1, "Move down", function() modifyList("down") end)
1261 gui:addButton(48, 48, 15, 1, "Move to", function() modifyList("to") end)
1262 gui:addLabel(45, 22, 7, "Name:")["text-color"] = 0x999999
1263 gui:addLabel(45, 25, 9, "World:")["text-color"] = 0x999999
1264 gui:addLabel(45, 29, 11, "Address:")["text-color"] = 0x999999
1265 gui:addLabel(45, 32, 27, "Connection time [10-300]:")["text-color"] = 0x999999
1266 element.name = gui:addTextField(45, 23, 25)
1267 element.world = gui:addTextField(45, 26, 25)
1268 element.address = gui:addTextField(45, 30, 25)
1269 element.time = gui:addTextField(45, 33, 10)
1270 element.dial = gui:addButton(45, 36, 25, 3, "Open a tunnel", dial)
1271 element.irisButton = gui:addButton(45, 40, 25, 3, "", function()
1272 if sg.irisState() == "Open" then
1273 sg.closeIris()
1274 elseif sg.irisState() == "Closed" then
1275 sg.openIris()
1276 end
1277 end)
1278 element.irisButton["text"] = sg.irisState() == "Closed" and "Open the iris" or (sg.irisState() == "Open" and "Close the iris" or "Switch the iris")
1279 element.autoIris = gui:addLabel(45, 44, 7, "Mode:")
1280 gui:addButton(52, 44, 18, 1, data.config.autoIris and "automatic" or "manual", function(self)
1281 data.config.autoIris = not data.config.autoIris
1282 self["text"] = data.config.autoIris and "automatic" or "manual"
1283 self:draw()
1284 saveConfig()
1285 end)
1286 ------------------------
1287
1288 -- Remote iris authentication
1289 gui:addLabel(110, 5, 7, "Port:")
1290 gui:addButton(120, 5, 15, 1, data.config.portStatus and "Open" or "Closed", function(self)
1291 if data.config.portStatus then
1292 modem.close(data.config.port)
1293 self["text"] = "Closed"
1294 self["text-color"] = 0xff0000
1295 else
1296 modem.open(data.config.port)
1297 self["text"] = "Open"
1298 self["text-color"] = 0x00ff00
1299 end
1300 data.config.portStatus = not data.config.portStatus
1301 self:draw()
1302 saveConfig()
1303 end)["text-color"] = data.config.portStatus and 0x00ff00 or 0xff0000
1304 gui:addLabel(110, 6, 10, "Channel:")
1305 gui:addButton(120, 6, 15, 1, tostring(data.config.port), function(self)
1306 channelCodeChooser(true)
1307 self.text = tostring(data.config.port)
1308 self:draw()
1309 end)
1310 gui:addLabel(110, 7, 6, "Code:")
1311 gui:addButton(120, 7, 15, 1, tostring(data.config.codes[1]), function(self)
1312 channelCodeChooser(false)
1313 self.text = tostring(data.config.codes[1])
1314 self:draw()
1315 end)
1316 ------------------------
1317
1318 -- Address calculator
1319 gui:addButton(110, 9, 25, 1, "Address calculator", function() coordsCalculator() end)
1320 ------------------------
1321
1322 -- Address groups
1323 gui:addLabel(3, 10, 16, "Address groups:")
1324 element.groupSelector = gui:addComboBox(3, 11, 40, {})
1325 element.groupSelector.onSelected = function (gs, pos, value)
1326 data.activeGroup = data.groups[pos]
1327 element.list:refreshList()
1328 end
1329 element.groupSelector.refresh = function (gs)
1330 local groups = {}
1331 for _, g in pairs(data.groups) do
1332 table.insert(groups, g.name)
1333 end
1334
1335 gs:updateList(groups)
1336 end
1337 element.groupSelector:refresh()
1338 gui:addButton(33, 10, 10, 1, "Edit", manageGroups)
1339 ------------------------
1340
1341 -- Address search
1342 gui:addLabel(3, 16, 20, "Search for address:")
1343 element.addressFilter = gui:addTextField(3, 17, 25)
1344 gui:addButton(32, 17, 10, 1, "Filter", function ()
1345 element.list:refreshList()
1346 end)
1347 ------------------------
1348
1349 tmp.GMLbgcolor = GMLextractProperty(gui, GMLgetAppliedStyles(gui), "fill-color-bg")
1350 gui:run()
1351end
1352
1353local function main()
1354 local address = sg.localAddress():sub(1, 7)
1355 sgChunk = addressToChunk(address)
1356
1357 require("term").setCursorBlink(false)
1358 darkStyle = gml.loadStyle("dark")
1359 createUI()
1360end
1361
1362local function __eventListener(...)
1363 local ev = {...}
1364 if ev[1] == "sgDialIn" then
1365 if data.config.autoIris then
1366 event.timer(5, function()
1367 sg.closeIris()
1368 end)
1369 end
1370 timeToClose = 300
1371 element.connectionType["text"] = "Incomming connection"
1372 element.connectionType:show()
1373 element.remoteAddress["text"] = "Remote address: " .. separateAddress(sg.remoteAddress())
1374 element.remoteAddress:show()
1375 elseif ev[1] == "sgIrisStateChange" then
1376 element.iris["text"] = "Iris: " .. sg.irisState()
1377 element.iris:draw()
1378 if ev[3] == "Closed" then
1379 element.irisButton["text"] = "Open the iris"
1380 element.irisButton:draw()
1381 elseif ev[3] == "Open" then
1382 element.irisButton["text"] = "Close the iris"
1383 element.irisButton:draw()
1384 end
1385 if ev[3] == "Open" or ev[3] == "Closed" then element.stargate:draw() end
1386 elseif ev[1] == "sgStargateStateChange" then
1387 element.status["text"] = "Status: " .. sg.stargateState()
1388 element.status:draw()
1389 if ev[3] == "Idle" then
1390 if data.config.autoIris then
1391 event.timer(2, function()
1392 sg.openIris()
1393 end)
1394 end
1395 element.connectionType:hide()
1396 element.remoteAddress:hide()
1397 element.timeout:hide()
1398 element.dial["text"] = "Open a tunnel"
1399 element.dial:draw()
1400 countdownTimer:stop()
1401 timeToClose = 0
1402 element.stargate:draw()
1403 element.stargate:lockSymbol(0)
1404 elseif ev[3] == "Connected" then
1405 element.remoteAddress["text"] = "Remote address: " .. separateAddress(sg.remoteAddress())
1406 element.remoteAddress:show()
1407 element.timeout["text"] = "Remaining time: "
1408 element.timeout:show()
1409 element.dial["text"] = "Close the tunnel"
1410 element.dial:draw()
1411 countdownTimer:start(1, timeToClose)
1412 element.stargate:draw()
1413 element.distance.text = "Distance: " .. tostring(computeDistance(sg.remoteAddress()))
1414 element.distance:draw()
1415 end
1416 elseif ev[1] == "sgChevronEngaged" then
1417 element.stargate:lockSymbol(ev[3])
1418 elseif ev[1] == "modem_message" then
1419 if ev[4] == data.config.port then
1420 local matches = false
1421 if type(data.config.codes) == "table" then
1422 for _, code in pairs(data.config.codes) do
1423 matches = matches or code == ev[7]
1424 end
1425 end
1426 if matches then
1427 os.sleep(0.1)
1428 modem.send(ev[3], ev[6], serial.serialize({true, "Iris open", irisTimeout}))
1429 if sg.irisState() == "Closed" then
1430 sg.openIris()
1431 irisTime = irisTimeout
1432 irisTimer = event.timer(1, irisTimerFunction, irisTimeout + 5)
1433 end
1434 else
1435 os.sleep(0.1)
1436 modem.send(ev[3], ev[6], serial.serialize({false, "Wrong iris code!", irisTimeout}))
1437 end
1438 end
1439 end
1440end
1441
1442local function eventListener(...)
1443 local args = {...}
1444 local status, msg = pcall(__eventListener, table.unpack(args))
1445 if not status then
1446 GMLmessageBox("Error: " .. msg)
1447 end
1448end
1449
1450local newAddress = chooseInterface()
1451if type(newAddress) == "string" and newAddress:len() == 0 then
1452 newAddress = nil
1453elseif type(newAddress) == "nil" then
1454 return
1455end
1456if not loadConfig(newAddress) then return end
1457
1458event.listen("sgDialIn", eventListener)
1459event.listen("sgIrisStateChange", eventListener)
1460event.listen("sgStargateStateChange", eventListener)
1461event.listen("sgChevronEngaged", eventListener)
1462event.listen("modem_message", eventListener)
1463timerEneriga = event.timer(5, energyRefresh, math.huge)
1464main()
1465event.cancel(timerEneriga)
1466if data.config.port and modem.isOpen(data.config.port) then modem.close(data.config.port) end
1467event.ignore("sgDialIn", eventListener)
1468event.ignore("sgIrisStateChange", eventListener)
1469event.ignore("sgStargateStateChange", eventListener)
1470event.ignore("sgChevronEngaged", eventListener)
1471event.ignore("modem_message", eventListener)
1472saveConfig()