· 4 years ago · Apr 25, 2021, 10:38 PM
1--Extractor program
2local fts = {[1]={["name"]="swarmsetup",["contents"]="local dir = shell.dir()\
3os.loadAPI(fs.combine('/', 'apis.lua'))\
4\
5function setLabel(prefix)\
6 if not os.setComputerLabel(prefix) then\
7 local id = os.getComputerID()\
8 local label\
9 if turtle then\
10 label = prefix .. '_' .. id\
11 else\
12 label = prefix .. '_' .. id\
13 end\
14 os.setComputerLabel(label)\
15 print(\"Label set as \" .. label)\
16 end\
17end\
18\
19function writeStartup(role)\
20\
21 if fs.exists('/startup.old') then\
22 fs.delete('/startup.old')\
23 end\
24 if fs.exists('/startup') then\
25 fs.copy('/startup', '/startup.old')\
26 end\
27\
28 local contents = string.format(\
29 'shell.run(\"%s\")\\n',\
30 role)\
31\
32 Util.writeFile('/startup', contents)\
33end\
34\
35function setup(role, labelPrefix, mustBeTurtle)\
36 if mustBeTurtle and not turtle then\
37 error('This program can only be run on a turtle')\
38 end\
39 setLabel(labelPrefix)\
40 writeStartup(role)\
41 if mustBeTurtle then\
42 local fuel = turtle.getFuelLevel()\
43 print('Fuel: ' .. fuel)\
44 if fuel < 5000 then\
45 print('Please add fuel (5000 is recommended)')\
46 end\
47 end\
48end\
49\
50local startPage = UI.Page({\
51 titleBar = UI.TitleBar({\
52 title = 'Turtle Mining Swarm setup'\
53 }),\
54 menu = UI.Menu({\
55 x = 12,\
56 y = 5,\
57 menuItems = { \
58 { prompt = 'Set as Mine Worker', event = 'mineWorker' }, \
59 { prompt = 'Set as Mining Boss', event = 'miningBoss' }, \
60 { prompt = 'Set as Stats Viewer', event = 'miningStats' }, \
61 { prompt = 'Quit', event = 'quit' }\
62 }\
63 })\
64})\
65\
66function startPage:eventHandler(event)\
67\
68 if event.type == 'mineWorker' then\
69 Event.exitPullEvents()\
70 UI.term:reset()\
71 setup('mineWorker', 'miner', true)\
72 print('Break and load this turtle into the boss when ready')\
73\
74 elseif event.type == 'miningBoss' then\
75 Event.exitPullEvents()\
76 UI.term:reset()\
77 setup('miningBoss', 'miningBoss', true)\
78 print('Ready to boss some miners')\
79 print('Reboot when ready')\
80\
81 elseif event.type == 'miningStats' then\
82 Event.exitPullEvents()\
83 UI.term:reset()\
84 setup('miningStatus.lua', 'miningStatus', false)\
85 print('Best to start this computer/turtle before the boss')\
86 print('Reboot when ready')\
87\
88 elseif event.type == 'quit' then\
89 Event.exitPullEvents()\
90 UI.term:reset()\
91 end\
92\
93 return UI.Page.eventHandler(self, event)\
94end\
95\
96Logger.disable()\
97\
98local args = { ... }\
99if #args > 0 then\
100 if args[1] == 'miner' then\
101 os.queueEvent('char', '1')\
102 elseif args[1] == 'boss' then\
103 os.queueEvent('char', '2')\
104 elseif args[1] == 'stats' then\
105 os.queueEvent('char', '3')\
106 else\
107 error('valid options are: miner, boss, or stats')\
108 end\
109end\
110\
111UI.pager:setPage(startPage)\
112\
113Event.pullEvents()\
114",},[2]={["name"]="mineWorker",["contents"]="os.loadAPI('apis.lua')\
115\
116Peripheral.wrap(\"wireless_modem\")\
117\
118local slots\
119local fillerSlots\
120local fuelSlot\
121local condenced = false\
122\
123function initSlots()\
124 slots = TL2.getSlots()\
125 fillerSlots = { }\
126\
127 for k,slot in ipairs(slots) do\
128 if k == 1 then\
129 slot.type = 'Chest'\
130 slot.qty = 1\
131 elseif slot.qty == 1 then\
132 slot.type = 'filler'\
133 slot.qty = 64\
134 table.insert(fillerSlots, slot)\
135 else\
136 break\
137 end\
138 end\
139end\
140\
141function mineable(action)\
142 if not action.detect() then\
143 return false\
144 end\
145 for k,slot in ipairs(fillerSlots) do\
146 turtle.select(slot.slotNo)\
147 if action.compare() then\
148 if turtle.getItemCount(slot.slotNo) == 64 then\
149 condenced = false\
150 end\
151 if k ~= 1 then\
152 table.remove(fillerSlots, k)\
153 table.insert(fillerSlots, 1, slot)\
154 end\
155 if action.side == 'bottom' then\
156 return true\
157 end\
158 return false\
159 end\
160 end\
161 collectDrops(action.suck)\
162 return true\
163end\
164\
165function setFuelSlot()\
166 -- if not try and find a slot with fuel\
167 if not fuelSlot then\
168 for _,slot in pairs(slots) do\
169 if not slot.type then\
170 local qty = turtle.getItemCount(slot.slotNo)\
171 if qty > 1 then\
172 turtle.select(slot.slotNo)\
173 local fuelLevel = turtle.getFuelLevel()\
174 if turtle.refuel(1) then\
175 local fueled = turtle.getFuelLevel() - fuelLevel\
176 if fueled > 15 then -- wood is 15, ash is 20, coal is 80\
177 -- mark this one as fuel for later comparison\
178 slot.type = 'fuel'\
179 slot.qty = 64\
180 fuelSlot = slot\
181 break\
182 end\
183 end\
184 end\
185 end\
186 end\
187 end\
188end\
189\
190function refuel()\
191 setFuelSlot()\
192 if turtle.getFuelLevel() < 7500 then\
193 Logger.log('mineWorker', 'refueling')\
194 for _,slot in pairs(slots) do\
195 if not slot.type or slot.type == 'fuel' then\
196 local qty = turtle.getItemCount(slot.slotNo)\
197 if slot.type then\
198 qty = qty - 1\
199 end\
200 if qty > 0 then\
201 Logger.log('mineWorker', 'refueling ' .. qty)\
202 turtle.select(slot.slotNo)\
203 turtle.refuel(qty)\
204 end\
205 end\
206 end\
207 end\
208end\
209\
210function unload()\
211 Logger.log('mineWorker', 'unloading')\
212 TL2.setDigStrategy('cautious')\
213 if turtle.getItemCount(1) ~= 1 then\
214 turtle.select(1)\
215 if not turtle.digDown() then\
216 Logger.log('mineWorker', 'Missing ender chest')\
217 TL2.setDigStrategy('normal')\
218 return\
219 end\
220 end\
221 turtle.select(1)\
222 if not Util.tryTimed(5, function()\
223 TL2.digDown()\
224 return turtle.placeDown()\
225 end) then\
226 Logger.log('mineWorker', 'placedown failed')\
227 else\
228 TL2.reconcileInventory(slots, turtle.dropDown)\
229\
230 turtle.select(1)\
231 turtle.drop(64)\
232 turtle.digDown() \
233 end\
234 TL2.setDigStrategy('normal')\
235end\
236\
237TLC.registerAction('plug', function(args)\
238 if args.plug and not turtle.detectDown() then\
239 if getFiller(1) then\
240 turtle.placeDown()\
241 turtle.select(2)\
242 end\
243 end\
244 return true\
245end)\
246\
247TLC.registerAction('ChestUnload', function(args)\
248 local reserveSlots = args.slots\
249\
250 fillerSlots = { }\
251 for k,slot in pairs(slots) do\
252 if k <= reserveSlots then\
253 if k == 1 then\
254 slot.type = 'Chest'\
255 else\
256 slot.type = 'filler'\
257 table.insert(fillerSlots, slot)\
258 end\
259 slot.qty = 1\
260 else\
261 slot.qty = 0\
262 slot.type = nil\
263 end\
264 end\
265 unload()\
266 return true\
267end)\
268\
269function getFiller(reserve)\
270 for _,slot in pairs(fillerSlots) do\
271 if turtle.getItemCount(slot.slotNo) > reserve then\
272 turtle.select(slot.slotNo)\
273 return true\
274 end\
275 end\
276 if not condenced then\
277 condenceAll()\
278 return getFiller(reserve)\
279 end\
280 return false\
281end\
282\
283function doCondence(slot, condenceSlots)\
284 local iQty = turtle.getItemCount(slot)\
285 turtle.select(slot)\
286 for _,cslot in pairs(condenceSlots) do\
287 if cslot.slotNo ~= slot then\
288 if cslot.qty > 0 and turtle.compareTo(cslot.slotNo) then\
289 turtle.select(cslot.slotNo)\
290 turtle.transferTo(slot, cslot.qty)\
291 iQty = iQty + cslot.qty\
292 cslot.qty = 0\
293 if iQty >= 64 then\
294 break\
295 end\
296 turtle.select(slot)\
297 end\
298 end\
299 end\
300end\
301\
302function condenceAll()\
303 if not condenced then\
304 Logger.log('mineWorker', 'condencing')\
305 local firstSlot\
306 for k,slot in ipairs(slots) do\
307 if not slot.type then\
308 firstSlot = k\
309 break\
310 end\
311 end\
312 if firstSlot then\
313 Logger.log('mineWorker', 'condence: first slot: ' .. firstSlot)\
314 local condenceSlots = TL2.getFilledSlots(firstSlot)\
315 if Util.size(condenceSlots) > 0 then\
316 for _,slot in ipairs(fillerSlots) do\
317 doCondence(slot.slotNo, condenceSlots)\
318 end\
319 if fuelSlot then\
320 doCondence(fuelSlot.slotNo, condenceSlots)\
321 end\
322 end\
323 end\
324 end\
325 condenced = true\
326end\
327\
328-- let the boss know how much fuel we are carrying\
329function setReserveFuel()\
330 TL2.getState().reserveFuel = 0\
331 if fuelSlot then\
332 TL2.getState().reserveFuel = turtle.getItemCount(fuelSlot.slotNo)-1\
333 end\
334end\
335\
336TLC.registerAction('transferFuel', function()\
337 if fuelSlot then\
338 local qty = turtle.getItemCount(fuelSlot.slotNo)\
339 turtle.select(fuelSlot.slotNo)\
340 turtle.dropUp(qty-1)\
341 end\
342 TL2.getState().reserveFuel = 0\
343\
344 return true\
345end)\
346\
347TLC.registerAction('bore', function(actionArgs, action)\
348\
349 TL2.setDigStrategy('normal')\
350\
351 local loc = TL2.getState()\
352 local level = loc.z\
353 local depth = actionArgs.depth or 9000\
354\
355 while true do\
356 mine(TL2.actions.down)\
357 if loc.z <= -depth then\
358 break\
359 end\
360 if not Util.tryTimed(3, TL2.down) then\
361 break\
362 end\
363\
364 mine(TL2.actions.forward)\
365 TL2.turnRight()\
366 mine(TL2.actions.forward)\
367\
368 if loc.z % 10 == 0 then\
369 TLC.performAction('status', { requestor = action.requestor })\
370 end \
371 end\
372\
373 TL2.turnRight()\
374 mine(TL2.actions.forward)\
375\
376 TL2.turnRight()\
377 mine(TL2.actions.forward)\
378\
379 TL2.turnLeft()\
380\
381 while true do\
382 turtle.select(2)\
383 Util.tryTimed(3, TL2.up)\
384 if getFiller(2) then\
385 turtle.placeDown()\
386 end\
387 if loc.z == level then\
388 break\
389 end\
390 if loc.z >= 0 then\
391 break\
392 end\
393\
394 mine(TL2.actions.forward)\
395 TL2.turnLeft()\
396 mine(TL2.actions.forward)\
397\
398 if loc.z % 10 == 0 then\
399 TLC.performAction('status', { requestor = action.requestor })\
400 end \
401 end\
402\
403 Logger.log('mineWorker', 'fuel: ' .. turtle.getFuelLevel())\
404 if turtle.getFuelLevel() < 1500 then\
405 refuel()\
406 end\
407\
408 setReserveFuel()\
409 Logger.log('mineWorker', 'reserve fuel: ' .. TL2.getState().reserveFuel)\
410\
411 TL2.setDigStrategy('cautious')\
412 return true\
413end)\
414\
415function checkSpace()\
416 if turtle.getItemCount(16) > 0 then\
417 refuel()\
418 condenceAll()\
419 unload()\
420 turtle.select(2)\
421 end\
422end\
423\
424function collectDrops(suckAction)\
425 turtle.select(2)\
426 for i = 1, 50 do\
427 checkSpace()\
428 if not suckAction() then\
429 break\
430 end\
431 condenced = false\
432 end\
433end\
434\
435function mine(action)\
436 if mineable(action) then\
437 checkSpace()\
438 --turtle.select(2)\
439 action.dig()\
440 if action.side ~= 'bottom' and getFiller(2) then\
441 turtle.place()\
442 end\
443 end\
444end\
445\
446TL2.setDigStrategy('cautious')\
447TL2.setMode('destructive')\
448\
449local options = {\
450 logMethod = { arg = 'l', type = 'string', value = 'none',\
451 desc = 'Logging: wireless or file' }\
452}\
453Util.getOptions(options, { ... })\
454\
455Logger.disable()\
456Logger.filter('event')\
457\
458if options.logMethod.value == 'wireless' then\
459 Message.enableWirelessLogging()\
460elseif options.logMethod.value == 'file' then\
461 Logger.setFileLogging('mine.log')\
462elseif options.logMethod.value == 'screen' then\
463 Logger.setScreenLogging()\
464end\
465\
466TL2.getState().reserveFuel = 0\
467initSlots()\
468print('mineWorker started...')\
469TLC.pullEvents('mineWorker', true)\
470",},[3]={["name"]="miningBoss",["contents"]="os.loadAPI('apis.lua')\
471\
472Peripheral.wrap(\"wireless_modem\")\
473\
474local args = { ... }\
475local options = {\
476 chunks = { arg = 'c', type = 'number', value = -1,\
477 desc = 'Number of chunks to mine' },\
478 depth = { arg = 'd', type = 'number', value = 9000,\
479 desc = 'Mining depth' },\
480 plug = { arg = 'f', type = 'flag', value = false,\
481 desc = 'Fill in top hole' },\
482 logMethod = { arg = 'l', type = 'string', value = 'wireless',\
483 desc = 'Logging: file, screen' },\
484 help = { arg = 'h', type = 'flag', value = false,\
485 desc = 'Displays the options' }\
486}\
487\
488local miners = {}\
489local pickupQueue = {}\
490local workQueue = {}\
491local miningStatus = {\
492 fuel = 0,\
493 activity = nil,\
494 count = 0,\
495 version = 'v1.3a',\
496 prompt = 'm for menu'\
497}\
498\
499local mining = {\
500 status = 'idle',\
501 firstTurtle,\
502 diameter = 1,\
503 chunkIndex = 0,\
504 chunks = -1,\
505 depth = 9000,\
506 holes = 0,\
507 plug = false\
508}\
509\
510function getChunkCoordinates(diameter, index, x, y)\
511 local dirs = { -- circumference of grid\
512 { xd = 0, yd = 1, heading = 1 }, -- south\
513 { xd = -1, yd = 0, heading = 2 },\
514 { xd = 0, yd = -1, heading = 3 },\
515 { xd = 1, yd = 0, heading = 0 } -- east\
516 }\
517 -- always move east when entering the next diameter\
518 if index == 0 then\
519 dirs[4].x = x + 16\
520 dirs[4].y = y\
521 return dirs[4]\
522 end\
523 dir = dirs[math.floor(index / (diameter - 1)) + 1]\
524 dir.x = x + dir.xd * 16\
525 dir.y = y + dir.yd * 16\
526 return dir\
527end\
528\
529function getChunkLoadedBox()\
530 local ax, ay = getCornerOf(TL2.getNamedLocation('chunkborder'))\
531 if TL2.getNamedLocation('chunkloader1') then\
532 ax, ay = getCornerOf(TL2.getNamedLocation('chunkloader1'))\
533 end\
534\
535 local bx, by = getCornerOf(TL2.getNamedLocation('chunkloader2'))\
536 local boundingBox = {\
537 ax = math.min(ax, bx),\
538 ay = math.min(ay, by),\
539 bx = math.max(ax, bx)+15,\
540 by = math.max(ay, by)+15\
541 }\
542 return boundingBox\
543end\
544\
545function getBoreLocations(x, y)\
546\
547 local locations = {}\
548\
549 while true do\
550 local a = math.abs(y)\
551 local b = math.abs(x)\
552\
553 if x > 0 and y > 0 or\
554 x < 0 and y < 0 then\
555 -- rotate coords\
556 a = math.abs(x)\
557 b = math.abs(y)\
558 end\
559 if (a % 5 == 0 and b % 5 == 0) or\
560 (a % 5 == 2 and b % 5 == 1) or\
561 (a % 5 == 4 and b % 5 == 2) or\
562 (a % 5 == 1 and b % 5 == 3) or\
563 (a % 5 == 3 and b % 5 == 4) then\
564 table.insert(locations, { x = x, y = y })\
565 end\
566 if y % 2 == 0 then -- forward dir\
567 if (x + 1) % 16 == 0 then\
568 y = y + 1\
569 else\
570 x = x + 1\
571 end\
572 else\
573 if (x - 1) % 16 == 15 then\
574 if (y + 1) % 16 == 0 then\
575 break\
576 end\
577 y = y + 1\
578 else\
579 x = x - 1\
580 end\
581 end\
582 end\
583 return locations\
584end\
585\
586-- get the bore location closest to the miner\
587local function getClosestLocation(points, b)\
588 local key = 1\
589 local distance = 9000\
590 for k,a in pairs(points) do\
591 -- try and avoid floating point operation\
592 local d = math.max(a.x, b.x) - math.min(a.x, b.x) + \
593 math.max(a.y, b.y) - math.min(a.y, b.y)\
594\
595 if d < distance then\
596 d = math.sqrt(\
597 math.pow(a.x - b.x, 2) + math.pow(a.y - b.y, 2)) \
598 if d < distance then\
599 key = k \
600 distance = d \
601 if distance <= 1 then\
602 break\
603 end \
604 end \
605 end \
606 end \
607 return table.remove(points, key)\
608end \
609\
610function getCornerOf(c)\
611 return math.floor(c.x / 16) * 16, math.floor(c.y / 16) * 16\
612end\
613\
614function showSetup(message)\
615 message = message or ''\
616 errorPage.message = message\
617 UI.pager:setPage('error')\
618end\
619\
620function releaseMiners()\
621\
622 if mining.status ~= 'mining' then -- chunk loaders are already placed if mining\
623 local chunkloadersNeeded = 0\
624 if TL2.getNamedLocation('chunkloader1') then\
625 chunkloadersNeeded = 1\
626 end\
627 if TL2.getNamedLocation('chunkloader2') then\
628 chunkloadersNeeded = chunkloadersNeeded - 1\
629 end\
630 if turtle.getItemCount(1) < chunkloadersNeeded then\
631 showSetup('Not enough chunk loaders in slot 1')\
632 return false\
633 end\
634 end\
635\
636 local maxMiners = turtle.getItemCount(2)\
637 if maxMiners == 0 then\
638 showSetup('No ender chests in slot 2')\
639 end\
640\
641 if not mining.firstTurtle then\
642 local firstTurtle\
643\
644 if maxMiners == 1 then\
645 for i = 16, 3, -1 do\
646 if turtle.getItemCount(i) == 1 then\
647 firstTurtle = i\
648 break\
649 end\
650 end\
651 else\
652 for i = 3, 16 do\
653 local itemCount = turtle.getItemCount(i)\
654 if itemCount == 0 then\
655 break\
656 end\
657 if itemCount > 1 then\
658 if itemCount < maxMiners then\
659 maxMiners = itemCount\
660 end\
661 else\
662 firstTurtle = i\
663 break\
664 end\
665 end\
666 end\
667\
668 if not firstTurtle then\
669 showSetup('No turtles found in inventory')\
670 return false\
671 end\
672Logger.log('miningBoss', 'firstTurtle: ' .. firstTurtle)\
673Logger.log('miningBoss', 'maxMiners: ' .. maxMiners)\
674\
675 -- check resources\
676 local minerCount = 0\
677 for i = firstTurtle, 16 do\
678 if turtle.getItemCount(i) == 1 then\
679 minerCount = minerCount + 1\
680 end\
681 end\
682Logger.log('miningBoss', 'minerCount: ' .. minerCount)\
683 if minerCount > maxMiners then\
684 for i = 3, firstTurtle-1 do\
685 if turtle.getItemCount(i) == maxMiners then\
686 showSetup('Not enough resources in slot ' .. i)\
687 return false\
688 end\
689 end\
690 end\
691 -- sanity checking\
692 for i = firstTurtle, 16 do\
693 if turtle.getItemCount(i) > 1 then\
694 showSetup('Invalid setup')\
695 return false\
696 end\
697 end\
698 mining.firstTurtle = firstTurtle\
699 end\
700\
701 for i = mining.firstTurtle, 16 do\
702 if turtle.getItemCount(i) == 1 then\
703 queueWork(51, 'releaseMiner-' .. i, 'releaseMiner', { slot = i })\
704 end\
705 end\
706\
707 return true\
708end\
709\
710function updateStatus(miner, status)\
711\
712 local function getIndicator(value, past)\
713 local ind = ' '\
714 if past then\
715 if value > past then\
716 ind = '^'\
717 elseif value < past then\
718 ind = 'v'\
719 end\
720 end\
721 return ind\
722 end\
723\
724 miner.statusD = miner.xStatus\
725 if miner.xStatus ~= 'deployed' then\
726 miner.statusD = miner.xStatus\
727 if miner.xStatus == 'pickup' then\
728 miner.statusD = 'pkup'\
729 end\
730 end\
731\
732 if not miner.lastFuel then\
733 miner.lastFuel = miner.fuel\
734 end\
735 miner.fuelD = string.format(\"%s%dk\",\
736 getIndicator(math.floor(miner.fuel/1024), math.floor(miner.lastFuel/1024)),\
737 math.floor(miner.fuel/1024))\
738 miner.lastFuel = miner.fuel\
739\
740 miner.depthD = string.format(\"%s%d\",\
741 getIndicator(status.z, miner.lastLocation.z),\
742 status.z)\
743 miner.lastLocation = miner.location\
744 miner.coordsD = string.format(\"%d,%d\", status.x, status.y)\
745\
746 miner.timestamp = os.clock()\
747\
748 local timer = Event.getNamedTimer('statusTimer')\
749 if not timer or not Event.isTimerActive(timer) then\
750 Event.addNamedTimer('statusTimer', 1, false, function()\
751 if statusPage.enabled then\
752 statusPage:draw()\
753 end\
754 end)\
755 end\
756end\
757\
758function releaseMiner(slot)\
759\
760 if mining.status ~= 'mining' then\
761 return true\
762 end\
763\
764 showActivity('Releasing miner')\
765 local x, y = getCornerOf(TL2.getNamedLocation('chunkloader2'), 16, 16, 1)\
766 local box = TL2.pointToBox({ x = x, y = y, z = 0 }, 16, 16, 1)\
767 while true do\
768 local deployPt = TL2.createPoint(TL2.getState())\
769 deployPt.x = deployPt.x - 2\
770 deployPt.y = deployPt.y - 2\
771 local deployBox = TL2.pointToBox(deployPt, 4, 4, 1)\
772 TL2.boxContain(box, deployBox)\
773 if not TL2.pointInBox(TL2.getState(), deployBox) or TL2.isTurtleAt('bottom') then\
774 local r = Util.random(15)\
775 TL2.goto(deployBox.ax + math.floor(r / 4), deployBox.ay + r % 4, 0)\
776 end\
777 if not turtle.detectDown() then\
778 break\
779 end\
780 if not TL2.isTurtleAt('bottom') and TL2.digDown() then\
781 break\
782 end\
783 end\
784\
785 if turtle.getItemCount(slot) ~= 1 then\
786 showActivity('Not a turtle')\
787 return true\
788 end\
789\
790 for i = 2, mining.firstTurtle - 1 do\
791 if turtle.getItemCount(i) == 0 then\
792 showActivity('Not enough resources')\
793 return true\
794 end\
795 end\
796\
797 -- place miner\
798 if not TL2.placeDown(slot) then\
799 -- someone took out a turtle from the inventory :(\
800 return true\
801 end\
802 os.sleep(.1)\
803\
804 -- give miner resources\
805 for i = 2, mining.firstTurtle - 1 do\
806 TL2.select(i)\
807 turtle.dropDown(1)\
808 end\
809\
810 peripheral.call('bottom', 'turnOn')\
811\
812 if not Util.tryTimed(5,\
813 function()\
814 local id = peripheral.call('bottom', 'getID')\
815 if id then\
816 local pt = TL2.createPoint(TL2.getState())\
817 pt.heading = TL2.getState().heading\
818 miners[id] = {\
819 id = id,\
820 status = 'idle',\
821 xStatus = 'new',\
822 deployLocation = pt,\
823 location = pt,\
824 lastLocation = pt,\
825 name = 'turtle_' .. tostring(id)\
826 }\
827 TLC.requestState(id)\
828 return true\
829 end\
830 os.sleep(.1)\
831 return false\
832 end) then\
833 -- turtle won't start or this is not a turtle\
834 TL2.select(slot)\
835 turtle.digDown()\
836 end\
837\
838 --pager:setPage('status')\
839 return true\
840end\
841\
842TLC.registerAction('releaseMiner', function(args)\
843 return releaseMiner(args.slot)\
844end)\
845\
846function collectMiner(miner)\
847 showActivity('Picking up turtle')\
848\
849 if not TL2.pointInBox(miner.location, getChunkLoadedBox()) then\
850 shutdownCommand(miner)\
851 Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' too far away')\
852 return true\
853 end\
854\
855 TL2.goto(miner.location.x, miner.location.y, 0)\
856\
857 if not TL2.isTurtleAt('bottom') then\
858 queueWork(3, 'rescue-' .. miner.id, 'rescueMiner', miner)\
859--[[\
860 Logger.log('shutting down ' .. miner.id .. ' unable to locate')\
861 TLC.sendAction(miner.id, 'shutdown', location)\
862 miners[miner.id] = nil\
863--]]\
864 return true\
865 end\
866\
867 local id = peripheral.call('bottom', 'getID')\
868 if id and miners[id] then\
869 local miner = miners[id]\
870 local slots = TL2.getInventory()\
871 for i = 2, mining.firstTurtle-1 do\
872 TL2.select(i)\
873 if turtle.suckDown() then\
874 slots[i].qty = slots[i].qty + 1\
875 end\
876 end\
877 TL2.reconcileInventory(slots)\
878 local slot = TL2.selectOpenSlot(3)\
879 miners[miner.id] = nil\
880 if not slot then\
881 -- too many turtles for inventory\
882 peripheral.call('bottom', 'shutdown')\
883 Logger.log('miningBoss', 'shutting down ' .. id .. ' no room')\
884 elseif turtle.digDown() then\
885 if mining.status == 'mining' and miner.fuel > 1024 then\
886 -- check to see if we mined up something other than a turtle\
887 if turtle.getItemCount(slot) == 1 then\
888 releaseMiner(slot)\
889 end\
890 end\
891 end\
892 UI.pager:getCurrentPage():draw()\
893 if Util.size(miners) == 0 then\
894 UI.pager:setPage('menu')\
895 local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex\
896 if mining.status == 'recalling' and\
897 mining.chunks ~= -1 and \
898 chunks >= mining.chunks then\
899 -- automatically exit if we exceeded requested diameter\
900 Event.exitPullEvents()\
901 end\
902 end\
903 end\
904 return true\
905end\
906\
907function setStatus(status)\
908 mining.status = status\
909 showActivity()\
910end\
911\
912function showActivity(currentActivity)\
913 if currentActivity then\
914 miningStatus.activity = currentActivity\
915 Logger.log('miningBoss', currentActivity)\
916 else\
917 miningStatus.activity = mining.status\
918 end\
919 miningStatus.count = Util.size(miners)\
920\
921 if UI.pager:getCurrentPage().statusBar then\
922 UI.pager:getCurrentPage().statusBar:draw()\
923 end\
924end\
925\
926optionsPage = UI.Page({\
927 titleBar = UI.TitleBar({\
928 title = 'Mining Options',\
929 previousPage = true\
930 }),\
931 form = UI.Form({\
932 fields = {\
933 { label = 'Chunks', key = 'chunks',display = UI.Form.D.entry, limit = 4,\
934 help = 'Number of chunks to mine' },\
935 { label = 'Depth', key = 'depth', display = UI.Form.D.entry, limit = 3,\
936 help = 'Leave blank for unlimited' },\
937 { label = 'Fill top hole', key = 'plug', display = UI.Form.D.chooser,\
938 choices = {\
939 { name = 'No', value = 'n' },\
940 { name = 'Yes', value = 'y' },\
941 },\
942 width = 7, help = '' },\
943 { text = 'Accept', event = 'accept', display = UI.Form.D.button,\
944 x = 5, y = 5, width = 10 },\
945 { text = 'Cancel', event = 'cancel', display = UI.Form.D.button,\
946 x = 19, y = 5, width = 10 }\
947 },\
948 labelWidth = 18,\
949 valueWidth = UI.term.width-18,\
950 x = 5,\
951 y = 4,\
952 height = UI.term.height-4\
953 }),\
954 statusBar = UI.StatusBar()\
955})\
956\
957function optionsPage:enable()\
958 local t = {\
959 plug = 'n',\
960 depth = '',\
961 chunks = '',\
962 }\
963\
964 if mining.plug then\
965 t.plug = 'y'\
966 end\
967 if mining.depth ~= 9000 then\
968 t.depth = tostring(mining.depth)\
969 end\
970 if mining.chunks ~= -1 then\
971 t.chunks = tostring(mining.chunks)\
972 end\
973\
974 self.form:setValues(t)\
975 self:focusFirst()\
976end\
977\
978function optionsPage:eventHandler(event)\
979\
980 if event.type == 'focus_change' then\
981 self.statusBar:setStatus(event.focused.help)\
982 self.statusBar:draw()\
983\
984 elseif event.type == 'cancel' then\
985 UI.pager:setPreviousPage()\
986\
987 elseif event.type == 'accept' then\
988 local values = self.form.values\
989\
990 values.chunks = tonumber(values.chunks)\
991 if not values.chunks or not tonumber(values.chunks) then\
992 values.chunks = -1\
993 end\
994\
995 values.depth = tonumber(values.depth)\
996 if not values.depth or not tonumber(values.depth) then\
997 values.depth = 9000\
998 end\
999\
1000 if values.plug == 'Y' or values.plug == 'y' then\
1001 values.plug = true\
1002 else\
1003 values.plug = false\
1004 end\
1005\
1006 mining.depth = values.depth\
1007 mining.chunks = values.chunks\
1008 mining.plug = values.plug\
1009\
1010 UI.pager:setPreviousPage()\
1011 end\
1012\
1013 return UI.Page.eventHandler(self, event)\
1014end\
1015\
1016\
1017--[[ -- menuPage -- ]]--\
1018menuPage = UI.Page({\
1019 titleBar = UI.TitleBar({\
1020 title = 'Turtle Mining Swarm ' .. miningStatus.version\
1021 }),\
1022 menu = UI.Menu({\
1023 centered = true,\
1024 y = 4,\
1025 width = UI.term.width,\
1026 menuItems = {\
1027 { prompt = 'Deploy Miners', event = 'deploy' },\
1028 { prompt = 'Options', event = 'options' },\
1029 { prompt = 'Stop mining', event = 'stop' },\
1030 { prompt = 'Status', event = 'status' },\
1031 { prompt = 'Help', event = 'help' },\
1032 { prompt = 'Quit', event = 'quit' }\
1033 }\
1034 }),\
1035--[[\
1036 progressWindow = UI.Window({\
1037 x = UI.term.width - 20,\
1038 y = 3,\
1039 width = 18,\
1040 height = 9,\
1041 backgroundColor = colors.blue,\
1042 totalHolesProgressBar = UI.VerticalMeter({\
1043 y = 7,\
1044 x = 2,\
1045 height = 8\
1046 }),\
1047 chunkHolesProgressBar = UI.VerticalMeter({\
1048 y = 10,\
1049 x = 2,\
1050 height = 8\
1051 }),\
1052 }),\
1053--]]\
1054 statusBar = UI.StatusBar({\
1055 columns = { \
1056 { '', 'activity', UI.term.width - 13 },\
1057 { '', 'fuel', 10 }\
1058 },\
1059 status = miningStatus\
1060 })\
1061})\
1062\
1063function menuPage:enable()\
1064 local fuel = turtle.getFuelLevel()\
1065 if fuel > 9999 then\
1066 miningStatus.fuel = string.format('Fuel: %dk', math.floor(fuel/1000))\
1067 else\
1068 miningStatus.fuel = 'Fuel: ' .. turtle.getFuelLevel()\
1069 end\
1070end\
1071\
1072function menuPage:eventHandler(event)\
1073 if event.type == 'deploy' then\
1074 if releaseMiners() then\
1075 setStatus('mining')\
1076 UI.pager:setPage('status')\
1077 if not TL2.getNamedLocation('chunkloader2') then\
1078 TL2.gotoNamedLocation('chunkborder')\
1079 TL2.placeUp(1)\
1080 TL2.saveNamedLocation('chunkloader2', 0, 0, 0)\
1081 Message.broadcast('alive')\
1082 end\
1083 end\
1084\
1085 elseif event.type == 'options' then\
1086 UI.pager:setPage('options')\
1087\
1088 elseif event.type == 'stop' then\
1089 if mining.status == 'mining' then\
1090 UI.pager:setPage('status')\
1091 setStatus('recalling')\
1092 end\
1093\
1094 elseif event.type == 'status' then\
1095 UI.pager:setPage('status')\
1096\
1097 elseif event.type == 'help' then\
1098 showSetup()\
1099\
1100 elseif event.type == 'quit' then\
1101 Event.exitPullEvents()\
1102 return true\
1103 end\
1104\
1105 return UI.Page.eventHandler(self, event)\
1106end\
1107\
1108--[[ -- statusPage -- ]]--\
1109statusPage = UI.Page({\
1110 grid = UI.Grid({\
1111 columns = {\
1112 { 'Name', 'name', 13 },\
1113 { 'Fuel', 'fuelD', 5 },\
1114 { 'Depth', 'depthD', 6 },\
1115 --{ 'Coords', 'coordsD', 6 },\
1116 { 'Time', 'timeD', 5 },\
1117 { '', 'statusD', 4 }\
1118 },\
1119 sortColumn = 'name',\
1120 pageSize = 11,\
1121 width = UI.term.width,\
1122 t = miners,\
1123 sizeMethod = 'count'\
1124 }),\
1125 statusBar = UI.StatusBar({\
1126 columns = { \
1127 { '', 'activity', UI.term.width - 13 },\
1128 { '', 'prompt', 10 }\
1129 },\
1130 --backgroundColor = colors.blue,\
1131 status = miningStatus\
1132 })\
1133})\
1134\
1135function statusPage:eventHandler(event)\
1136 if event.type == 'key' then\
1137 if event.key == 'm' or event.key == 'q' or event.key == 'enter' then\
1138 UI.pager:setPage('menu')\
1139 return true\
1140 end\
1141 end\
1142 return self.grid:eventHandler(event)\
1143end\
1144\
1145function statusPage:draw()\
1146 for _,miner in pairs(miners) do\
1147 if miner.timestamp then\
1148 miner.timeD =\
1149 string.format(\"%ds\",\
1150 math.floor(os.clock()-miner.timestamp))\
1151 end\
1152 end\
1153 self.grid:draw()\
1154 self.statusBar:draw()\
1155end\
1156\
1157--[[-- errorPage --]]--\
1158errorPage = UI.Page({\
1159 titleBar = UI.TitleBar({\
1160 title = 'Configuration'\
1161 }),\
1162 window = UI.Window({\
1163 y = 2,\
1164 height = UI.term.height -1,\
1165 backgroundColor = colors.blue,\
1166 focus = function() end\
1167 })\
1168})\
1169\
1170function errorPage:draw()\
1171 self.titleBar:draw()\
1172 self.window:draw()\
1173 self.window:setCursorPos(1, 1)\
1174 self.window:print(self.message)\
1175 self.window:setCursorPos(1, 3)\
1176 self.window:print('Slot 1: 2 chunk loaders')\
1177 self.window:print('Slot 2: ender chests ')\
1178 self.window:print('Slot 3 through x: resources that should not be mined')\
1179 self.window:print('Place turtles in the remainder of the slots immediately following the resources')\
1180 self.window:print('')\
1181 self.window:print('Press any key to continue')\
1182end\
1183\
1184function errorPage:eventHandler(event)\
1185 if event.type == 'key' then\
1186 UI.pager:setPage('menu')\
1187 end\
1188end\
1189\
1190-- tell the miner to unload chest, go to pickup plane and report when ready\
1191function pickupCommand(miner)\
1192 if miner.xStatus ~= 'pickup' then\
1193 TLC.sendActions(miner.id, {\
1194 { action = 'ChestUnload', args = { slots = mining.firstTurtle-2 }},\
1195 { action = 'gotoZ', args = {\
1196 z = 0, digStrategy = 'cautious', mode = 'destructive' }},\
1197 { action = 'status' }\
1198 })\
1199 miner.xStatus = 'pickup'\
1200 end\
1201end\
1202\
1203function shutdownCommand(miner)\
1204 miners[miner.id] = nil\
1205 TLC.sendActions(miner.id, {\
1206 { action = 'ChestUnload', args = { slots = mining.firstTurtle-2 }},\
1207 { action = 'shutdown' }\
1208 })\
1209end\
1210\
1211function transferFuelCommand(miner)\
1212 TLC.sendActions(miner.id, {\
1213 { action = 'gotoZ', args = {\
1214 z = 0, digStrategy = 'cautious', mode = 'destructive' }},\
1215 { action = 'transferFuel' }\
1216 })\
1217end\
1218\
1219function boreCommand(miner)\
1220 local bore = getClosestLocation(mining.locations,\
1221 miner.location)\
1222\
1223 mining.holes = mining.holes + 1 -- for status monitor only\
1224\
1225 miner.location.x = bore.x\
1226 miner.location.y = bore.y\
1227 miner.xStatus = 'busy'\
1228\
1229 local actions = {\
1230 { action = 'gotoZ', args = {\
1231 z = -1, -- make sure he is on the correct plane\
1232 digStrategy = 'cautious',\
1233 mode = 'destructive' }},\
1234 { action = 'plug', args = {\
1235 plug = mining.plug }},\
1236 { action = 'goto', args = {\
1237 x = bore.x, y = bore.y, z = -1,\
1238 digStrategy = 'cautious',\
1239 mode = 'destructive' }},\
1240 { action = 'gotoZ', args = {\
1241 z = -2,\
1242 digStrategy = 'cautious',\
1243 mode = 'destructive' }},\
1244 { action = 'bore', args = {\
1245 depth = mining.depth }},\
1246 { action = 'status' }\
1247 }\
1248 TLC.sendActions(miner.id, actions)\
1249 saveState()\
1250end\
1251\
1252TLC.registerAction('refuelFromMiner', function(miner)\
1253 local slot = TL2.selectOpenSlot()\
1254 if slot and TL2.pointInBox(miner.location, getChunkLoadedBox()) then\
1255 showActivity('Refueling')\
1256 TL2.goto(miner.location.x, miner.location.y)\
1257 transferFuelCommand(miner)\
1258 Event.waitForEvent('turtle_inventory', 3)\
1259 turtle.select(slot)\
1260 turtle.refuel(64)\
1261 if menuPage.enabled then\
1262 menuPage:draw()\
1263 end\
1264 end\
1265 return true\
1266end)\
1267\
1268TLC.registerAction('rescueMiner', function(miner)\
1269\
1270 --if miner.xStatus ~= 'lost' then\
1271 --return true\
1272 --end\
1273\
1274 local distance\
1275 Util.tryTimes(5, function()\
1276 Message.send(miner.id, 'alive')\
1277 _,_,_,distance = Message.waitForMessage('isAlive', 1, miner.id)\
1278 return distance\
1279 end)\
1280\
1281 if not distance then\
1282 if not miner.retryCount then\
1283 miner.retryCount = 0\
1284 end\
1285 miner.retryCount = miner.retryCount + 1\
1286 if miner.retryCount > 3 then\
1287 shutdownCommand(miner)\
1288 Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' unable to locate')\
1289 else\
1290 queueWork(3, 'rescue-' .. miner.id, 'rescueMiner', miner)\
1291 end\
1292 return true\
1293 end\
1294\
1295 local boundingBox = getChunkLoadedBox()\
1296\
1297 showActivity('Rescuing turtle')\
1298 --pager:setPage('status')\
1299 local location = TLC.tracker(miner.id, distance, true, boundingBox)\
1300 if location then\
1301 -- miners start out 1 below boss plane\
1302 location.z = - location.z + 1\
1303 TLC.sendAction(miner.id, 'setLocation', location)\
1304 end\
1305\
1306 if not location or (args.fuel == 0 and location.z ~= 0) then\
1307 -- not in a place we can pick up (don't want to leave plane)\
1308 shutdownCommand(miner)\
1309 Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' no fuel or too far away')\
1310 else\
1311 miner.xStatus = 'lost'\
1312 pickupCommand(miner)\
1313 end\
1314\
1315 UI.pager:getCurrentPage():draw()\
1316 Message.broadcast('alive')\
1317\
1318 return true\
1319end)\
1320\
1321Message.addHandler('turtleStatus', function(h, id, msg, distance)\
1322\
1323 local status = msg.contents\
1324\
1325--Logger.log(string.format('%d %s', id, status.status))\
1326\
1327 local miner = miners[id]\
1328\
1329--if miner then\
1330--Logger.log('xstatus: ' .. miner.xStatus)\
1331--end\
1332\
1333 if not miner then\
1334 Message.send(id, 'alive')\
1335 if mining.status ~= 'mining' or status.status ~= 'idle' then\
1336 return\
1337 end\
1338 miners[id] = {\
1339 id = id,\
1340 xStatus = 'lost',\
1341 lastLocation = { x = 0, y = 0, z = 0 }\
1342 }\
1343 miner = miners[id]\
1344 if status.x == 0 and status.y == 0 and status.z == 0 then\
1345 queueWork(3, 'rescue-' .. miner.id, 'rescueMiner', miner)\
1346 elseif TL2.pointInBox(status, getChunkLoadedBox()) then\
1347 miner.name = tostring(status.name)\
1348 miner.status = status.status\
1349 miner.fuel = status.fuel\
1350 miner.location = { x = status.x, y = status.y, z = status.z }\
1351 pickupCommand(miner)\
1352 return\
1353 else\
1354 queueWork(3, 'rescue-' .. miner.id, 'rescueMiner', miner)\
1355 end\
1356 end\
1357\
1358 miner.name = tostring(status.name)\
1359 miner.status = status.status\
1360 miner.fuel = status.fuel\
1361 miner.location = { x = status.x, y = status.y, z = status.z }\
1362\
1363 if miner.xStatus == 'new' then\
1364 -- just deployed\
1365 TLC.sendAction(id, 'setLocation', miner.deployLocation)\
1366 miner.location = miner.deployLocation\
1367 miner.xStatus = 'deployed' -- needed ?? shouldn't it be 'idle'\
1368 end\
1369\
1370 if miner.xStatus == 'lost' then\
1371 -- ignore\
1372\
1373 elseif miner.xStatus == 'pickup' then\
1374 if not pickupQueue[miner.id] then\
1375 pickupQueue[miner.id] = miner\
1376 queueWork(50, 'checkMiners', 'checkMiners')\
1377 end\
1378\
1379 elseif miner.status == 'idle' then\
1380\
1381 if mining.status == 'recalling' then\
1382 pickupCommand(miner)\
1383\
1384 elseif mining.status == 'mining' and TL2.getState().status == 'idle' then\
1385\
1386 if msg.contents.fuel > 1024 then\
1387 if #mining.locations == 0 then\
1388 if not isQueued('moveChunkLoaders') then\
1389 queueWork(75, 'moveChunkLoaders', 'moveChunkLoaders')\
1390 end\
1391 else\
1392 if turtle.getFuelLevel() < 15000 and status.reserveFuel > 0 then\
1393 TLC.performAction('refuelFromMiner', miner)\
1394 end\
1395 boreCommand(miner)\
1396 end\
1397 else\
1398 Logger.log('miningBoss', 'miner ' .. miner.id .. ' low fuel')\
1399 pickupCommand(miner)\
1400 end\
1401 end\
1402 end\
1403 updateStatus(miner, status)\
1404--Logger.log('end xstatus: ' .. miner.xStatus)\
1405end)\
1406\
1407function placeChunkLoader(direction, x, y, heading)\
1408 if direction == 'up' then\
1409 TL2.goto(x, y, 0)\
1410 TL2.placeUp(1)\
1411 TL2.saveNamedLocation('chunkloader1', x, y, 0)\
1412 else\
1413 TL2.goto(x, y, 0, heading)\
1414 TL2.up()\
1415 TL2.place(1)\
1416 TL2.down()\
1417 local heading = TL2.getHeadingInfo()\
1418 TL2.saveNamedLocation('chunkloader2',\
1419 x + heading.xd, y + heading.yd, 0)\
1420 end\
1421end\
1422\
1423function collectChunkLoaders()\
1424 showActivity('Collecting chunk loaders')\
1425 if TL2.gotoNamedLocation('chunkloader1') then\
1426 TL2.emptySlot(1)\
1427 TL2.select(1)\
1428 turtle.digUp()\
1429 end\
1430 if TL2.gotoNamedLocation('chunkloader2') then\
1431 TL2.select(1)\
1432 turtle.digUp(1)\
1433 end\
1434 showActivity()\
1435end\
1436\
1437function outsideBox(c, box)\
1438 return c.x < box.ax or c.y < box.ay or c.x > box.bx or c.y > box.by\
1439end\
1440\
1441function collectChunkLoader(namedLocation)\
1442 local c = TL2.getNamedLocation(namedLocation)\
1443 if c then\
1444 local cb = TL2.getNamedLocation('chunkborder')\
1445 local b = { ax = cb.x, ay = cb.y, bx = cb.x + 15, by = cb.y + 15 }\
1446 if outsideBox(c, b) then\
1447 local x, y = c.x, c.y\
1448 if c.x < cb.x then\
1449 x = math.max(c.x, cb.x)\
1450 elseif c.x > cb.x+15 then\
1451 x = math.min(c.x, cb.x+15)\
1452 end\
1453 if c.y < cb.y then\
1454 y = math.max(c.y, cb.y)\
1455 elseif c.y > cb.y+15 then\
1456 y = math.min(c.y, cb.y+15)\
1457 end\
1458 TL2.goto(x, y)\
1459 TL2.headTowards(c)\
1460 TL2.up()\
1461 TL2.select(1)\
1462 turtle.dig(1)\
1463 TL2.down()\
1464 else\
1465 TL2.gotoPoint(c)\
1466 TL2.select(1)\
1467 turtle.digUp(1)\
1468 end\
1469 end\
1470end\
1471\
1472TLC.registerAction('setLocation', function()\
1473 Logger.log('miningBoss', 'ignoring set location')\
1474 return true\
1475end)\
1476\
1477-- rise above to eliminate the rednet deadzone\
1478TLC.registerAction('gps', function()\
1479\
1480 -- become a mini-gps\
1481 Message.broadcast('alive')\
1482\
1483 local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))\
1484\
1485 local function broadcastPosition()\
1486 local pt = TL2.createPoint(TL2.getState())\
1487 pt.z = pt.z + 1\
1488 Message.broadcast('position', pt)\
1489 end\
1490\
1491 TL2.goto(x + 8, y + 5, 86)\
1492 -- drop down a couple of blocks in case we hit a bedrock roof (nether)\
1493 TL2.gotoZ(TL2.getState().z - 4)\
1494 local z = TL2.getState().z\
1495 broadcastPosition()\
1496\
1497 TL2.goto(x + 8, y + 11, z)\
1498 broadcastPosition()\
1499\
1500 TL2.goto(x + 5, y + 8, z-1)\
1501 broadcastPosition()\
1502\
1503 TL2.goto(x + 11, y + 8, z-1)\
1504 broadcastPosition()\
1505\
1506 queueWork(2, 'descend', 'descend')\
1507 mining.status = 'mining'\
1508 return true\
1509end)\
1510\
1511TLC.registerAction('descend', function()\
1512 if TL2.getState().z ~= 0 then\
1513 local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))\
1514 TL2.goto(x + 8, y + 8, 0)\
1515 end\
1516 return true\
1517end)\
1518\
1519TLC.registerAction('moveChunkLoaders', function()\
1520 showActivity('Moving chunk loaders')\
1521\
1522 local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))\
1523 local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)\
1524 mining.chunkIndex = mining.chunkIndex + 1\
1525\
1526 if mining.chunkIndex >= points then\
1527 mining.diameter = mining.diameter + 2\
1528 mining.chunkIndex = 0\
1529 end\
1530\
1531 if mining.chunks ~= -1 then\
1532 local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex\
1533 if chunks >= mining.chunks then\
1534 setStatus('recalling')\
1535 return true\
1536 end\
1537 end\
1538\
1539 local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, y)\
1540\
1541 local cl1 = { x = x + nc.xd * 15, y = y + nc.yd * 15 }\
1542 local cl2 = { x = x + nc.xd * 15, y = y + nc.yd * 15 }\
1543\
1544 -- brute force calculations\
1545 if nc.heading == 0 then\
1546 cl1.y = cl1.y + 1\
1547 elseif nc.heading == 1 then\
1548 cl1.x = cl1.x + 1\
1549 elseif nc.heading == 2 then\
1550 cl1.x = nc.x + 16\
1551 cl1.y = nc.y\
1552 cl2.x = nc.x + 16\
1553 cl2.y = nc.y + 1\
1554 elseif nc.heading == 3 then\
1555 cl1.x = nc.x + 15\
1556 cl1.y = nc.y + 16\
1557 cl2.x = nc.x + 14\
1558 cl2.y = nc.y + 16\
1559 end\
1560\
1561 -- collect prev chunk's loader\
1562 collectChunkLoader('chunkloader1')\
1563\
1564 local ocl = TL2.getNamedLocation('chunkloader2')\
1565 if cl1.x == ocl.x and cl1.y == ocl.y then\
1566 -- turning a corner - no need to move 2nd chunk loader\
1567 TL2.saveNamedLocation('chunkloader1', cl1.x, cl1.y, 0)\
1568 else\
1569 -- place at edge of loaded chunk\
1570 -- now 2 in chunk\
1571 placeChunkLoader('up', cl1.x, cl1.y)\
1572\
1573 -- get the first one\
1574 collectChunkLoader('chunkloader2')\
1575 end\
1576\
1577 -- place in next chunk\
1578 placeChunkLoader('front', cl2.x, cl2.y, nc.heading)\
1579\
1580 mining.locations = getBoreLocations(nc.x, nc.y)\
1581\
1582 -- enter next chunk\
1583 TL2.gotoPoint(TL2.getNamedLocation('chunkloader2'))\
1584 TL2.saveNamedLocation('chunkborder', nc.x, nc.y, 0, 0)\
1585 queueWork(50, 'checkMiners', 'checkMiners')\
1586 return true\
1587end)\
1588\
1589-- clear state every time we move\
1590-- if the server shuts down during movement, we cannot resume\
1591-- 99.9% of the time, we should be idle\
1592TLC.registerAction('clearState', function()\
1593 fs.delete('mining.state')\
1594 return true\
1595end)\
1596TLC.actionChainStart('clearState')\
1597\
1598function saveState()\
1599 local state = TL2.getState()\
1600 mining.x = state.x\
1601 mining.y = state.y\
1602 mining.heading = state.heading\
1603 mining.namedLocations = TL2.getMemory().locations\
1604\
1605 fs.delete('mining.state')\
1606 Util.writeTable('mining.state', mining)\
1607end\
1608\
1609TLC.registerAction('saveState', function()\
1610 saveState()\
1611 showActivity()\
1612 return true\
1613end)\
1614TLC.actionChainEnd('saveState')\
1615\
1616TLC.registerAction('checkMiners', function()\
1617\
1618 local chunkborder = TL2.getNamedLocation('chunkborder')\
1619\
1620 -- pickup any that are ready\
1621 if Util.size(pickupQueue) > 0 then\
1622 if TL2.selectOpenSlot(3) or mining.status == 'recalling' then\
1623 for _,miner in pairs(pickupQueue) do\
1624 -- pickup any miners in the deployment location\
1625 if miner.location.x == chunkborder.x and miner.location.y == chunkborder.y then\
1626 miner.distance = 0\
1627 else\
1628 miner.distance = TL2.calculateDistance(TL2.getState(), miner.location)\
1629 end\
1630 end\
1631 local k,miner = Util.first(pickupQueue,\
1632 function(a,b) return a.distance < b.distance end)\
1633 table.remove(pickupQueue, k)\
1634 collectMiner(miner)\
1635 queueWork(50, 'checkMiners', 'checkMiners')\
1636 return true\
1637 end\
1638 end\
1639\
1640 return true\
1641end)\
1642\
1643Message.addHandler('getMiningStatus', function(h, id, msg)\
1644 Message.send(id, 'miningStatus', { state = mining, status = miningStatus })\
1645end)\
1646\
1647function enableWirelessLogging()\
1648 Message.broadcast('logClient')\
1649 local _, id = Message.waitForMessage('logServer', 1)\
1650 if not id then\
1651 return false\
1652 end\
1653 Logger.setWirelessLogging(id)\
1654 return true\
1655end\
1656\
1657function resume()\
1658 if not fs.exists('mining.state') then\
1659 return false\
1660 end\
1661\
1662 local tmining = Util.readTable('mining.state')\
1663\
1664 if not tmining.namedLocations or \
1665 not tmining.x or\
1666 Util.size(tmining.namedLocations) == 0 then\
1667 return false\
1668 end\
1669\
1670 local state = TL2.getState()\
1671\
1672 state.x = tmining.x\
1673 state.y = tmining.y\
1674 state.heading = tmining.heading\
1675 TL2.getMemory().locations = tmining.namedLocations\
1676 mining = tmining\
1677 miningStatus.activity = 'Resuming mining'\
1678 -- release any miners that we did not release previously\
1679 if mining.status == 'mining' then\
1680 --releaseMiners()\
1681 UI.pager:setPage('status')\
1682 end\
1683 queueWork(1, 'gps', 'gps')\
1684 mining.status = 'resuming'\
1685\
1686 return true\
1687end\
1688\
1689function getWork()\
1690 local k,work = Util.first(workQueue, function(a,b) return a.priority < b.priority end)\
1691 if k then\
1692 workQueue[k] = nil\
1693 return work\
1694 end\
1695end\
1696\
1697Event.addHandler('workReady', function()\
1698 if TL2.getState().status ~= 'idle' then\
1699 Logger.log('miningBoss', 'busy')\
1700 Event.queueTimedEvent('endTimer', 2.5, 'workReady')\
1701 return\
1702 end\
1703\
1704 Logger.log('miningBoss', 'Queue size: ' .. Util.size(workQueue))\
1705 local work = getWork()\
1706 if work then\
1707 TLC.performAction(work.action, work.actionArgs)\
1708 if Util.size(workQueue) > 0 then\
1709 Event.queueTimedEvent('endTimer', 2.5, 'workReady')\
1710 end\
1711 end\
1712end)\
1713\
1714function isQueued(name)\
1715 return workQueue[name]\
1716end\
1717\
1718function queueWork(priority, name, action, actionArgs)\
1719 if not workQueue[name] then\
1720 workQueue[name] = {\
1721 priority = priority,\
1722 name = name,\
1723 action = action,\
1724 actionArgs = actionArgs\
1725 }\
1726 end\
1727 Event.queueTimedEvent('endTimer', 2.5, 'workReady')\
1728 Logger.log('miningBoss', 'Queuing: ' .. name)\
1729end\
1730\
1731TL2.setMode('destructive')\
1732TL2.setDigStrategy('wasteful')\
1733\
1734UI.pager:setPages({\
1735 [ 'menu' ] = menuPage,\
1736 [ 'status' ] = statusPage,\
1737 [ 'options' ] = optionsPage,\
1738 [ 'error' ] = errorPage\
1739})\
1740\
1741if not Util.getOptions(options, args) then\
1742 return\
1743end\
1744\
1745mining.depth = options.depth.value\
1746mining.chunks = options.chunks.value\
1747mining.plug = options.plug.value\
1748\
1749if turtle.getFuelLevel() < 1000 then\
1750 error('Add at least 1000 fuel before starting')\
1751end\
1752\
1753Logger.disable()\
1754UI.pager:setPage('menu')\
1755\
1756if not resume() then\
1757 -- set the reference plane to 2 above deployment location\
1758 TL2.getState().z = -2\
1759 TL2.saveNamedLocation('chunkborder', 0, 0, 0, 0)\
1760 mining.locations = getBoreLocations(0, 0)\
1761end\
1762\
1763Logger.filter('event', 'rednet_send', 'rednet_receive', 'debug')\
1764\
1765if options.logMethod.value == 'wireless' then\
1766 Message.enableWirelessLogging()\
1767elseif options.logMethod.value == 'file' then\
1768 Logger.setFileLogging('mine.log')\
1769elseif options.logMethod.value == 'screen' then\
1770 Logger.setScreenLogging()\
1771end\
1772\
1773TLC.pullEvents('miningBoss')\
1774\
1775fs.delete('mining.state')\
1776collectChunkLoaders()\
1777TL2.gotoZ(-2)\
1778--TL2.goto(0, 0, -2, 0)\
1779UI.term:reset()\
1780",},[4]={["name"]="apis.lua",["contents"]="--Import \
1781-- From http://lua-users.org/wiki/SimpleLuaClasses\
1782\
1783-- class.lua\
1784-- Compatible with Lua 5.1 (not 5.0).\
1785_G.class = { }\
1786function class.class(base, init)\
1787 local c = {} -- a new class instance\
1788 if not init and type(base) == 'function' then\
1789 init = base\
1790 base = nil\
1791 elseif type(base) == 'table' then\
1792 -- our new class is a shallow copy of the base class!\
1793 for i,v in pairs(base) do\
1794 c[i] = v\
1795 end\
1796 c._base = base\
1797 end\
1798 -- the class will be the metatable for all its objects,\
1799 -- and they will look up their methods in it.\
1800 c.__index = c\
1801\
1802 -- expose a constructor which can be called by <classname>(<args>)\
1803 local mt = {}\
1804 mt.__call =\
1805 function(class_tbl, ...)\
1806 local obj = {}\
1807 setmetatable(obj,c)\
1808 --if init then\
1809 -- init(obj,...)\
1810if class_tbl.init then\
1811 class_tbl.init(obj, ...)\
1812 else \
1813 -- make sure that any stuff from the base class is initialized!\
1814 if base and base.init then\
1815 base.init(obj, ...)\
1816 end\
1817 end\
1818 return obj\
1819 end\
1820\
1821 c.init = init\
1822 c.is_a =\
1823 function(self, klass)\
1824 local m = getmetatable(self)\
1825 while m do \
1826 if m == klass then return true end\
1827 m = m._base\
1828 end\
1829 return false\
1830 end\
1831 setmetatable(c, mt)\
1832 return c\
1833end\
1834\
1835\
1836--Import \
1837_G.Logger = { }\
1838\
1839local debugMon\
1840local logServerId\
1841local logFile\
1842local filteredEvents = {}\
1843\
1844local function nopLogger(text)\
1845end\
1846\
1847local function monitorLogger(text)\
1848 debugMon.write(text)\
1849 debugMon.scroll(-1)\
1850 debugMon.setCursorPos(1, 1)\
1851end\
1852\
1853local function screenLogger(text)\
1854 local x, y = term.getCursorPos()\
1855 if x ~= 1 then\
1856 local sx, sy = term.getSize()\
1857 term.setCursorPos(1, sy)\
1858 --term.scroll(1)\
1859 end\
1860 print(text)\
1861end\
1862\
1863local logger = screenLogger\
1864\
1865local function wirelessLogger(text)\
1866 if logServerId then\
1867 rednet.send(logServerId, {\
1868 type = 'log',\
1869 contents = text\
1870 })\
1871 end\
1872end\
1873\
1874local function fileLogger(text)\
1875 local mode = 'w'\
1876 if fs.exists(logFile) then\
1877 mode = 'a'\
1878 end\
1879 local file = io.open(logFile, mode)\
1880 if file then\
1881 file:write(text)\
1882 file:write('\\n')\
1883 file:close()\
1884 end\
1885end\
1886\
1887local function setLogger(ilogger)\
1888 logger = ilogger\
1889end\
1890\
1891function Logger.disable()\
1892 setLogger(nopLogger)\
1893end\
1894\
1895function Logger.setMonitorLogging(logServer)\
1896 debugMon = Util.wrap('monitor')\
1897 debugMon.setTextScale(.5)\
1898 debugMon.clear()\
1899 debugMon.setCursorPos(1, 1)\
1900 setLogger(monitorLogger)\
1901end\
1902\
1903function Logger.setScreenLogging()\
1904 setLogger(screenLogger)\
1905end\
1906\
1907function Logger.setWirelessLogging(id)\
1908 if id then\
1909 logServerId = id\
1910 end\
1911 setLogger(wirelessLogger)\
1912end\
1913\
1914function Logger.setFileLogging(fileName)\
1915 logFile = fileName\
1916 fs.delete(fileName)\
1917 setLogger(fileLogger)\
1918end\
1919\
1920function Logger.log(category, value, ...)\
1921 if filteredEvents[category] then\
1922 return\
1923 end\
1924\
1925 if type(value) == 'table' then\
1926 local str\
1927 for k,v in pairs(value) do\
1928 if not str then\
1929 str = '{ '\
1930 else\
1931 str = str .. ', '\
1932 end\
1933 str = str .. k .. '=' .. tostring(v)\
1934 end \
1935 value = str .. ' }'\
1936 elseif type(value) == 'string' then\
1937 local args = { ... }\
1938 if #args > 0 then\
1939 value = string.format(value, unpack(args))\
1940 end\
1941 else\
1942 value = tostring(value)\
1943 end\
1944 logger(category .. ': ' .. value)\
1945end\
1946\
1947function Logger.debug(value, ...)\
1948 Logger.log('debug', value, ...)\
1949end\
1950\
1951function Logger.logNestedTable(t, indent)\
1952 for _,v in ipairs(t) do\
1953 if type(v) == 'table' then\
1954 log('table')\
1955 logNestedTable(v) --, indent+1)\
1956 else\
1957 log(v)\
1958 end\
1959 end\
1960end\
1961\
1962function Logger.filter( ...)\
1963 local events = { ... }\
1964 for _,event in pairs(events) do\
1965 filteredEvents[event] = true\
1966 end\
1967end\
1968\
1969--Import \
1970_G.Util = { }\
1971-- _G.String = { }\
1972\
1973math.randomseed(os.time())\
1974\
1975function Util.tryTimed(timeout, f, ...)\
1976 local c = os.clock()\
1977 while not f(...) do\
1978 if os.clock()-c >= timeout then\
1979 return false\
1980 end\
1981 end\
1982 return true\
1983end\
1984\
1985function Util.tryTimes(attempts, f, ...)\
1986 local c = os.clock()\
1987 for i = 1, attempts do\
1988 local ret = f(...)\
1989 if ret then\
1990 return ret\
1991 end\
1992 end\
1993end\
1994\
1995function Util.print(value)\
1996 if type(value) == 'table' then\
1997 for k,v in pairs(value) do\
1998 print(k .. '=' .. tostring(v))\
1999 end\
2000 else\
2001 print(tostring(value))\
2002 end\
2003end\
2004\
2005function Util.clear(t)\
2006 local keys = Util.keys(t)\
2007 for _,k in pairs(keys) do\
2008 t[k] = nil\
2009 end\
2010end\
2011\
2012function Util.empty(t)\
2013 return Util.size(t) == 0\
2014end\
2015\
2016function Util.key(t, value)\
2017 for k,v in pairs(t) do\
2018 if v == value then\
2019 return k\
2020 end\
2021 end\
2022end\
2023\
2024function Util.keys(t)\
2025 local keys = {}\
2026 for k in pairs(t) do\
2027 keys[#keys+1] = k\
2028 end\
2029 return keys\
2030end\
2031\
2032function Util.find(t, name, value)\
2033 for k,v in pairs(t) do\
2034 if v[name] == value then\
2035 return v, k\
2036 end\
2037 end\
2038end\
2039\
2040function Util.findAll(t, name, value)\
2041 local rt = { }\
2042 for k,v in pairs(t) do\
2043 if v[name] == value then\
2044 table.insert(rt, v)\
2045 end\
2046 end\
2047 return rt\
2048end\
2049\
2050--http://lua-users.org/wiki/TableUtils\
2051function table.val_to_str ( v )\
2052 if \"string\" == type( v ) then\
2053 v = string.gsub( v, \"\\n\", \"\\\\n\" )\
2054 if string.match( string.gsub(v,\"[^'\\\"]\",\"\"), '^\"+$' ) then\
2055 return \"'\" .. v .. \"'\"\
2056 end\
2057 return '\"' .. string.gsub(v,'\"', '\\\\\"' ) .. '\"'\
2058 else\
2059 return \"table\" == type( v ) and table.tostring( v ) or\
2060 tostring( v )\
2061 end\
2062end\
2063\
2064function table.key_to_str ( k )\
2065 if \"string\" == type( k ) and string.match( k, \"^[_%a][_%a%d]*$\" ) then\
2066 return k\
2067 else\
2068 return \"[\" .. table.val_to_str( k ) .. \"]\"\
2069 end\
2070end\
2071\
2072function table.tostring( tbl )\
2073 local result, done = {}, {}\
2074 for k, v in ipairs( tbl ) do\
2075 table.insert( result, table.val_to_str( v ) )\
2076 done[ k ] = true\
2077 end\
2078 for k, v in pairs( tbl ) do\
2079 if not done[ k ] then\
2080 table.insert( result,\
2081 table.key_to_str( k ) .. \"=\" .. table.val_to_str( v ) )\
2082 end\
2083 end\
2084 return \"{\" .. table.concat( result, \",\" ) .. \"}\"\
2085end\
2086--end http://lua-users.org/wiki/TableUtils\
2087\
2088--https://github.com/jtarchie/underscore-lua\
2089function Util.size(list, ...)\
2090 local args = {...}\
2091\
2092 if Util.isArray(list) then\
2093 return #list\
2094 elseif Util.isObject(list) then\
2095 local length = 0\
2096 Util.each(list, function() length = length + 1 end)\
2097 return length\
2098 end\
2099\
2100 return 0\
2101end\
2102\
2103function Util.each(list, func)\
2104 local pairing = pairs\
2105 if Util.isArray(list) then pairing = ipairs end\
2106\
2107 for index, value in pairing(list) do\
2108 func(value, index, list)\
2109 end\
2110end\
2111\
2112function Util.isObject(value)\
2113 return type(value) == \"table\"\
2114end\
2115\
2116function Util.isArray(value)\
2117 return type(value) == \"table\" and (value[1] or next(value) == nil)\
2118end\
2119-- end https://github.com/jtarchie/underscore-lua\
2120\
2121function Util.random(max, min)\
2122 min = min or 0\
2123 return math.random(0, max-min) + min\
2124end\
2125\
2126function Util.readFile(fname)\
2127 local f = fs.open(fname, \"r\")\
2128 if f then\
2129 local t = f.readAll()\
2130 f.close()\
2131 return t\
2132 end\
2133end\
2134\
2135function Util.readTable(fname)\
2136 local t = Util.readFile(fname)\
2137 if t then\
2138 return textutils.unserialize(t)\
2139 end\
2140end\
2141\
2142function Util.writeTable(fname, data)\
2143 Util.writeFile(fname, textutils.serialize(data))\
2144end\
2145\
2146function Util.writeFile(fname, data)\
2147 local file = io.open(fname, \"w\")\
2148 if not file then\
2149 error('Unable to open ' .. fname, 2)\
2150 end\
2151 file:write(data)\
2152 file:close()\
2153end\
2154\
2155function Util.shallowCopy(t)\
2156 local t2 = {}\
2157 for k,v in pairs(t) do\
2158 t2[k] = v \
2159 end \
2160 return t2\
2161end\
2162\
2163function Util.split(str)\
2164 local t = {}\
2165 local function helper(line) table.insert(t, line) return \"\" end\
2166 helper((str:gsub(\"(.-)\\n\", helper))) \
2167 return t\
2168end\
2169\
2170-- http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74\
2171-- Author: David Manura\
2172--String.endswith = function(s, send)\
2173 --return #s >= #send and s:find(send, #s-#send+1, true) and true or false\
2174--end\
2175-- end http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74\
2176\
2177string.lpad = function(str, len, char)\
2178 if char == nil then char = ' ' end\
2179 return str .. string.rep(char, len - #str)\
2180end\
2181\
2182-- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua\
2183function Util.spairs(t, order)\
2184 if not t then\
2185 error('spairs: nil passed')\
2186 end\
2187\
2188 -- collect the keys\
2189 local keys = {}\
2190 for k in pairs(t) do keys[#keys+1] = k end\
2191\
2192 -- if order function given, sort by it by passing the table and keys a, b,\
2193 -- otherwise just sort the keys \
2194 if order then\
2195 table.sort(keys, function(a,b) return order(t[a], t[b]) end)\
2196 else\
2197 table.sort(keys)\
2198 end\
2199\
2200 -- return the iterator function\
2201 local i = 0\
2202 return function()\
2203 i = i + 1\
2204 if keys[i] then\
2205 return keys[i], t[keys[i]]\
2206 end\
2207 end\
2208end\
2209\
2210function Util.first(t, order)\
2211 -- collect the keys\
2212 local keys = {}\
2213 for k in pairs(t) do keys[#keys+1] = k end\
2214\
2215 -- if order function given, sort by it by passing the table and keys a, b,\
2216 -- otherwise just sort the keys \
2217 if order then\
2218 table.sort(keys, function(a,b) return order(t[a], t[b]) end)\
2219 else\
2220 table.sort(keys)\
2221 end\
2222 return keys[1], t[keys[1]]\
2223end\
2224\
2225--[[\
2226pbInfo - Libs/lib.WordWrap.lua\
2227 v0.41\
2228 by p.b. a.k.a. novayuna\
2229 released under the Creative Commons License By-Nc-Sa: http://creativecommons.org/licenses/by-nc-sa/3.0/\
2230 \
2231 original code by Tomi H.: http://shadow.vs-hs.org/library/index.php?page=2&id=48\
2232]]\
2233function Util.WordWrap(strText, intMaxLength)\
2234 local tblOutput = {};\
2235 local intIndex;\
2236 local strBuffer = \"\";\
2237 local tblLines = Util.Explode(strText, \"\\n\");\
2238 for k, strLine in pairs(tblLines) do\
2239 local tblWords = Util.Explode(strLine, \" \");\
2240 if (#tblWords > 0) then\
2241 intIndex = 1;\
2242 while tblWords[intIndex] do\
2243 local strWord = \" \" .. tblWords[intIndex];\
2244 if (strBuffer:len() >= intMaxLength) then\
2245 table.insert(tblOutput, strBuffer:sub(1, intMaxLength));\
2246 strBuffer = strBuffer:sub(intMaxLength + 1);\
2247 else\
2248 if (strWord:len() > intMaxLength) then\
2249 strBuffer = strBuffer .. strWord;\
2250 elseif (strBuffer:len() + strWord:len() >= intMaxLength) then\
2251 table.insert(tblOutput, strBuffer);\
2252 strBuffer = \"\"\
2253 else\
2254 if (strBuffer == \"\") then\
2255 strBuffer = strWord:sub(2);\
2256 else\
2257 strBuffer = strBuffer .. strWord;\
2258 end;\
2259 intIndex = intIndex + 1;\
2260 end;\
2261 end;\
2262 end;\
2263 if strBuffer ~= \"\" then\
2264 table.insert(tblOutput, strBuffer);\
2265 strBuffer = \"\"\
2266 end;\
2267 end;\
2268 end;\
2269 return tblOutput;\
2270end\
2271\
2272function Util.Explode(strText, strDelimiter)\
2273 local strTemp = \"\";\
2274 local tblOutput = {};\
2275 for intIndex = 1, strText:len(), 1 do\
2276 if (strText:sub(intIndex, intIndex + strDelimiter:len() - 1) == strDelimiter) then\
2277 table.insert(tblOutput, strTemp);\
2278 strTemp = \"\";\
2279 else\
2280 strTemp = strTemp .. strText:sub(intIndex, intIndex);\
2281 end;\
2282 end;\
2283 if (strTemp ~= \"\") then\
2284 table.insert(tblOutput, strTemp)\
2285 end;\
2286 return tblOutput;\
2287end\
2288\
2289-- http://lua-users.org/wiki/AlternativeGetOpt\
2290local function getopt( arg, options )\
2291 local tab = {}\
2292 for k, v in ipairs(arg) do\
2293 if string.sub( v, 1, 2) == \"--\" then\
2294 local x = string.find( v, \"=\", 1, true )\
2295 if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )\
2296 else tab[ string.sub( v, 3 ) ] = true\
2297 end\
2298 elseif string.sub( v, 1, 1 ) == \"-\" then\
2299 local y = 2\
2300 local l = string.len(v)\
2301 local jopt\
2302 while ( y <= l ) do\
2303 jopt = string.sub( v, y, y )\
2304 if string.find( options, jopt, 1, true ) then\
2305 if y < l then\
2306 tab[ jopt ] = string.sub( v, y+1 )\
2307 y = l\
2308 else\
2309 tab[ jopt ] = arg[ k + 1 ]\
2310 end\
2311 else\
2312 tab[ jopt ] = true\
2313 end\
2314 y = y + 1\
2315 end\
2316 end\
2317 end\
2318 return tab\
2319end\
2320-- end http://lua-users.org/wiki/AlternativeGetOpt\
2321\
2322function Util.showOptions(options)\
2323 print('Arguments: ')\
2324 for k, v in pairs(options) do\
2325 print(string.format('-%s %s', v.arg, v.desc))\
2326 end\
2327end\
2328\
2329function Util.getOptions(options, args, syntaxMessage)\
2330 local argLetters = ''\
2331 for _,o in pairs(options) do\
2332 if o.type ~= 'flag' then\
2333 argLetters = argLetters .. o.arg\
2334 end\
2335 end\
2336 local rawOptions = getopt(args, argLetters)\
2337\
2338 for k,ro in pairs(rawOptions) do\
2339 local found = false\
2340 for _,o in pairs(options) do\
2341 if o.arg == k then\
2342 found = true\
2343 if o.type == 'number' then\
2344 o.value = tonumber(ro)\
2345 elseif o.type == 'help' then\
2346 Util.showOptions(options)\
2347 return false\
2348 else\
2349 o.value = ro\
2350 end\
2351 end\
2352 end\
2353 if not found then\
2354 print('Invalid argument')\
2355 Util.showOptions(options)\
2356 return false\
2357 end\
2358 end\
2359\
2360 return true\
2361\
2362end\
2363\
2364--Import \
2365_G.Peripheral = { }\
2366\
2367function Peripheral.getAll()\
2368 local aliasDB = {\
2369 obsidian = 'chest',\
2370 diamond = 'chest',\
2371 container_chest = 'chest'\
2372 }\
2373\
2374 local t = { }\
2375 for _,side in pairs(peripheral.getNames()) do\
2376\
2377 local pType = peripheral.getType(side)\
2378\
2379 if pType == 'modem' then\
2380 if peripheral.call(side, 'isWireless') then\
2381 t[side] = {\
2382 type = 'wireless_modem',\
2383 side = side\
2384 }\
2385 else\
2386 t[side] = {\
2387 type = 'wired_modem',\
2388 side = side\
2389 }\
2390 end\
2391 else\
2392 t[side] = {\
2393 type = pType,\
2394 side = side,\
2395 alias = aliasDB[pType]\
2396 }\
2397 end\
2398 end\
2399 return t\
2400end\
2401\
2402function Peripheral.getBySide(pList, sideName)\
2403 return pList[sideName]\
2404end\
2405\
2406function Peripheral.getByType(pList, typeName)\
2407 return Util.find(pList, 'type', typeName)\
2408end\
2409\
2410function Peripheral.getByAlias(pList, aliasName)\
2411 return Util.find(pList, 'alias', aliasName)\
2412end\
2413\
2414function Peripheral.hasMethod(p, methodName)\
2415 local methods = peripheral.getMethods(p.side)\
2416 return Util.key(methods, methodName)\
2417end\
2418\
2419function Peripheral.getByMethod(pList, methodName)\
2420 for _,p in pairs(pList) do\
2421 if Peripheral.hasMethod(p, methodName) then\
2422 return p\
2423 end\
2424 end\
2425end\
2426\
2427-- peripheral must match all arguments\
2428function Peripheral.isAllPresent(args)\
2429 local t = Peripheral.getAll()\
2430 local p\
2431\
2432 if args.side then\
2433 p = Peripheral.getBySide(t, args.side)\
2434 if not p then\
2435 return\
2436 end\
2437 end\
2438\
2439 if args.type then\
2440 if p then\
2441 if p.type ~= args.type then\
2442 return\
2443 end\
2444 else\
2445 p = Peripheral.getByType(t, args.type)\
2446 if not p then\
2447 return\
2448 end\
2449 end\
2450 end\
2451\
2452 if args.method then\
2453 if p then\
2454 if not Peripheral.hasMethod(p, args.method) then\
2455 return\
2456 end\
2457 else\
2458 p = Peripheral.getByMethod(t, args.method)\
2459 if p then\
2460 return\
2461 end\
2462 end\
2463 end\
2464\
2465 return p\
2466end\
2467\
2468function Peripheral.isPresent(args)\
2469\
2470 if type(args) == 'string' then\
2471 args = { type = args }\
2472 end\
2473\
2474 local t = Peripheral.getAll()\
2475 args = args or { type = pType }\
2476\
2477 if args.type then\
2478 local p = Peripheral.getByType(t, args.type)\
2479 if p then\
2480 return p\
2481 end\
2482 end\
2483\
2484 if args.alias then\
2485 local p = Peripheral.getByAlias(t, args.alias)\
2486 if p then\
2487 return p\
2488 end\
2489 end\
2490\
2491 if args.method then\
2492 local p = Peripheral.getByMethod(t, args.method)\
2493 if p then\
2494 return p\
2495 end\
2496 end\
2497\
2498 if args.side then\
2499 local p = Peripheral.getBySide(t, args.side)\
2500 if p then\
2501 return p\
2502 end\
2503 end\
2504end\
2505\
2506local function _wrap(p)\
2507 Logger.log('peripheral', 'Wrapping ' .. p.type)\
2508 if p.type == 'wired_modem' or p.type == 'wireless_modem' then\
2509 rednet.open(p.side)\
2510 return p\
2511 end\
2512 return peripheral.wrap(p.side)\
2513end\
2514\
2515function Peripheral.wrap(args)\
2516\
2517 if type(args) == 'string' then\
2518 args = { type = args }\
2519 end\
2520\
2521 local p = Peripheral.isPresent(args)\
2522 if p then\
2523 return _wrap(p)\
2524 end\
2525\
2526 error('Peripheral ' .. table.tostring(args, '?') .. ' is not connected', 2)\
2527end\
2528\
2529function Peripheral.wrapAll()\
2530 local t = Peripheral.getAll()\
2531 Util.each(t, function(p) p.wrapper = _wrap(p) end)\
2532 return t\
2533end\
2534\
2535function Peripheral.wrapSide(sideName)\
2536 if peripheral.isPresent(sideName) then\
2537 return peripheral.wrap(sideName)\
2538 end\
2539 error('No Peripheral on side ' .. sideName, 2)\
2540end\
2541\
2542--Import \
2543_G.Event = { }\
2544\
2545local eventHandlers = {\
2546 namedTimers = {}\
2547}\
2548local enableQueue = {}\
2549local removeQueue = {}\
2550\
2551local function deleteHandler(h)\
2552 for k,v in pairs(eventHandlers[h.event].handlers) do\
2553 if v == h then\
2554 table.remove(eventHandlers[h.event].handlers, k)\
2555 break\
2556 end\
2557 end\
2558 --table.remove(eventHandlers[h.event].handlers, h.key)\
2559end\
2560\
2561function Event.addHandler(type, f)\
2562 local event = eventHandlers[type]\
2563 if not event then\
2564 event = {}\
2565 event.handlers = {}\
2566 eventHandlers[type] = event\
2567 end\
2568\
2569 local handler = {}\
2570 handler.event = type\
2571 handler.f = f\
2572 handler.enabled = true\
2573 table.insert(event.handlers, handler)\
2574 -- any way to retrieve key here for removeHandler ?\
2575 \
2576 return handler\
2577end\
2578\
2579function Event.removeHandler(h)\
2580 h.deleted = true\
2581 h.enabled = false\
2582 table.insert(removeQueue, h)\
2583end\
2584\
2585function Event.queueTimedEvent(name, timeout, event, args)\
2586 Event.addNamedTimer(name, timeout, false,\
2587 function()\
2588 os.queueEvent(event, args)\
2589 end\
2590 )\
2591end\
2592\
2593function Event.addNamedTimer(name, interval, recurring, f)\
2594 Event.cancelNamedTimer(name)\
2595 eventHandlers.namedTimers[name] = Event.addTimer(interval, recurring, f)\
2596end\
2597\
2598function Event.getNamedTimer(name)\
2599 return eventHandlers.namedTimers[name]\
2600end\
2601\
2602function Event.cancelNamedTimer(name)\
2603 local timer = Event.getNamedTimer(name)\
2604\
2605 if timer then\
2606 timer.enabled = false\
2607 timer.recurring = false\
2608 end\
2609end\
2610\
2611function Event.isTimerActive(timer)\
2612 return timer.enabled and\
2613 os.clock() < timer.start + timer.interval\
2614end\
2615\
2616function Event.addTimer(interval, recurring, f)\
2617 local timer = Event.addHandler('timer',\
2618 function(t, id)\
2619 if t.timerId ~= id then\
2620 return\
2621 end\
2622 if t.enabled then\
2623 t.fired = true\
2624 t.cf(t, id)\
2625 end\
2626 if t.recurring then\
2627 t.fired = false\
2628 t.start = os.clock()\
2629 t.timerId = os.startTimer(t.interval)\
2630 else\
2631 Event.removeHandler(t)\
2632 end\
2633 end\
2634 )\
2635 timer.cf = f\
2636 timer.interval = interval\
2637 timer.recurring = recurring\
2638 timer.start = os.clock()\
2639 timer.timerId = os.startTimer(interval)\
2640\
2641 return timer\
2642end\
2643\
2644function Event.removeTimer(h)\
2645 Event.removeHandler(h)\
2646end\
2647\
2648function Event.blockUntilEvent(event, timeout)\
2649 return Event.waitForEvent(event, timeout, os.pullEvent)\
2650end\
2651\
2652function Event.waitForEvent(event, timeout, pullEvent)\
2653 pullEvent = pullEvent or Event.pullEvent\
2654\
2655 local timerId = os.startTimer(timeout)\
2656 repeat\
2657 local e, p1, p2, p3, p4 = pullEvent()\
2658 if e == event then\
2659 return e, p1, p2, p3, p4\
2660 end \
2661 until e == 'timer' and id == timerId\
2662end\
2663\
2664function Event.heartbeat(timeout)\
2665 local function heart()\
2666 while true do\
2667 os.sleep(timeout)\
2668 os.queueEvent('heartbeat')\
2669 end \
2670 end\
2671 parallel.waitForAny(Event.pullEvents, heart)\
2672end\
2673\
2674function Event.pullEvents()\
2675 while true do\
2676 local e = Event.pullEvent()\
2677 if e == 'exitPullEvents' then\
2678 break\
2679 end\
2680 end\
2681end\
2682\
2683function Event.exitPullEvents()\
2684 os.queueEvent('exitPullEvents')\
2685end\
2686\
2687function Event.enableHandler(h)\
2688 table.insert(enableQueue, h)\
2689end\
2690\
2691function Event.pullEvent(eventType)\
2692 local e, p1, p2, p3, p4, p5 = os.pullEvent(eventType)\
2693\
2694 Logger.log('event', { e, p1, p2, p3, p4, p5 })\
2695\
2696 local event = eventHandlers[e]\
2697 if event then\
2698 for k,v in pairs(event.handlers) do\
2699 if v.enabled then\
2700 v.f(v, p1, p2, p3, p4, p5)\
2701 end\
2702 end\
2703 while #enableQueue > 0 do\
2704 table.remove(handlerQueue).enabled = true\
2705 end\
2706 while #removeQueue > 0 do\
2707 deleteHandler(table.remove(removeQueue))\
2708 end\
2709 end\
2710 \
2711 return e, p1, p2, p3, p4, p5\
2712end\
2713\
2714--Import \
2715_G.Message = { }\
2716\
2717local messageHandlers = {}\
2718\
2719function Message.addHandler(type, f)\
2720 table.insert(messageHandlers, {\
2721 type = type,\
2722 f = f,\
2723 enabled = true\
2724 })\
2725end\
2726\
2727function Message.removeHandler(h)\
2728 for k,v in pairs(messageHandlers) do\
2729 if v == h then\
2730 messageHandlers[k] = nil\
2731 break\
2732 end\
2733 end\
2734end\
2735\
2736Event.addHandler('rednet_message',\
2737 function(event, id, msg, distance)\
2738 if msg and msg.type then -- filter out messages from other systems\
2739 Logger.log('rednet_receive', id, msg.type)\
2740 for k,h in pairs(messageHandlers) do\
2741 if h.type == msg.type then\
2742-- should provide msg.contents instead of message - type is already known\
2743 h.f(h, id, msg, distance)\
2744 end\
2745 end\
2746 end\
2747 end\
2748)\
2749\
2750function Message.send(id, msgType, contents)\
2751 if id then\
2752 Logger.log('rednet_send', { tostring(id), msgType })\
2753 rednet.send(id, { type = msgType, contents = contents })\
2754 else\
2755 Logger.log('rednet_send', { 'broadcast', msgType })\
2756 rednet.broadcast({ type = msgType, contents = contents })\
2757 end\
2758end\
2759\
2760function Message.broadcast(t, contents)\
2761 Logger.log('rednet_send', { 'broadcast', t })\
2762 rednet.broadcast({ type = t, contents = contents })\
2763end\
2764\
2765function Message.waitForMessage(msgType, timeout, fromId)\
2766 local timerId = os.startTimer(timeout)\
2767 repeat\
2768 local e, id, msg, distance = Event.pullEvent()\
2769 if e == 'rednet_message' then\
2770 if msg and msg.type and msg.type == msgType then\
2771 if not fromId or id == fromId then\
2772 return e, id, msg, distance\
2773 end\
2774 end \
2775 end\
2776 until e == 'timer' and id == timerId\
2777end\
2778\
2779function Message.enableWirelessLogging()\
2780 local modem = Peripheral.isPresent('wireless_modem')\
2781 if modem then\
2782 if not rednet.isOpen(modem.side) then\
2783 Logger.log('message', 'enableWirelessLogging: opening modem')\
2784 rednet.open(modem.side)\
2785 end\
2786 Message.broadcast('logClient')\
2787 local _, id = Message.waitForMessage('logServer', 1)\
2788 if not id then\
2789 return false\
2790 end\
2791 Logger.log('message', 'enableWirelessLogging: Logging to ' .. id)\
2792 Logger.setWirelessLogging(id)\
2793 return true\
2794 end\
2795end\
2796\
2797--Import \
2798_G.Relay = { }\
2799\
2800Relay.stationId = nil\
2801\
2802function Relay.find(msgType, stationType)\
2803 while true do\
2804 Logger.log('relay', 'locating relay station')\
2805 Message.broadcast('getRelayStation', os.getComputerLabel())\
2806 local _, id = Message.waitForMessage('relayStation', 2)\
2807 if id then\
2808 Relay.stationId = id\
2809 return id\
2810 end\
2811 end\
2812end\
2813\
2814function Relay.register(...)\
2815 local types = { ... }\
2816 Message.send(Relay.stationId, 'listen', types)\
2817end\
2818\
2819function Relay.send(type, contents, toId)\
2820 local relayMessage = {\
2821 type = type,\
2822 contents = contents,\
2823 fromId = os.getComputerID()\
2824 }\
2825 if toId then\
2826 relayMessage.toId = toId\
2827 end\
2828 Message.send(Relay.stationId, 'relay', relayMessage)\
2829end\
2830\
2831--Import \
2832_G.UI = { }\
2833\
2834local function widthify(s, len)\
2835 if not s then\
2836 s = ' '\
2837 end\
2838 return string.lpad(string.sub(s, 1, len) , len, ' ')\
2839end\
2840\
2841function UI.setProperties(obj, args)\
2842 if args then\
2843 for k,v in pairs(args) do\
2844 obj[k] = v\
2845 end\
2846 end\
2847end\
2848\
2849function UI.setDefaultDevice(device)\
2850 UI.defaultDevice = device\
2851end\
2852\
2853function UI.bestDefaultDevice(...)\
2854 local termList = { ... }\
2855 for _,name in ipairs(termList) do\
2856 if name == 'monitor' then\
2857 if Util.hasDevice('monitor') then\
2858 UI.defaultDevice = UI.Device({ deviceType = 'monitor' })\
2859 return UI.defaultDevice\
2860 end\
2861 end\
2862 end\
2863 return UI.term\
2864end\
2865\
2866--[[-- Terminal for computer / advanced computer / monitor --]]--\
2867UI.Device = class.class()\
2868\
2869function UI.Device:init(args)\
2870\
2871 self.backgroundColor = colors.black\
2872 self.textColor = colors.white\
2873 self.textScale = 1\
2874\
2875 UI.setProperties(self, args)\
2876\
2877 if self.deviceType then\
2878 self.device = Peripheral.wrap({ type = self.deviceType })\
2879 end\
2880\
2881 if self.device.setTextScale then\
2882 self.device.setTextScale(self.textScale)\
2883 end\
2884\
2885 self.isColor = self.device.isColor()\
2886 if not self.isColor then\
2887 self.device.setBackgroundColor = function(...) end\
2888 self.device.setTextColor = function(...) end\
2889 end\
2890\
2891 self.width, self.height = self.device.getSize()\
2892end\
2893\
2894function UI.Device:displayCursor(x, y)\
2895 self.device.setCursorPos(x, y)\
2896end\
2897\
2898function UI.Device:write(x, y, text, bg, tc)\
2899 bg = bg or self.backgroundColor\
2900 tc = tc or self.textColor\
2901\
2902 self.device.setCursorPos(x, y)\
2903 self.device.setTextColor(tc)\
2904 self.device.setBackgroundColor(bg)\
2905 self.device.write(text)\
2906end\
2907\
2908function UI.Device:clear(bg)\
2909 bg = bg or self.backgroundColor\
2910 self.device.setBackgroundColor(bg)\
2911 self.device.clear()\
2912end\
2913\
2914function UI.Device:reset(bg)\
2915 self:clear(bg)\
2916 self.device.setCursorPos(1, 1)\
2917end\
2918\
2919UI.term = UI.Device({ device = term })\
2920UI.defaultDevice = UI.term\
2921\
2922--[[-- Glasses device --]]--\
2923UI.Glasses = class.class()\
2924function UI.Glasses:init(args)\
2925\
2926 local defaults = {\
2927 backgroundColor = colors.black,\
2928 textColor = colors.white,\
2929 textScale = .5,\
2930 backgroundOpacity = .5\
2931 }\
2932 defaults.width, defaults.height = term.getSize()\
2933\
2934 UI.setProperties(defaults, args)\
2935 UI.setProperties(self, defaults)\
2936\
2937 self.bridge = Peripheral.wrap({\
2938 type = 'openperipheral_glassesbridge',\
2939 method = 'addBox'\
2940 })\
2941 self.bridge.clear()\
2942\
2943 self.setBackgroundColor = function(...) end\
2944 self.setTextColor = function(...) end\
2945\
2946 self.t = { }\
2947 for i = 1, self.height do\
2948 self.t[i] = {\
2949 text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),\
2950 bg = { }\
2951 }\
2952 self.t[i].text.setScale(self.textScale)\
2953 self.t[i].text.setZ(1)\
2954 end\
2955end\
2956\
2957function UI.Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)\
2958 local colors = {\
2959 [ colors.black ] = 0x000000,\
2960 [ colors.brown ] = 0x7F664C,\
2961 [ colors.blue ] = 0x253192,\
2962 [ colors.gray ] = 0x272727,\
2963 [ colors.lime ] = 0x426A0D,\
2964 [ colors.green ] = 0x2D5628,\
2965 [ colors.white ] = 0xFFFFFF\
2966 }\
2967\
2968 local function overlap(box, ax, bx)\
2969 if bx < box.ax or ax > box.bx then\
2970 return false\
2971 end\
2972 return true\
2973 end\
2974\
2975 for _,box in pairs(boxes) do\
2976 if overlap(box, ax, bx) then \
2977 if box.bgColor == bgColor then\
2978 ax = math.min(ax, box.ax)\
2979 bx = math.max(bx, box.bx)\
2980 box.ax = box.bx + 1\
2981 elseif ax == box.ax then\
2982 box.ax = bx + 1\
2983 elseif ax > box.ax then\
2984 if bx < box.bx then\
2985 table.insert(boxes, { -- split\
2986 ax = bx + 1,\
2987 bx = box.bx,\
2988 bgColor = box.bgColor\
2989 })\
2990 box.bx = ax - 1\
2991 break\
2992 else\
2993 box.ax = box.bx + 1\
2994 end\
2995 elseif ax < box.ax then\
2996 if bx > box.bx then\
2997 box.ax = box.bx + 1 -- delete\
2998 else\
2999 box.ax = bx + 1\
3000 end\
3001 end\
3002 end\
3003 end\
3004 if bgColor ~= colors.black then\
3005 table.insert(boxes, {\
3006 ax = ax,\
3007 bx = bx,\
3008 bgColor = bgColor\
3009 })\
3010 end\
3011\
3012 local deleted\
3013 repeat\
3014 deleted = false\
3015 for k,box in pairs(boxes) do\
3016 if box.ax > box.bx then\
3017 if box.box then\
3018 box.box.delete()\
3019 end\
3020 table.remove(boxes, k)\
3021 deleted = true\
3022 break\
3023 end\
3024 if not box.box then\
3025 box.box = self.bridge.addBox(\
3026 math.floor((box.ax - 1) * 2.6665),\
3027 40 + y * 4,\
3028 math.ceil((box.bx - box.ax + 1) * 2.6665),\
3029 4,\
3030 colors[bgColor],\
3031 self.backgroundOpacity)\
3032 else\
3033 box.box.setX(math.floor((box.ax - 1) * 2.6665))\
3034 box.box.setWidth(math.ceil((box.bx - box.ax + 1) * 2.6665))\
3035 end\
3036 end\
3037 until not deleted\
3038end\
3039\
3040function UI.Glasses:write(x, y, text, bg)\
3041\
3042 if x < 1 then\
3043 error(' less ', 6)\
3044 end\
3045 if y <= #self.t then\
3046 local line = self.t[y]\
3047 local str = line.text.getText()\
3048 str = str:sub(1, x-1) .. text .. str:sub(x + 1 + #text)\
3049 line.text.setText(str)\
3050 self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)\
3051UI.term:write(x, y, text, bg)\
3052 end\
3053end\
3054\
3055function UI.Glasses:clear(bg)\
3056 for i = 1, self.height do\
3057 self.t[i].text.setText('')\
3058 end\
3059end\
3060\
3061--[[-- Basic drawable area --]]--\
3062UI.Window = class.class()\
3063function UI.Window:init(args)\
3064 local defaults = {\
3065 UIElement = 'Window',\
3066 x = 1,\
3067 y = 1,\
3068 cursorX = 1,\
3069 cursorY = 1,\
3070 isUIElement = true\
3071 }\
3072\
3073 UI.setProperties(self, defaults)\
3074 UI.setProperties(self, args)\
3075\
3076 if self.parent then\
3077 self:setParent()\
3078 end\
3079end\
3080\
3081function UI.Window:setParent()\
3082 if not self.width then\
3083 self.width = self.parent.width - self.x + 1\
3084 end\
3085 if not self.height then\
3086 self.height = self.parent.height - self.y + 1\
3087 end\
3088\
3089 local children = self.children\
3090 for k,child in pairs(self) do\
3091 if type(child) == 'table' and child.isUIElement and not child.parent then\
3092 if not children then\
3093 children = { }\
3094 end\
3095 --self.children[k] = child\
3096 table.insert(children, child)\
3097 end\
3098 end\
3099 if children then\
3100 for _,child in pairs(children) do\
3101 if not child.parent then\
3102 child.parent = self\
3103 child:setParent()\
3104 end\
3105 end\
3106 end\
3107 self.children = children\
3108end\
3109\
3110function UI.Window:add(children)\
3111 UI.setProperties(self, children)\
3112 for k,child in pairs(children) do\
3113 if type(child) == 'table' and child.isUIElement and not child.parent then\
3114 if not self.children then\
3115 self.children = { }\
3116 end\
3117 self.children[k] = child\
3118 --table.insert(self.children, child)\
3119 child.parent = self\
3120 child:setParent()\
3121 end\
3122 end\
3123end\
3124\
3125function UI.Window:getCursorPos()\
3126 return self.cursorX, self.cursorY\
3127end\
3128\
3129function UI.Window:setCursorPos(x, y)\
3130 self.cursorX = x\
3131 self.cursorY = y\
3132end\
3133\
3134function UI.Window:setBackgroundColor(bgColor)\
3135 self.backgroundColor = bgColor\
3136end\
3137\
3138function UI.Window:draw()\
3139 self:clear(self.backgroundColor)\
3140 if self.children then\
3141 for k,child in pairs(self.children) do\
3142 child:draw()\
3143 end\
3144 end\
3145end\
3146\
3147function UI.Window:reset(bg)\
3148 self:clear(bg)\
3149 self:setCursorPos(1, 1)\
3150end\
3151\
3152function UI.Window:clear(bg)\
3153 self:clearArea(1, 1, self.width, self.height, bg)\
3154end\
3155\
3156function UI.Window:clearLine(y, bg)\
3157 local filler = string.rep(' ', self.width)\
3158 self:write(1, y, filler, bg)\
3159end\
3160\
3161function UI.Window:clearArea(x, y, width, height, bg)\
3162 local filler = string.rep(' ', width)\
3163 for i = 0, height-1 do\
3164 self:write(x, y+i, filler, bg)\
3165 end\
3166end\
3167\
3168function UI.Window:write(x, y, text, bg, tc)\
3169 bg = bg or self.backgroundColor\
3170 tc = tc or self.textColor\
3171 if y < self.height+1 then\
3172 self.parent:write(\
3173 self.x + x - 1, self.y + y - 1, tostring(text), bg, tc)\
3174 end\
3175end\
3176\
3177function UI.Window:displayCursor(x, y)\
3178 self.parent:displayCursor(self.x + x - 1, self.y + y - 1)\
3179end\
3180\
3181function UI.Window:centeredWrite(y, text, bg)\
3182 if #text >= self.width then\
3183 self:write(1, y, text, bg)\
3184 else\
3185 local space = math.floor((self.width-#text) / 2)\
3186 local filler = string.rep(' ', space + 1)\
3187 local str = filler:sub(1, space) .. text\
3188 str = str .. filler:sub(self.width - #str + 1)\
3189 self:write(1, y, str, bg)\
3190 end\
3191end\
3192\
3193function UI.Window:wrappedWrite(x, y, text, len, bg)\
3194 for k,v in pairs(Util.WordWrap(text, len)) do\
3195 self:write(x, y, v, bg)\
3196 y = y + 1\
3197 end\
3198 return y\
3199end\
3200\
3201function UI.Window:print(text, indent, len, bg)\
3202 indent = indent or 0\
3203 len = len or self.width - indent\
3204 if #text + self.cursorX > self.width then\
3205 for k,v in pairs(Util.WordWrap(text, len+1)) do\
3206 self:write(self.cursorX, self.cursorY, v, bg)\
3207 self.cursorY = self.cursorY + 1\
3208 self.cursorX = 1 + indent\
3209 end\
3210 else\
3211 self:write(self.cursorX, self.cursorY, text, bg)\
3212 self.cursorY = self.cursorY + 1\
3213 self.cursorX = 1\
3214 end\
3215end\
3216\
3217function UI.Window.setTextScale(scale)\
3218 self.scale = scale\
3219 self.parent.setTextScale(scale)\
3220end\
3221\
3222function UI.Window:emit(event)\
3223 local parent = self\
3224Logger.log('ui', self.UIElement .. ' emitting ' .. event.type)\
3225 while parent do\
3226 if parent.eventHandler then\
3227 if parent:eventHandler(event) then\
3228 return true\
3229 end\
3230 end\
3231 parent = parent.parent\
3232 end\
3233end\
3234\
3235function UI.Window:eventHandler(event)\
3236 return false\
3237end\
3238\
3239function UI.Window:setFocus(focus)\
3240 self.parent:setFocus(focus)\
3241end\
3242\
3243function UI.Window:getPreviousFocus(focused)\
3244 if self.children then\
3245 local k = Util.key(self.children, focused)\
3246 for k = k-1, 1, -1 do\
3247 if self.children[k].focus then\
3248 return self.children[k]\
3249 end\
3250 end\
3251 end\
3252end\
3253\
3254function UI.Window:getNextFocus(focused)\
3255 if self.children then\
3256 local k = Util.key(self.children, focused)\
3257 for k = k+1, #self.children do\
3258 if self.children[k].focus then\
3259 return self.children[k]\
3260 end\
3261 end\
3262 end\
3263end\
3264\
3265-- default drawing console on term\
3266UI.console = UI.Window({ parent = UI.term })\
3267\
3268--[[-- StringBuffer --]]--\
3269UI.StringBuffer = class.class()\
3270function UI.StringBuffer:init(bufSize)\
3271 self.bufSize = bufSize\
3272 self.buffer = {}\
3273end\
3274\
3275function UI.StringBuffer:insert(s, index)\
3276 table.insert(self.buffer, { index = index, str = s })\
3277end\
3278\
3279function UI.StringBuffer:append(s)\
3280 local str = self:get()\
3281 self:insert(s, #str)\
3282end\
3283\
3284-- pi -> getBeeParents -> Demonic -> special conditions\
3285function UI.StringBuffer:get()\
3286 local str = ''\
3287 for k,v in Util.spairs(self.buffer, function(a, b) return a.index < b.index end) do\
3288 str = str .. string.rep(' ', math.max(v.index - string.len(str), 0)) .. v.str\
3289 end\
3290 local len = string.len(str)\
3291 if len < self.bufSize then\
3292 str = str .. string.rep(' ', self.bufSize - len)\
3293 end\
3294 return str\
3295end\
3296\
3297function UI.StringBuffer:clear()\
3298 self.buffer = {}\
3299end\
3300\
3301--[[-- Pager --]]--\
3302UI.Pager = class.class()\
3303function UI.Pager:init(args)\
3304 local defaults = {\
3305 pages = { }\
3306 }\
3307 UI.setProperties(defaults, args)\
3308 UI.setProperties(self, defaults)\
3309\
3310 Event.addHandler('mouse_scroll', function(h, direction, x, y)\
3311 local event = self:pointToChild(self.currentPage, x, y)\
3312 local directions = {\
3313 [ -1 ] = 'up',\
3314 [ 1 ] = 'down'\
3315 }\
3316 event.type = 'key'\
3317 event.key = directions[direction]\
3318 event.UIElement:emit(event)\
3319 end)\
3320\
3321 Event.addHandler('monitor_touch', function(h, button, x, y)\
3322 self:click(1, x, y)\
3323 end)\
3324\
3325 Event.addHandler('mouse_click', function(h, button, x, y)\
3326 self:click(button, x, y)\
3327 end)\
3328\
3329 Event.addHandler('char', function(h, ch)\
3330 self:emitEvent({ type = 'key', key = ch })\
3331 end)\
3332\
3333 Event.addHandler('key', function(h, code)\
3334 local ch = keys.getName(code)\
3335 -- filter out a through z as they will be get picked up\
3336 -- as char events\
3337 if ch and #ch > 1 then\
3338 if self.currentPage then\
3339 self:emitEvent({ type = 'key', key = ch })\
3340 if ch == 'f10' then\
3341 UI.displayTable(_G, 'Global Memory')\
3342 elseif ch == 'f9' then\
3343 UI.displayTable(getfenv(4), 'Local memory')\
3344 end\
3345 end\
3346 end\
3347 end)\
3348end\
3349\
3350function UI.Pager:emitEvent(event)\
3351 if self.currentPage and self.currentPage.focused then\
3352 return self.currentPage.focused:emit(event)\
3353 end\
3354end\
3355\
3356function UI.Pager:pointToChild(parent, x, y)\
3357 if parent.children then\
3358 for _,child in pairs(parent.children) do\
3359 if x >= child.x and x < child.x + child.width and\
3360 y >= child.y and y < child.y + child.height then\
3361 local c = self:pointToChild(child, x - child.x + 1, y - child.y + 1)\
3362 if c then\
3363 return c\
3364 end\
3365 end\
3366 end\
3367 end\
3368 return {\
3369 UIElement = parent,\
3370 x = x,\
3371 y = y\
3372 }\
3373end\
3374\
3375function UI.Pager:click(button, x, y)\
3376 if self.currentPage then\
3377\
3378 local function getClicked(parent, button, x, y)\
3379 if parent.children then\
3380 for _,child in pairs(parent.children) do\
3381 if x >= child.x and x < child.x + child.width and\
3382 y >= child.y and y < child.y + child.height and\
3383 not child.isShadow then\
3384 local c = getClicked(child, button, x - child.x + 1, y - child.y + 1)\
3385 if c then\
3386 return c\
3387 end\
3388 end\
3389 end\
3390 end\
3391 local events = { 'mouse_click', 'mouse_rightclick' }\
3392 return {\
3393 UIElement = parent,\
3394 type = events[button],\
3395 button = button,\
3396 x = x,\
3397 y = y\
3398 }\
3399 end\
3400\
3401 local clickEvent = getClicked(self.currentPage, button,\
3402 x - self.currentPage.x + 1,\
3403 y - self.currentPage.y + 1)\
3404\
3405 if clickEvent.UIElement.focus then\
3406 self.currentPage:setFocus(clickEvent.UIElement)\
3407 end\
3408 clickEvent.UIElement:emit(clickEvent)\
3409 end\
3410end\
3411\
3412function UI.Pager:addPage(name, page)\
3413 self.pages[name] = page\
3414end\
3415\
3416function UI.Pager:setPages(pages)\
3417 self.pages = pages\
3418end\
3419\
3420function UI.Pager:getPage(pageName)\
3421 local page = self.pages[pageName]\
3422\
3423 if not page then\
3424 error('Pager:getPage: Invalid page: ' .. tostring(pageName), 2)\
3425 end\
3426\
3427 return page\
3428end\
3429\
3430function UI.Pager:setPage(pageOrName)\
3431 local page = pageOrName\
3432\
3433 if type(pageOrName) == 'string' then\
3434 page = self.pages[pageOrName]\
3435 end\
3436\
3437 if page == self.currentPage then\
3438 page:draw()\
3439 else\
3440 if self.currentPage then\
3441 if self.currentPage.focused then\
3442 self.currentPage.focused.focused = false\
3443 self.currentPage.focused:focus()\
3444 end\
3445 self.currentPage:disable()\
3446 self.currentPage.enabled = false\
3447 page.previousPage = self.currentPage\
3448 end\
3449 self.currentPage = page\
3450 self.currentPage:reset(page.backgroundColor)\
3451 page.enabled = true\
3452 page:enable()\
3453 page:draw()\
3454 if self.currentPage.focused then\
3455 self.currentPage.focused.focused = true\
3456 self.currentPage.focused:focus()\
3457 end\
3458 end\
3459end\
3460\
3461function UI.Pager:getCurrentPage()\
3462 return self.currentPage\
3463end\
3464\
3465function UI.Pager:setPreviousPage()\
3466 if self.currentPage.previousPage then\
3467 local previousPage = self.currentPage.previousPage.previousPage\
3468 self:setPage(self.currentPage.previousPage)\
3469 self.currentPage.previousPage = previousPage\
3470 end\
3471end\
3472\
3473UI.pager = UI.Pager()\
3474\
3475--[[-- Page --]]--\
3476UI.Page = class.class(UI.Window)\
3477function UI.Page:init(args)\
3478 local defaults = {\
3479 UIElement = 'Page',\
3480 parent = UI.defaultDevice,\
3481 accelerators = { }\
3482 }\
3483 --if not args or not args.parent then\
3484 --self.parent = UI.defaultDevice\
3485 --end\
3486 \
3487 UI.setProperties(defaults, args)\
3488 UI.Window.init(self, defaults)\
3489\
3490 self.focused = self:findFirstFocus(self)\
3491 --if self.focused then\
3492 --self.focused.focused = true\
3493 --end\
3494end\
3495\
3496function UI.Page:enable()\
3497end\
3498\
3499function UI.Page:disable()\
3500end\
3501\
3502function UI.Page:draw()\
3503 UI.Window.draw(self)\
3504 --if self.focused then\
3505 --self:setFocus(self.focused)\
3506 --end\
3507end\
3508\
3509function UI.Page:findFirstFocus(parent)\
3510 if not parent.children then\
3511 return\
3512 end\
3513 for _,child in ipairs(parent.children) do\
3514 if child.children then\
3515 local c = self:findFirstFocus(child)\
3516 if c then\
3517 return c\
3518 end\
3519 end\
3520 if child.focus then\
3521 return child\
3522 end\
3523 end\
3524end\
3525\
3526function UI.Page:getFocused()\
3527 return self.focused\
3528end\
3529\
3530function UI.Page:focusFirst()\
3531 local focused = self:findFirstFocus(self)\
3532 if focused then\
3533 self:setFocus(focused)\
3534 end\
3535end\
3536\
3537function UI.Page:focusPrevious()\
3538\
3539 local parent = self.focused.parent\
3540 local child = self.focused\
3541 local focused\
3542\
3543 while parent do\
3544 focused = parent:getPreviousFocus(child)\
3545 if focused then\
3546 break\
3547 end\
3548 child = parent\
3549 parent = parent.parent\
3550 if not parent.getPreviousFocused then\
3551 break\
3552 end\
3553 end\
3554\
3555 if focused then\
3556 self:setFocus(focused)\
3557 end\
3558end\
3559\
3560function UI.Page:focusNext()\
3561\
3562 local parent = self.focused.parent\
3563 local child = self.focused\
3564 local focused\
3565\
3566 while parent do\
3567 focused = parent:getNextFocus(child)\
3568 if focused then\
3569 break\
3570 end\
3571 child = parent\
3572 parent = parent.parent\
3573 if not parent.getNextFocused then\
3574 break\
3575 end\
3576 end\
3577\
3578 if focused then\
3579 self:setFocus(focused)\
3580 end\
3581end\
3582\
3583function UI.Page:setFocus(child)\
3584 if not child.focus then\
3585 return\
3586 --error('cannot focus child ' .. child.UIElement, 2)\
3587 end\
3588\
3589 if self.focused and self.focused ~= child then\
3590 self.focused.focused = false\
3591 self.focused:focus()\
3592 self.focused = child\
3593 end\
3594\
3595 if not child.focused then\
3596 child.focused = true\
3597 self:emit({ type = 'focus_change', focused = child })\
3598 end\
3599\
3600 child:focus()\
3601end\
3602\
3603function UI.Page:eventHandler(event)\
3604\
3605 if self.focused then\
3606 if event.type == 'key' then\
3607 local acc = self.accelerators[event.key]\
3608 if acc then\
3609 if self:eventHandler({ type = acc }) then\
3610 return true\
3611 end\
3612 end\
3613 local ch = event.key\
3614 if ch == 'down' or ch == 'enter' or ch == 'k' or ch == 'tab' then\
3615 self:focusNext()\
3616 return true\
3617 elseif ch == 'tab' then\
3618 --self:focusNextGroup()\
3619 elseif ch == 'up' or ch == 'j' then\
3620 self:focusPrevious()\
3621 return true\
3622 end\
3623 end\
3624 end\
3625end\
3626\
3627--[[-- GridLayout --]]--\
3628UI.GridLayout = class.class(UI.Window)\
3629function UI.GridLayout:init(args)\
3630 local defaults = {\
3631 UIElement = 'GridLayout',\
3632 x = 1,\
3633 y = 1,\
3634 textColor = colors.white,\
3635 backgroundColor = colors.black,\
3636 values = {},\
3637 columns = {}\
3638 }\
3639 UI.setProperties(defaults, args)\
3640 UI.Window.init(self, defaults)\
3641end\
3642\
3643function UI.GridLayout:setParent()\
3644 UI.Window.setParent(self)\
3645 self:adjustWidth()\
3646end\
3647\
3648function UI.GridLayout:adjustWidth()\
3649 if not self.width then\
3650 self.width = self:calculateWidth()\
3651 end\
3652 if self.autospace then\
3653 local width\
3654 for _,col in pairs(self.columns) do\
3655 width = 1\
3656 for _,row in pairs(self.t) do\
3657 local value = row[col[2]]\
3658 if value then\
3659 value = tostring(value)\
3660 if #value > width then\
3661 width = #value\
3662 end\
3663 end\
3664 end\
3665 col[3] = width\
3666 end\
3667\
3668 local colswidth = 0\
3669 for _,c in pairs(self.columns) do\
3670 colswidth = colswidth + c[3] + 1\
3671 end\
3672\
3673 local spacing = (self.width - colswidth - 1) \
3674 if spacing > 0 then\
3675 spacing = math.floor(spacing / (#self.columns - 1) )\
3676 for _,c in pairs(self.columns) do\
3677 c[3] = c[3] + spacing\
3678 end\
3679 end\
3680\
3681--[[\
3682 width = 1\
3683 for _,c in pairs(self.columns) do\
3684 width = c[3] + width + 1\
3685 end\
3686\
3687 if width < self.width then\
3688 local spacing = self.width - width\
3689 spacing = spacing / #self.columns\
3690 for i = 1, #self.columns do\
3691 local col = self.columns[i]\
3692 col[3] = col[3] + spacing\
3693 end\
3694 elseif width > self.width then\
3695 end\
3696--]]\
3697 end\
3698end\
3699\
3700function UI.GridLayout:calculateWidth()\
3701 -- gutters on each side\
3702 local width = 2\
3703 for _,col in pairs(self.columns) do\
3704 width = width + col[3] + 1\
3705 end\
3706 return width - 1\
3707end\
3708\
3709function UI.GridLayout:drawRow(row, y)\
3710 local sb = UI.StringBuffer(self.width)\
3711 local x = 1\
3712 for _,col in pairs(self.columns) do\
3713\
3714 local value = row[col[2]]\
3715 if value then\
3716 sb:insert(string.sub(value, 1, col[3]), x)\
3717 end\
3718\
3719 x = x + col[3] + 1\
3720 end\
3721\
3722 local selected = index == self.index and self.selectable\
3723 if selected then\
3724 self:setSelected(row)\
3725 end\
3726\
3727 self.parent:write(\
3728 self.x, y, sb:get(), self.backgroundColor, self.textColor)\
3729end\
3730\
3731function UI.GridLayout:draw()\
3732\
3733 local size = #self.values\
3734 local startRow = self:getStartRow()\
3735 local endRow = startRow + self.height - 1\
3736 if endRow > size then\
3737 endRow = size\
3738 end\
3739\
3740 for i = startRow, endRow do\
3741 self:drawRow(self.values[i], self.y + i - 1)\
3742 end\
3743\
3744 if endRow - startRow < self.height - 1 then\
3745 self.parent:clearArea(\
3746 self.x, self.y + endRow, self.width, self.height - endRow, self.backgroundColor)\
3747 end\
3748end\
3749\
3750function UI.GridLayout:getStartRow()\
3751 return 1\
3752end\
3753\
3754--[[-- Grid --]]--\
3755UI.Grid = class.class(UI.Window)\
3756function UI.Grid:init(args)\
3757 local defaults = {\
3758 UIElement = 'Grid',\
3759 x = 1,\
3760 y = 1,\
3761 pageNo = 1,\
3762 index = 1,\
3763 inverseSort = false,\
3764 disableHeader = false,\
3765 selectable = true,\
3766 textColor = colors.white,\
3767 textSelectedColor = colors.white,\
3768 backgroundColor = colors.black,\
3769 backgroundSelectedColor = colors.green,\
3770 t = {},\
3771 columns = {}\
3772 }\
3773 UI.setProperties(defaults, args)\
3774 UI.Window.init(self, defaults)\
3775end\
3776\
3777function UI.Grid:setParent()\
3778 UI.Window.setParent(self)\
3779 self:adjustWidth()\
3780 if not self.pageSize then\
3781 if self.disableHeader then\
3782 self.pageSize = self.height\
3783 else\
3784 self.pageSize = self.height - 1\
3785 end\
3786 end\
3787end\
3788\
3789function UI.Grid:adjustWidth()\
3790 if self.autospace then\
3791 for _,col in pairs(self.columns) do\
3792 col[3] = #col[1]\
3793 end\
3794\
3795 for _,col in pairs(self.columns) do\
3796 for _,row in pairs(self.t) do\
3797 local value = row[col[2]]\
3798 if value then\
3799 value = tostring(value)\
3800 if #value > col[3] then\
3801 col[3] = #value\
3802 end\
3803 end\
3804 end\
3805 end\
3806\
3807 local colswidth = 1\
3808 for _,c in pairs(self.columns) do\
3809 colswidth = colswidth + c[3] + 1\
3810 end\
3811\
3812 local function round(num) \
3813 if num >= 0 then return math.floor(num+.5) \
3814 else return math.ceil(num-.5) end\
3815 end\
3816\
3817 local spacing = (self.width - colswidth) \
3818 if spacing > 0 then\
3819 spacing = spacing / (#self.columns - 1)\
3820 local space = 0\
3821 for k,c in pairs(self.columns) do\
3822 space = space + spacing\
3823 c[3] = c[3] + math.floor(round(space) / k)\
3824 end\
3825 end\
3826 end\
3827end\
3828\
3829function UI.Grid:setPosition(x, y)\
3830 self.x = x\
3831 self.y = y\
3832end\
3833\
3834function UI.Grid:setPageSize(pageSize)\
3835 self.pageSize = pageSize\
3836end\
3837\
3838function UI.Grid:setColumns(columns)\
3839 self.columns = columns\
3840end\
3841\
3842function UI.Grid:getTable()\
3843 return self.t\
3844end\
3845\
3846function UI.Grid:setTable(t)\
3847 self.t = t\
3848end\
3849\
3850function UI.Grid:setInverseSort(inverseSort)\
3851 self.inverseSort = inverseSort\
3852 self:drawRows()\
3853end\
3854\
3855function UI.Grid:setSortColumn(column)\
3856 self.sortColumn = column\
3857 for _,col in pairs(self.columns) do\
3858 if col[2] == column then\
3859 return\
3860 end\
3861 end\
3862 error('Grid:setSortColumn: invalid column', 2)\
3863end\
3864\
3865function UI.Grid:setSelected(row)\
3866 self.selected = row\
3867end\
3868\
3869function UI.Grid:getSelected()\
3870 return self.selected\
3871end\
3872\
3873function UI.Grid:focus()\
3874 self:draw()\
3875end\
3876\
3877function UI.Grid:draw()\
3878 if not self.disableHeader then\
3879 self:drawHeadings()\
3880 end\
3881 self:drawRows()\
3882end\
3883\
3884function UI.Grid:drawHeadings()\
3885\
3886 local sb = UI.StringBuffer(self.width)\
3887 local x = 1\
3888 for k,col in ipairs(self.columns) do\
3889 local width = col[3] + 1\
3890 sb:insert(col[1], x)\
3891 x = x + width\
3892 end\
3893 self.parent:write(self.x, self.y, sb:get(), colors.blue)\
3894end\
3895\
3896function UI.Grid:calculateWidth()\
3897 -- gutters on each side\
3898 local width = 2\
3899 for _,col in pairs(self.columns) do\
3900 width = width + col[3] + 1\
3901 end\
3902 return width - 1\
3903end\
3904\
3905function UI.Grid:drawRows()\
3906\
3907 local function sortM(a, b)\
3908 if a[self.sortColumn] and b[self.sortColumn] then\
3909 return a[self.sortColumn] < b[self.sortColumn]\
3910 end\
3911 return false\
3912 end\
3913\
3914 local function inverseSortM(a, b)\
3915 if a[self.sortColumn] and b[self.sortColumn] then\
3916 return a[self.sortColumn] > b[self.sortColumn]\
3917 end\
3918 return false\
3919 end\
3920\
3921 local sortMethod\
3922 if self.sortColumn then\
3923 sortMethod = sortM\
3924 if self.inverseSort then\
3925 sortMethod = inverseSortM\
3926 end\
3927 end\
3928\
3929 if self.index > Util.size(self.t) then\
3930 local newIndex = Util.size(self.t)\
3931 if newIndex <= 0 then\
3932 newIndex = 1\
3933 end\
3934 self:setIndex(newIndex)\
3935 if Util.size(self.t) == 0 then\
3936 y = 1\
3937 if not self.disableHeader then\
3938 y = y + 1\
3939 end\
3940 self:clearArea(1, y, self.width, self.pageSize, self.backgroundColor)\
3941 end\
3942 return\
3943 end\
3944\
3945 local startRow = self:getStartRow()\
3946 local y = self.y\
3947 local rowCount = 0\
3948 local sb = UI.StringBuffer(self.width)\
3949\
3950 if not self.disableHeader then\
3951 y = y + 1\
3952 end\
3953\
3954 local index = 1\
3955 for _,row in Util.spairs(self.t, sortMethod) do\
3956 if index >= startRow then\
3957 sb:clear()\
3958 if index >= startRow + self.pageSize then\
3959 break\
3960 end\
3961\
3962 --if not self.isColor then\
3963 if self.focused then\
3964 if index == self.index and self.selectable then\
3965 sb:insert('>', 0)\
3966 end\
3967 end\
3968 --end\
3969\
3970 local x = 1\
3971 for _,col in pairs(self.columns) do\
3972\
3973 local value = row[col[2]]\
3974 if value then\
3975 sb:insert(string.sub(value, 1, col[3]), x)\
3976 end\
3977\
3978 x = x + col[3] + 1\
3979 end\
3980\
3981 local selected = index == self.index and self.selectable\
3982 if selected then\
3983 self:setSelected(row)\
3984 end\
3985\
3986 self.parent:write(self.x, y, sb:get(),\
3987 self:getRowBackgroundColor(row, selected),\
3988 self:getRowTextColor(row, selected))\
3989\
3990 y = y + 1\
3991 rowCount = rowCount + 1\
3992 end\
3993 index = index + 1\
3994 end\
3995\
3996 if rowCount < self.pageSize then\
3997 self.parent:clearArea(self.x, y, self.width, self.pageSize-rowCount, self.backgroundColor)\
3998 end\
3999 term.setTextColor(colors.white)\
4000end\
4001\
4002function UI.Grid:getRowTextColor(row, selected)\
4003 if selected then\
4004 return self.textSelectedColor\
4005 end\
4006 return self.textColor\
4007end\
4008\
4009function UI.Grid:getRowBackgroundColor(row, selected)\
4010 if selected then\
4011 if self.focused then\
4012 return self.backgroundSelectedColor\
4013 else\
4014 return colors.lightGray\
4015 end\
4016 end\
4017 return self.backgroundColor\
4018end\
4019\
4020function UI.Grid:getIndex(index)\
4021 return self.index\
4022end\
4023\
4024function UI.Grid:setIndex(index)\
4025 if self.index ~= index then\
4026 if index < 1 then\
4027 index = 1\
4028 end\
4029 self.index = index\
4030 self:drawRows()\
4031 end\
4032end\
4033\
4034function UI.Grid:getStartRow()\
4035 return math.floor((self.index - 1)/ self.pageSize) * self.pageSize + 1\
4036end\
4037\
4038function UI.Grid:getPage()\
4039 return math.floor(self.index / self.pageSize) + 1\
4040end\
4041\
4042function UI.Grid:getPageCount()\
4043 local tableSize = Util.size(self.t)\
4044 local pc = math.floor(tableSize / self.pageSize)\
4045 if tableSize % self.pageSize > 0 then\
4046 pc = pc + 1\
4047 end\
4048 return pc\
4049end\
4050\
4051function UI.Grid:nextPage()\
4052 self:setPage(self:getPage() + 1)\
4053end\
4054\
4055function UI.Grid:previousPage()\
4056 self:setPage(self:getPage() - 1)\
4057end\
4058\
4059function UI.Grid:setPage(pageNo)\
4060 -- 1 based paging\
4061 self:setIndex((pageNo-1) * self.pageSize + 1)\
4062end\
4063\
4064function UI.Grid:eventHandler(event)\
4065\
4066 if event.type == 'mouse_click' then\
4067 local row = self:getStartRow() + event.y - 1\
4068 if not self.disableHeader then\
4069 row = row - 1\
4070 end\
4071 if row <= Util.size(self.t) then\
4072 self:setIndex(row)\
4073 self:emit({ type = 'key', key = 'enter' })\
4074 return true\
4075 end\
4076 elseif event.type == 'key' then\
4077 if event.key == 'enter' then\
4078 self:emit({ type = 'grid_select', selected = self.selected })\
4079 return false\
4080 elseif event.key == 'j' or event.key == 'down' then\
4081 self:setIndex(self.index + 1)\
4082 elseif event.key == 'k' or event.key == 'up' then\
4083 self:setIndex(self.index - 1)\
4084 elseif event.key == 'h' then\
4085 self:setIndex(self.index - self.pageSize)\
4086 elseif event.key == 'l' then\
4087 self:setIndex(self.index + self.pageSize)\
4088 elseif event.key == 'home' then\
4089 self:setIndex(1)\
4090 elseif event.key == 'end' then\
4091 self:setIndex(Util.size(self.t))\
4092 elseif event.key == 's' then\
4093 self:setInverseSort(not self.inverseSort)\
4094 else\
4095 return false\
4096 end\
4097 return true\
4098 end\
4099 return false\
4100end\
4101\
4102--[[-- ScrollingGrid --]]--\
4103UI.ScrollingGrid = class.class(UI.Grid)\
4104function UI.ScrollingGrid:init(args)\
4105 local defaults = {\
4106 UIElement = 'ScrollingGrid',\
4107 scrollOffset = 1\
4108 }\
4109 UI.setProperties(self, defaults)\
4110 UI.Grid.init(self, args)\
4111end\
4112\
4113function UI.ScrollingGrid:drawRows()\
4114 UI.Grid.drawRows(self)\
4115 self:drawScrollbar()\
4116end\
4117\
4118function UI.ScrollingGrid:drawScrollbar()\
4119 local ts = Util.size(self.t)\
4120 if ts > self.pageSize then\
4121 term.setBackgroundColor(self.backgroundColor)\
4122 local sbSize = self.pageSize - 2\
4123 local sa = ts -- - self.pageSize\
4124 sa = self.pageSize / sa\
4125 sa = math.floor(sbSize * sa)\
4126 if sa < 1 then\
4127 sa = 1\
4128 end\
4129 if sa > sbSize then\
4130 sa = sbSize\
4131 end\
4132 local sp = ts-self.pageSize\
4133 sp = self.scrollOffset / sp\
4134 sp = math.floor(sp * (sbSize-sa + 0.5))\
4135\
4136 local x = self.x + self.width-1\
4137 if self.scrollOffset > 1 then\
4138 self.parent:write(x, self.y + 1, '^')\
4139 else\
4140 self.parent:write(x, self.y + 1, ' ')\
4141 end\
4142 local row = 0\
4143 for i = 0, sp - 1 do\
4144 self.parent:write(x, self.y + row+2, '|')\
4145 row = row + 1\
4146 end\
4147 for i = 1, sa do\
4148 self.parent:write(x, self.y + row+2, '#')\
4149 row = row + 1\
4150 end\
4151 for i = row, sbSize do\
4152 self.parent:write(x, self.y + row+2, '|')\
4153 row = row + 1\
4154 end\
4155 if self.scrollOffset + self.pageSize - 1 < Util.size(self.t) then\
4156 self.parent:write(x, self.y + self.pageSize, 'v')\
4157 else\
4158 self.parent:write(x, self.y + self.pageSize, ' ')\
4159 end\
4160 end\
4161end\
4162\
4163function UI.ScrollingGrid:getStartRow()\
4164 local ts = Util.size(self.t)\
4165 if ts < self.pageSize then\
4166 self.scrollOffset = 1\
4167 end\
4168 return self.scrollOffset\
4169end\
4170\
4171function UI.ScrollingGrid:setIndex(index)\
4172 if index < self.scrollOffset then\
4173 self.scrollOffset = index\
4174 elseif index - (self.scrollOffset - 1) > self.pageSize then\
4175 self.scrollOffset = index - self.pageSize + 1\
4176 end\
4177\
4178 if self.scrollOffset < 1 then\
4179 self.scrollOffset = 1\
4180 else\
4181 local ts = Util.size(self.t)\
4182 if self.pageSize + self.scrollOffset > ts then\
4183 self.scrollOffset = ts - self.pageSize + 1\
4184 end\
4185 end\
4186 UI.Grid.setIndex(self, index)\
4187end\
4188\
4189--[[-- Menu --]]--\
4190UI.Menu = class.class(UI.Grid)\
4191function UI.Menu:init(args)\
4192 local defaults = {\
4193 UIElement = 'Menu',\
4194 disableHeader = true,\
4195 columns = { { 'Prompt', 'prompt', 20 } },\
4196 t = args['menuItems']\
4197 }\
4198 UI.setProperties(defaults, args)\
4199 UI.Grid.init(self, defaults)\
4200 self.pageSize = #args.menuItems\
4201end\
4202\
4203function UI.Menu:setParent()\
4204 UI.Grid.setParent(self)\
4205 self.itemWidth = 1\
4206 for _,v in pairs(self.t) do\
4207 if #v.prompt > self.itemWidth then\
4208 self.itemWidth = #v.prompt\
4209 end\
4210 end\
4211 self.columns[1][3] = self.itemWidth\
4212\
4213 if self.centered then\
4214 self:center()\
4215 else\
4216 self.width = self.itemWidth + 2\
4217 end\
4218end\
4219\
4220function UI.Menu:center()\
4221 self.x = (self.width - self.itemWidth + 2) / 2\
4222 self.width = self.itemWidth + 2\
4223\
4224end\
4225\
4226function UI.Menu:eventHandler(event)\
4227 if event.type == 'key' then\
4228 if event.key and self.menuItems[tonumber(event.key)] then\
4229 local selected = self.menuItems[tonumber(event.key)]\
4230 self:emit({\
4231 type = selected.event or 'menu_select',\
4232 selected = selected\
4233 })\
4234 return true\
4235 elseif event.key == 'enter' then\
4236 local selected = self.menuItems[self.index]\
4237 self:emit({\
4238 type = selected.event or 'menu_select',\
4239 selected = selected\
4240 })\
4241 return true\
4242 end\
4243 elseif event.type == 'mouse_click' then\
4244 if event.y <= #self.menuItems then\
4245 UI.Grid.setIndex(self, event.y)\
4246 local selected = self.menuItems[self.index]\
4247 self:emit({\
4248 type = selected.event or 'menu_select',\
4249 selected = selected\
4250 })\
4251 return true\
4252 end\
4253 end\
4254 return UI.Grid.eventHandler(self, event)\
4255end\
4256\
4257--[[-- ViewportWindow --]]--\
4258UI.ViewportWindow = class.class(UI.Window)\
4259function UI.ViewportWindow:init(args)\
4260 local defaults = {\
4261 UIElement = 'ViewportWindow',\
4262 x = 1,\
4263 y = 1,\
4264 --width = console.width,\
4265 --height = console.height,\
4266 offset = 0\
4267 }\
4268 UI.setProperties(self, defaults)\
4269 UI.Window.init(self, args)\
4270 self.vpHeight = self.height\
4271end\
4272\
4273function UI.ViewportWindow:clear(bg)\
4274 self:clearArea(1, 1+self.offset, self.width, self.height+self.offset, bg)\
4275end\
4276\
4277function UI.ViewportWindow:write(x, y, text, bg)\
4278 local y = y - self.offset\
4279 if y > self.vpHeight then\
4280 self.vpHeight = y\
4281 end\
4282 if y <= self.height and y > 0 then\
4283 UI.Window.write(self, x, y, text, bg)\
4284 end\
4285end\
4286\
4287function UI.ViewportWindow:setPage(pageNo)\
4288 self:setOffset((pageNo-1) * self.vpHeight + 1)\
4289end\
4290\
4291function UI.ViewportWindow:setOffset(offset)\
4292 local newOffset = math.max(0, math.min(math.max(0, offset), self.vpHeight-self.height))\
4293 if newOffset ~= self.offset then\
4294 self.offset = newOffset\
4295 self:clear()\
4296 self:draw()\
4297 return true\
4298 end\
4299 return false\
4300end\
4301\
4302function UI.ViewportWindow:eventHandler(event)\
4303\
4304 if ch == 'j' or ch == 'down' then\
4305 return self:setOffset(self.offset + 1)\
4306 elseif ch == 'k' or ch == 'up' then\
4307 return self:setOffset(self.offset - 1)\
4308 elseif ch == 'home' then\
4309 self:setOffset(0)\
4310 elseif ch == 'end' then\
4311 return self:setOffset(self.height-self.vpHeight)\
4312 elseif ch == 'h' then\
4313 return self:setPage(\
4314 math.floor((self.offset - self.vpHeight) / self.vpHeight))\
4315 elseif ch == 'l' then\
4316 return self:setPage(\
4317 math.floor((self.offset + self.vpHeight) / self.vpHeight) + 1)\
4318 else\
4319 return false\
4320 end\
4321 return true\
4322end\
4323 \
4324--[[-- ScrollingText --]]--\
4325UI.ScrollingText = class.class(UI.Window)\
4326function UI.ScrollingText:init(args)\
4327 local defaults = {\
4328 UIElement = 'ScrollingText',\
4329 x = 1,\
4330 y = 1,\
4331 backgroundColor = colors.black,\
4332 --height = console.height,\
4333 --width = console.width,\
4334 buffer = { }\
4335 }\
4336 UI.setProperties(defaults, args)\
4337 UI.Window.init(self, defaults)\
4338end\
4339\
4340function UI.ScrollingText:write(text)\
4341 if #self.buffer+1 >= self.height then\
4342 table.remove(self.buffer, 1)\
4343 end\
4344 table.insert(self.buffer, text)\
4345 self:draw()\
4346end\
4347\
4348function UI.ScrollingText:clear()\
4349 self.buffer = { }\
4350 self.parent:clearArea(self.x, self.y, self.width, self.height, self.backgroundColor)\
4351end\
4352\
4353function UI.ScrollingText:draw()\
4354 for k,text in ipairs(self.buffer) do\
4355 self.parent:write(self.x, self.y + k - 1, widthify(text, self.width), self.backgroundColor)\
4356 end\
4357end\
4358\
4359--[[-- TitleBar --]]--\
4360UI.TitleBar = class.class(UI.Window)\
4361function UI.TitleBar:init(args)\
4362 local defaults = {\
4363 UIElement = 'TitleBar',\
4364 height = 1,\
4365 backgroundColor = colors.brown,\
4366 title = ''\
4367 }\
4368 UI.setProperties(defaults, args)\
4369 UI.Window.init(self, defaults)\
4370end\
4371\
4372function UI.TitleBar:draw()\
4373 self:clearArea(1, 1, self.width, 1, self.backgroundColor)\
4374 local centered = math.ceil((self.width - #self.title) / 2)\
4375 self:write(1 + centered, 1, self.title, self.backgroundColor)\
4376 if self.previousPage then\
4377 self:write(self.width, 1, '*', self.backgroundColor, colors.black)\
4378 end\
4379 --self:write(self.width-1, 1, '?', self.backgroundColor)\
4380end\
4381\
4382function UI.TitleBar:eventHandler(event)\
4383 if event.type == 'mouse_click' then\
4384 if self.previousPage and event.x == self.width then\
4385 if type(self.previousPage) == 'string' or\
4386 type(self.previousPage) == 'table' then\
4387 UI.pager:setPage(self.previousPage)\
4388 else\
4389 UI.pager:setPreviousPage()\
4390 end\
4391 return true\
4392 end\
4393 end\
4394end\
4395\
4396--[[-- MenuBar --]]--\
4397UI.MenuBar = class.class(UI.Window)\
4398function UI.MenuBar:init(args)\
4399 local defaults = {\
4400 UIElement = 'MenuBar',\
4401 buttons = { },\
4402 height = 1,\
4403 backgroundColor = colors.lightBlue,\
4404 title = ''\
4405 }\
4406 UI.setProperties(defaults, args)\
4407 UI.Window.init(self, defaults)\
4408\
4409 if not self.children then\
4410 self.children = { }\
4411 end\
4412 local x = 1\
4413 for _,button in pairs(self.buttons) do\
4414 local buttonProperties = {\
4415 x = x,\
4416 width = #button.text + 2,\
4417 backgroundColor = colors.lightBlue,\
4418 textColor = colors.black\
4419 }\
4420 x = x + buttonProperties.width\
4421 UI.setProperties(buttonProperties, button)\
4422 local child = UI.Button(buttonProperties)\
4423 child.parent = self\
4424 table.insert(self.children, child)\
4425 end\
4426end\
4427\
4428--[[-- StatusBar --]]--\
4429UI.StatusBar = class.class(UI.GridLayout)\
4430function UI.StatusBar:init(args)\
4431 local defaults = {\
4432 UIElement = 'StatusBar',\
4433 backgroundColor = colors.gray,\
4434 columns = { \
4435 { '', 'status', 10 },\
4436 },\
4437 values = { },\
4438 status = { status = '' }\
4439 }\
4440 UI.setProperties(defaults, args)\
4441 UI.GridLayout.init(self, defaults)\
4442 self:setStatus(self.status)\
4443end\
4444\
4445function UI.StatusBar:setParent()\
4446 UI.GridLayout.setParent(self)\
4447 self.y = self.height\
4448 if #self.columns == 1 then\
4449 self.columns[1][3] = self.width\
4450 end\
4451end\
4452\
4453function UI.StatusBar:setStatus(status)\
4454 if type(status) == 'string' then\
4455 self.values[1] = { status = status }\
4456 else\
4457 self.values[1] = status\
4458 end\
4459end\
4460\
4461function UI.StatusBar:setValue(name, value)\
4462 self.status[name] = value\
4463end\
4464\
4465function UI.StatusBar:getValue(name)\
4466 return self.status[name]\
4467end\
4468\
4469function UI.StatusBar:timedStatus(status, timeout)\
4470 timeout = timeout or 3\
4471 self:write(2, 1, widthify(status, self.width-2), self.backgroundColor)\
4472 Event.addNamedTimer('statusTimer', timeout, false, function()\
4473 -- fix someday\
4474 if self.parent.enabled then\
4475 self:draw()\
4476 end\
4477 end)\
4478end\
4479\
4480function UI.StatusBar:getColumnWidth(name)\
4481 for _,v in pairs(self.columns) do\
4482 if v[2] == name then\
4483 return v[3]\
4484 end\
4485 end\
4486end\
4487\
4488function UI.StatusBar:setColumnWidth(name, width)\
4489 for _,v in pairs(self.columns) do\
4490 if v[2] == name then\
4491 v[3] = width\
4492 break\
4493 end\
4494 end\
4495end\
4496\
4497--[[-- ProgressBar --]]--\
4498UI.ProgressBar = class.class(UI.Window)\
4499function UI.ProgressBar:init(args)\
4500 local defaults = {\
4501 UIElement = 'ProgressBar',\
4502 progressColor = colors.lime,\
4503 backgroundColor = colors.gray,\
4504 height = 1,\
4505 progress = 0\
4506 }\
4507 UI.setProperties(self, defaults)\
4508 UI.Window.init(self, args)\
4509end\
4510\
4511function UI.ProgressBar:draw()\
4512 local progressWidth = math.ceil(self.progress / 100 * self.width)\
4513 if progressWidth > 0 then\
4514 self.parent:write(self.x, self.y, string.rep(' ', progressWidth), self.progressColor)\
4515 end\
4516 local x = self.x + progressWidth\
4517 progressWidth = self.width - progressWidth\
4518 if progressWidth > 0 then\
4519 self.parent:write(x, self.y, string.rep(' ', progressWidth), self.backgroundColor)\
4520 end\
4521end\
4522\
4523function UI.ProgressBar:setProgress(progress)\
4524 self.progress = progress\
4525end\
4526\
4527--[[-- VerticalMeter --]]--\
4528UI.VerticalMeter = class.class(UI.Window)\
4529function UI.VerticalMeter:init(args)\
4530 local defaults = {\
4531 UIElement = 'VerticalMeter',\
4532 meterColor = colors.lime,\
4533 height = 1,\
4534 percent = 0\
4535 }\
4536 UI.setProperties(defaults, args)\
4537 UI.Window.init(self, defaults)\
4538end\
4539\
4540function UI.VerticalMeter:draw()\
4541 local height = self.height - math.ceil(self.percent / 100 * self.height)\
4542 local filler = string.rep(' ', self.width)\
4543\
4544 for i = 1, height do\
4545 self:write(1, i, filler, self.backgroundColor)\
4546 end\
4547\
4548 for i = height+1, self.height do\
4549 self:write(1, i, filler, self.meterColor)\
4550 end\
4551end\
4552\
4553function UI.VerticalMeter:setPercent(percent)\
4554 self.percent = percent\
4555end\
4556\
4557--[[-- Button --]]--\
4558UI.Button = class.class(UI.Window)\
4559function UI.Button:init(args)\
4560 local defaults = {\
4561 UIElement = 'Button',\
4562 text = 'button',\
4563 focused = false,\
4564 backgroundColor = colors.gray,\
4565 backgroundFocusColor = colors.green,\
4566 height = 1,\
4567 width = 8,\
4568 event = 'button_press'\
4569 }\
4570 UI.setProperties(defaults, args)\
4571 UI.Window.init(self, defaults)\
4572end\
4573\
4574function UI.Button:draw()\
4575 local bg = self.backgroundColor\
4576 local ind = ' '\
4577 if self.focused then\
4578 bg = self.backgroundFocusColor\
4579 ind = '>'\
4580 end\
4581 self:clear(bg)\
4582 local text = ind .. self.text .. ' '\
4583 self:centeredWrite(1 + math.floor(self.height / 2), text, bg)\
4584end\
4585\
4586function UI.Button:focus()\
4587 self:draw()\
4588end\
4589\
4590function UI.Button:eventHandler(event)\
4591 if (event.type == 'key' and event.key == 'enter') or \
4592 event.type == 'mouse_click' then\
4593 self:emit({ type = self.event, button = self })\
4594 return true\
4595 end\
4596 return false\
4597end\
4598\
4599--[[-- TextEntry --]]--\
4600UI.TextEntry = class.class(UI.Window)\
4601function UI.TextEntry:init(args)\
4602 local defaults = {\
4603 UIElement = 'TextEntry',\
4604 value = '',\
4605 type = 'string',\
4606 focused = false,\
4607 backgroundColor = colors.lightGray,\
4608 backgroundFocusColor = colors.green,\
4609 height = 1,\
4610 width = 8,\
4611 limit = 6\
4612 }\
4613 UI.setProperties(defaults, args)\
4614 UI.Window.init(self, defaults)\
4615end\
4616\
4617function UI.TextEntry:setParent()\
4618 UI.Window.setParent(self)\
4619 if self.limit + 2 > self.width then\
4620 self.limit = self.width - 2\
4621 end\
4622end\
4623\
4624function UI.TextEntry:draw()\
4625 local bg = self.backgroundColor\
4626 if self.focused then\
4627 bg = self.backgroundFocusColor\
4628 end\
4629 self:write(1, 1, ' ' .. widthify(self.value, self.limit) .. ' ', bg)\
4630 if self.focused then\
4631 self:updateCursor()\
4632 end\
4633end\
4634\
4635function UI.TextEntry:updateCursor()\
4636 if not self.pos then\
4637 self.pos = #self.value\
4638 elseif self.pos > #self.value then\
4639 self.pos = #self.value\
4640 end\
4641 self:displayCursor(self.pos+2, 1)\
4642end\
4643\
4644function UI.TextEntry:focus()\
4645 self:draw()\
4646 if self.focused then\
4647 term.setCursorBlink(true)\
4648 else\
4649 term.setCursorBlink(false)\
4650 end\
4651end\
4652\
4653--[[\
4654 A few lines below from theoriginalbit\
4655 http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/\
4656--]]\
4657function UI.TextEntry:eventHandler(event)\
4658 if event.type == 'key' then\
4659 local ch = event.key\
4660 if ch == 'left' then\
4661 if self.pos > 0 then\
4662 self.pos = math.max(self.pos-1, 0)\
4663 self:updateCursor()\
4664 end\
4665 elseif ch == 'right' then\
4666 local input = self.value\
4667 if self.pos < #input then\
4668 self.pos = math.min(self.pos+1, #input)\
4669 self:updateCursor()\
4670 end\
4671 elseif ch == 'home' then\
4672 self.pos = 0\
4673 self:updateCursor()\
4674 elseif ch == 'end' then\
4675 self.pos = #self.value\
4676 self:updateCursor()\
4677 elseif ch == 'backspace' then\
4678 if self.pos > 0 then\
4679 local input = self.value\
4680 self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1)\
4681 self.pos = self.pos - 1\
4682 self:draw()\
4683 self:updateCursor()\
4684 end\
4685 elseif ch == 'delete' then\
4686 local input = self.value\
4687 if self.pos < #input then\
4688 self.value = input:sub(1, self.pos) .. input:sub(self.pos+2)\
4689 self:draw()\
4690 self:updateCursor()\
4691 end\
4692 elseif #ch == 1 then\
4693 local input = self.value\
4694 if #input < self.limit then\
4695 self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1)\
4696 self.pos = self.pos + 1\
4697 self:draw()\
4698 self:updateCursor()\
4699 end\
4700 else\
4701 return false\
4702 end\
4703 return true\
4704 end\
4705 return false\
4706end\
4707\
4708--[[-- Chooser --]]--\
4709UI.Chooser = class.class(UI.Window)\
4710function UI.Chooser:init(args)\
4711 local defaults = {\
4712 UIElement = 'Chooser',\
4713 choices = { },\
4714 nochoice = 'Select',\
4715 backgroundColor = colors.lightGray,\
4716 backgroundFocusColor = colors.green,\
4717 height = 1\
4718 }\
4719 UI.setProperties(defaults, args)\
4720 UI.Window.init(self, defaults)\
4721end\
4722\
4723function UI.Chooser:setParent()\
4724 if not self.width then\
4725 self.width = 1\
4726 for _,v in pairs(self.choices) do\
4727 if #v.name > self.width then\
4728 self.width = #v.name\
4729 end\
4730 end\
4731 self.width = self.width + 4\
4732 end\
4733 UI.Window.setParent(self)\
4734end\
4735\
4736function UI.Chooser:draw()\
4737 local bg = self.backgroundColor\
4738 if self.focused then\
4739 bg = self.backgroundFocusColor\
4740 end\
4741 local choice = Util.find(self.choices, 'value', self.value)\
4742 local value = self.nochoice\
4743 if choice then\
4744 value = choice.name\
4745 end\
4746 self:write(1, 1, '<', bg, colors.black)\
4747 self:write(2, 1, ' ' .. widthify(value, self.width-4) .. ' ', bg)\
4748 self:write(self.width, 1, '>', bg, colors.black)\
4749end\
4750\
4751function UI.Chooser:focus()\
4752 self:draw()\
4753end\
4754\
4755function UI.Chooser:eventHandler(event)\
4756 if event.type == 'key' then\
4757 if event.key == 'right' or event.key == 'space' then\
4758 local choice,k = Util.find(self.choices, 'value', self.value)\
4759 if k and k < #self.choices then\
4760 self.value = self.choices[k+1].value\
4761 else\
4762 self.value = self.choices[1].value\
4763 end\
4764 self:emit({ type = 'choice_change', value = self.value })\
4765 self:draw()\
4766 return true\
4767 elseif event.key == 'left' then\
4768 local choice,k = Util.find(self.choices, 'value', self.value)\
4769 if k and k > 1 then\
4770 self.value = self.choices[k-1].value\
4771 else\
4772 self.value = self.choices[#self.choices].value\
4773 end\
4774 self:emit({ type = 'choice_change', value = self.value })\
4775 self:draw()\
4776 return true\
4777 end\
4778 elseif event.type == 'mouse_click' then\
4779 if event.x == 1 then\
4780 self:emit({ type = 'key', key = 'left' })\
4781 return true\
4782 elseif event.x == self.width then\
4783 self:emit({ type = 'key', key = 'right' })\
4784 return true\
4785 end\
4786 end\
4787end\
4788\
4789--[[-- Text --]]--\
4790UI.Text = class.class(UI.Window)\
4791function UI.Text:init(args)\
4792 local defaults = {\
4793 UIElement = 'Text',\
4794 value = '',\
4795 height = 1\
4796 }\
4797 UI.setProperties(defaults, args)\
4798 UI.Window.init(self, defaults)\
4799end\
4800\
4801function UI.Text:setParent()\
4802 if not self.width then\
4803 self.width = #self.value\
4804 end\
4805 UI.Window.setParent(self)\
4806end\
4807\
4808function UI.Text:draw()\
4809 local value = self.value or ''\
4810 self:write(1, 1, widthify(value, self.width), self.backgroundColor)\
4811end\
4812\
4813--[[-- Form --]]--\
4814UI.Form = class.class(UI.Window)\
4815UI.Form.D = { -- display\
4816 static = UI.Text,\
4817 entry = UI.TextEntry,\
4818 chooser = UI.Chooser,\
4819 button = UI.Button\
4820}\
4821\
4822UI.Form.V = { -- validation\
4823 number = function(value)\
4824 return type(value) == 'number'\
4825 end\
4826}\
4827\
4828UI.Form.T = { -- data types\
4829 number = function(value)\
4830 return tonumber(value)\
4831 end\
4832}\
4833\
4834function UI.Form:init(args)\
4835 local defaults = {\
4836 UIElement = 'Form',\
4837 values = {},\
4838 fields = {},\
4839 columns = {\
4840 { 'Name', 'name', 20 },\
4841 { 'Values', 'value', 20 }\
4842 },\
4843 x = 1,\
4844 y = 1,\
4845 labelWidth = 20,\
4846 accept = function() end,\
4847 cancel = function() end\
4848 }\
4849 UI.setProperties(defaults, args)\
4850 UI.Window.init(self, defaults)\
4851 self:initChildren(self.values)\
4852end\
4853\
4854function UI.Form:setValues(values)\
4855 self.values = values\
4856 for k,child in pairs(self.children) do\
4857 if child.key then\
4858 child.value = self.values[child.key]\
4859 if not child.value then\
4860 child.value = ''\
4861 end\
4862 end\
4863 end\
4864end\
4865\
4866function UI.Form:initChildren(values)\
4867 self.values = values\
4868\
4869 if not self.children then\
4870 self.children = { }\
4871 end\
4872\
4873 for k,field in pairs(self.fields) do\
4874 if field.label then\
4875 self['label_' .. k] = UI.Text({\
4876 x = 1,\
4877 y = k,\
4878 width = #field.label,\
4879 value = field.label\
4880 })\
4881 end\
4882 local value\
4883 if field.key then\
4884 value = self.values[field.key]\
4885 end\
4886 if not value then\
4887 value = ''\
4888 end\
4889 value = tostring(value)\
4890 local width = #value\
4891 if field.limit then\
4892 width = field.limit + 2\
4893 end\
4894 local fieldProperties = {\
4895 x = self.labelWidth + 2,\
4896 y = k,\
4897 width = width,\
4898 value = value\
4899 }\
4900 UI.setProperties(fieldProperties, field)\
4901 local child = field.display(fieldProperties)\
4902 child.parent = self\
4903 table.insert(self.children, child)\
4904 end\
4905end\
4906\
4907function UI.Form:eventHandler(event)\
4908\
4909 if event.type == 'accept' then\
4910 for _,child in pairs(self.children) do\
4911 if child.key then\
4912 self.values[child.key] = child.value\
4913 end\
4914 end\
4915 return false\
4916 end\
4917\
4918 return false\
4919end\
4920\
4921--[[-- Dialog --]]--\
4922UI.Dialog = class.class(UI.Page)\
4923function UI.Dialog:init(args)\
4924 local defaults = {\
4925 x = 7,\
4926 y = 4,\
4927 width = UI.term.width-11,\
4928 height = 7,\
4929 backgroundColor = colors.lightBlue,\
4930 titleBar = UI.TitleBar({ previousPage = true }),\
4931 acceptButton = UI.Button({\
4932 text = 'Accept',\
4933 event = 'accept',\
4934 x = 5,\
4935 y = 5\
4936 }),\
4937 cancelButton = UI.Button({\
4938 text = 'Cancel',\
4939 event = 'cancel',\
4940 x = 17,\
4941 y = 5\
4942 }),\
4943 statusBar = UI.StatusBar(),\
4944 }\
4945 UI.setProperties(defaults, args)\
4946 UI.Page.init(self, defaults)\
4947end\
4948\
4949function UI.Dialog:eventHandler(event)\
4950 if event.type == 'cancel' then\
4951 UI.pager:setPreviousPage()\
4952 end\
4953 return UI.Page.eventHandler(self, event)\
4954end\
4955\
4956--[[-- Spinner --]]--\
4957UI.Spinner = class.class()\
4958function UI.Spinner:init(args)\
4959 local defaults = {\
4960 UIElement = 'Spinner',\
4961 timeout = .095,\
4962 x = 1,\
4963 y = 1,\
4964 c = os.clock(),\
4965 spinIndex = 0,\
4966 spinSymbols = { '-', '/', '|', '\\\\' }\
4967 }\
4968 defaults.x, defaults.y = term.getCursorPos()\
4969 defaults.startX = defaults.x\
4970 defaults.startY = defaults.y\
4971\
4972 UI.setProperties(self, defaults)\
4973 UI.setProperties(self, args)\
4974end\
4975\
4976function UI.Spinner:spin(text)\
4977 local cc = os.clock()\
4978 if cc > self.c + self.timeout then\
4979 term.setCursorPos(self.x, self.y)\
4980 local str = self.spinSymbols[self.spinIndex % #self.spinSymbols + 1]\
4981 if text then\
4982 str = str .. ' ' .. text\
4983 end\
4984 term.write(str)\
4985 self.spinIndex = self.spinIndex + 1\
4986 self.c = cc\
4987 os.sleep(0)\
4988 end\
4989end\
4990\
4991function UI.Spinner:stop(text)\
4992 term.setCursorPos(self.x, self.y)\
4993 local str = string.rep(' ', #self.spinSymbols)\
4994 if text then\
4995 str = str .. ' ' .. text\
4996 end\
4997 term.write(str)\
4998 term.setCursorPos(self.startX, self.startY)\
4999end\
5000\
5001--[[-- Table Viewer --]]--\
5002function UI.displayTable(t, title)\
5003\
5004 local resultPath = { }\
5005\
5006 local resultsPage = UI.Page({\
5007 parent = UI.term,\
5008 titleBar = UI.TitleBar(),\
5009 grid = UI.ScrollingGrid({\
5010 columns = { \
5011 { 'Name', 'name', 10 },\
5012 { 'Value', 'value', 10 }\
5013 }, \
5014 sortColumn = 'name',\
5015 pageSize = UI.term.height - 2,\
5016 y = 2,\
5017 width = UI.term.width,\
5018 height = UI.term.height - 3,\
5019 autospace = true\
5020 }),\
5021 })\
5022\
5023 function resultsPage:setResult(result, title)\
5024 local t = { }\
5025 if type(result) == 'table' then\
5026 for k,v in pairs(result) do\
5027 local entry = {\
5028 name = k,\
5029 value = tostring(v)\
5030 }\
5031 if type(v) == 'table' then\
5032 if Util.size(v) == 0 then\
5033 entry.value = 'table: (empty)'\
5034 else\
5035 entry.value = 'table'\
5036 entry.table = v\
5037 end\
5038 end\
5039 table.insert(t, entry)\
5040 end\
5041 else\
5042 table.insert(t, {\
5043 name = 'result',\
5044 value = tostring(result)\
5045 })\
5046 end\
5047 self.grid.sortColumn = 'Name'\
5048 self.grid.columns = { \
5049 { 'Name', 'name', 10 },\
5050 { 'Value', 'value', 10 }\
5051 }\
5052 self.grid.t = t\
5053 self.grid:adjustWidth()\
5054 if title then\
5055 self.titleBar.title = title\
5056 end\
5057 end\
5058\
5059 function resultsPage.grid:flatten()\
5060 self.columns = { }\
5061 local _,first = next(self.t)\
5062 for k in pairs(first.table) do\
5063 table.insert(self.columns, {\
5064 k, k, 1\
5065 })\
5066 end\
5067 local t = { }\
5068 for k,v in pairs(self.t) do\
5069 v.table.__key = v.name\
5070 t[v.name] = v.table\
5071 end\
5072 self.t = t\
5073 self.sortColumn = '__key'\
5074 self:adjustWidth()\
5075 self:draw()\
5076 end\
5077\
5078 function resultsPage.grid:eventHandler(event)\
5079 if event.type == 'key' then\
5080 local ch = event.key\
5081 if ch == 'enter' or ch == 'l' then\
5082 local nameValue = self:getSelected()\
5083 if nameValue.table then\
5084 if Util.size(nameValue.table) > 0 then\
5085 table.insert(resultPath, self.t)\
5086 resultsPage:setResult(nameValue.table)\
5087 self:setIndex(1)\
5088 self:draw()\
5089 end\
5090 end\
5091 return true\
5092 elseif ch == 'f' then\
5093 self:flatten()\
5094 elseif ch == 'h' then\
5095 if #resultPath > 0 then\
5096 self.t = table.remove(resultPath)\
5097 self.columns = { \
5098 { 'Name', 'name', 10 },\
5099 { 'Value', 'value', 10 }\
5100 }\
5101 self.sortColumn = 'Name'\
5102 self:adjustWidth()\
5103 self:draw()\
5104 else\
5105 UI.pager:setPreviousPage()\
5106 end\
5107 return true\
5108 elseif ch == 'q' then\
5109 UI.pager:setPreviousPage()\
5110 return true\
5111 end\
5112 end\
5113 return UI.Grid.eventHandler(self, event)\
5114 end\
5115\
5116 resultsPage:setResult(t, title or 'Table Viewer')\
5117 UI.pager:setPage(resultsPage)\
5118end\
5119\
5120--Import \
5121_G.TableDB = class.class()\
5122function TableDB:init(args)\
5123 local defaults = {\
5124 fileName = '',\
5125 dirty = false,\
5126 data = { },\
5127 tabledef = { },\
5128 }\
5129 UI.setProperties(defaults, args)\
5130 UI.setProperties(self, defaults)\
5131end\
5132\
5133function TableDB:load()\
5134 local table = Util.readTable(self.fileName)\
5135 if table then\
5136 self.data = table.data\
5137 self.tabledef = table.tabledef\
5138 end\
5139end\
5140\
5141function TableDB:add(key, entry)\
5142 if type(key) == 'table' then\
5143 key = table.concat(key, ':')\
5144 end\
5145 self.data[key] = entry\
5146 self.dirty = true\
5147end\
5148\
5149function TableDB:get(key)\
5150 if type(key) == 'table' then\
5151 key = table.concat(key, ':')\
5152 end\
5153 return self.data[key]\
5154end\
5155\
5156function TableDB:remove(key)\
5157 self.data[key] = nil\
5158 self.dirty = true\
5159end\
5160\
5161function TableDB:flush()\
5162 if self.dirty then\
5163 Util.writeTable(self.fileName, {\
5164 tabledef = self.tabledef,\
5165 data = self.data,\
5166 })\
5167 self.dirty = false\
5168 end\
5169end\
5170\
5171if turtle then\
5172--Import \
5173--[[\
5174Modes:\
5175 normal - oob functionality\
5176 pathfind - goes around blocks/mobs\
5177 destructive - destroys blocks\
5178 friendly - destructive and creates a 2 block walking passage (not implemented)\
5179\
5180Dig strategies:\
5181 none - does not dig or kill mobs\
5182 normal - digs and kills mobs\
5183 wasteful - digs and drops mined blocks and kills mobs\
5184 cautious - digs and kills mobs but will not destroy other turtles\
5185--]]\
5186\
5187_G.TL2 = { }\
5188\
5189TL2.actions = {\
5190 up = {\
5191 detect = turtle.detectUp,\
5192 dig = turtle.digUp,\
5193 move = turtle.up,\
5194 attack = turtle.attackUp,\
5195 place = turtle.placeUp,\
5196 suck = turtle.suckUp,\
5197 compare = turtle.compareUp,\
5198 side = 'top'\
5199 },\
5200 down = {\
5201 detect = turtle.detectDown,\
5202 dig = turtle.digDown,\
5203 move = turtle.down,\
5204 attack = turtle.attackDown,\
5205 place = turtle.placeDown,\
5206 suck = turtle.suckDown,\
5207 compare = turtle.compareDown,\
5208 side = 'bottom'\
5209 },\
5210 forward = {\
5211 detect = turtle.detect,\
5212 dig = turtle.dig,\
5213 move = turtle.forward,\
5214 attack = turtle.attack,\
5215 place = turtle.place,\
5216 suck = turtle.suck,\
5217 compare = turtle.compare,\
5218 side = 'front'\
5219 }\
5220}\
5221\
5222TL2.headings = {\
5223 [ 0 ] = { xd = 1, yd = 0, zd = 0, heading = 0 }, -- east\
5224 [ 1 ] = { xd = 0, yd = 1, zd = 0, heading = 1 }, -- south\
5225 [ 2 ] = { xd = -1, yd = 0, zd = 0, heading = 2 }, -- west\
5226 [ 3 ] = { xd = 0, yd = -1, zd = 0, heading = 3 }, -- north\
5227 [ 4 ] = { xd = 0, yd = 0, zd = 1, heading = 4 }, -- up\
5228 [ 5 ] = { xd = 0, yd = 0, zd = -1, heading = 5 } -- down\
5229}\
5230\
5231TL2.namedHeadings = {\
5232 east = 0,\
5233 south = 1,\
5234 west = 2,\
5235 north = 3,\
5236 up = 4,\
5237 down = 5\
5238}\
5239\
5240_G.Point = { }\
5241-- used mainly to extract point specific info from a table\
5242function Point.create(pt)\
5243 return { x = pt.x, y = pt.y, z = pt.z }\
5244end\
5245\
5246function Point.subtract(a, b)\
5247 a.x = a.x - b.x\
5248 a.y = a.y - b.y\
5249 a.z = a.z - b.z\
5250end\
5251\
5252function Point.tostring(pt)\
5253 local str = string.format('x:%d y:%d z:%d', pt.x, pt.y, pt.z)\
5254 if pt.heading then\
5255 str = str .. ' heading:' .. pt.heading\
5256 end\
5257 return str\
5258end\
5259\
5260-- deprecated\
5261function TL2.createPoint(pt)\
5262 return Point.create(pt)\
5263end\
5264\
5265function TL2.pointToBox(pt, width, length, height)\
5266 return { ax = pt.x,\
5267 ay = pt.y,\
5268 az = pt.z,\
5269 bx = pt.x + width - 1,\
5270 by = pt.y + length - 1,\
5271 bz = pt.z + height - 1\
5272 }\
5273end\
5274\
5275function TL2.pointInBox(pt, box)\
5276 return pt.x >= box.ax and\
5277 pt.y >= box.ay and\
5278 pt.x <= box.bx and\
5279 pt.y <= box.by\
5280end\
5281\
5282function TL2.boxContain(boundingBox, containedBox)\
5283\
5284 local shiftX = boundingBox.ax - containedBox.ax\
5285 if shiftX > 0 then\
5286 containedBox.ax = containedBox.ax + shiftX\
5287 containedBox.bx = containedBox.bx + shiftX\
5288 end\
5289 local shiftY = boundingBox.ay - containedBox.ay\
5290 if shiftY > 0 then\
5291 containedBox.ay = containedBox.ay + shiftY\
5292 containedBox.by = containedBox.by + shiftY\
5293 end\
5294\
5295 shiftX = boundingBox.bx - containedBox.bx\
5296 if shiftX < 0 then\
5297 containedBox.ax = containedBox.ax + shiftX\
5298 containedBox.bx = containedBox.bx + shiftX\
5299 end\
5300 shiftY = boundingBox.by - containedBox.by\
5301 if shiftY < 0 then\
5302 containedBox.ay = containedBox.ay + shiftY\
5303 containedBox.by = containedBox.by + shiftY\
5304 end\
5305end\
5306\
5307function TL2.calculateTurns(ih, oh)\
5308 if ih == oh then\
5309 return 0\
5310 end\
5311 if (ih % 2) == (oh % 2) then\
5312 return 2\
5313 end\
5314 return 1\
5315end\
5316\
5317function TL2.calculateDistance(a, b)\
5318--[[\
5319 return math.sqrt(\
5320 math.pow(a.x - b.x, 2) + \
5321 math.pow(a.y - b.y, 2) +\
5322 math.pow(a.z - b.z, 2))\
5323--]]\
5324 return math.max(a.x, b.x) - math.min(a.x, b.x) + \
5325 math.max(a.y, b.y) - math.min(a.y, b.y) +\
5326 math.max(a.z, b.z) - math.min(a.z, b.z)\
5327end\
5328\
5329function TL2.calculateHeadingTowards(startPt, endPt, heading)\
5330 local xo = endPt.x - startPt.x\
5331 local yo = endPt.y - startPt.y\
5332\
5333 if heading == 0 and xo > 0 or \
5334 heading == 2 and xo < 0 or \
5335 heading == 1 and yo > 0 or \
5336 heading == 3 and yo < 0 then \
5337 -- maintain current heading\
5338 return heading\
5339 elseif heading == 0 and yo > 0 or \
5340 heading == 2 and yo < 0 or\
5341 heading == 1 and xo < 0 or\
5342 heading == 3 and xo > 0 then\
5343 -- right\
5344 return (heading + 1) % 4\
5345 elseif heading == 0 and yo < 0 or \
5346 heading == 2 and yo > 0 or\
5347 heading == 1 and xo < 0 or\
5348 heading == 3 and xo > 0 then\
5349 -- left\
5350 return (heading + 1) % 4\
5351 elseif yo == 0 and xo ~= 0 or\
5352 xo == 0 and yo ~= 0 then\
5353 -- behind\
5354 return (heading + 2) % 4\
5355 elseif endPt.z > startPt.z then\
5356 -- up\
5357 return 4\
5358 elseif endPt.z < startPt.z then\
5359 -- down\
5360 return 5\
5361 end\
5362 return heading\
5363end\
5364\
5365local function _attack(action)\
5366 if action.attack() then\
5367 Util.tryTimed(4,\
5368 function()\
5369 -- keep trying until attack fails\
5370 return not action.attack()\
5371 end)\
5372 return true\
5373 end\
5374 return false\
5375end\
5376\
5377local modes = {\
5378 normal = function(action)\
5379 return action.move()\
5380 end,\
5381\
5382 destructive = function(action, digStrategy)\
5383 while not action.move() do\
5384 if not _attack(action) then\
5385 if not digStrategy(action) then\
5386 return false\
5387 end\
5388 end\
5389 Logger.log('turtle', 'destructive move retry: ')\
5390 end\
5391 return true\
5392 end,\
5393\
5394}\
5395\
5396TL2.digStrategies = {\
5397 none = function(action)\
5398 return false\
5399 end,\
5400\
5401 normal = function(action)\
5402 if action.dig() then\
5403 return true\
5404 end\
5405 if not action.attack() then\
5406 return false\
5407 end\
5408 for i = 1, 50 do\
5409 if not action.attack() then\
5410 break\
5411 end\
5412 end\
5413 return true\
5414 end,\
5415\
5416 cautious = function(action)\
5417 if not TL2.isTurtleAt(action.side) then\
5418 return action.dig()\
5419 end\
5420 os.sleep(.5)\
5421 return not action.detect()\
5422 end,\
5423\
5424 wasteful = function(action)\
5425 -- why is there no method to get current slot ?\
5426 local slots = TL2.getInventory()\
5427\
5428-- code below should be done -- only reconcile if no empty slot\
5429-- taking the cautious approach for now\
5430--if not selectOpenSlot() then\
5431--return false\
5432--end\
5433\
5434 if action.detect() and action.dig() then\
5435 TL2.reconcileInventory(slots)\
5436 return true\
5437 end\
5438 TL2.reconcileInventory(slots)\
5439 return false\
5440 end\
5441}\
5442\
5443local state = {\
5444 x = 0,\
5445 y = 0,\
5446 z = 0,\
5447 slot = 1, -- must track slot since there is no method to determine current slot\
5448 -- not relying on this for now (but will track)\
5449 heading = 0,\
5450 gps = false,\
5451 maxRetries = 100,\
5452 mode = modes.normal,\
5453 dig = TL2.digStrategies.normal\
5454}\
5455\
5456local memory = {\
5457 locations = {},\
5458 blocks = {}\
5459}\
5460\
5461function TL2.getState()\
5462 return state\
5463end\
5464\
5465function TL2.getMemory()\
5466 return memory\
5467end\
5468\
5469function TL2.select(slot)\
5470 state.slot = slot\
5471 turtle.select(slot)\
5472end\
5473\
5474function TL2.forward()\
5475 if not state.mode(TL2.actions.forward, state.dig) then\
5476 return false\
5477 end\
5478 state.x = state.x + TL2.headings[state.heading].xd\
5479 state.y = state.y + TL2.headings[state.heading].yd\
5480 return true\
5481end\
5482\
5483function TL2.up()\
5484 if state.mode(TL2.actions.up, state.dig) then\
5485 state.z = state.z + 1\
5486 return true\
5487 end\
5488 return false\
5489end\
5490\
5491function TL2.down()\
5492 if not state.mode(TL2.actions.down, state.dig) then\
5493 return false\
5494 end\
5495 state.z = state.z - 1\
5496 return true\
5497end\
5498\
5499function TL2.back()\
5500 if not turtle.back() then\
5501 return false\
5502 end\
5503 state.x = state.x - TL2.headings[state.heading].xd\
5504 state.y = state.y - TL2.headings[state.heading].yd\
5505 return true\
5506end\
5507\
5508function TL2.dig()\
5509 return state.dig(TL2.actions.forward)\
5510end\
5511\
5512function TL2.digUp()\
5513 return state.dig(TL2.actions.up)\
5514end\
5515\
5516function TL2.digDown()\
5517 return state.dig(TL2.actions.down)\
5518end\
5519\
5520function TL2.attack()\
5521 _attack(TL2.actions.forward)\
5522end\
5523\
5524function TL2.attackUp()\
5525 _attack(TL2.actions.up)\
5526end\
5527\
5528function TL2.attackDown()\
5529 _attack(TL2.actions.down)\
5530end\
5531\
5532local complexActions = {\
5533 up = {\
5534 detect = turtle.detectUp,\
5535 dig = TL2.digUp,\
5536 move = TL2.up,\
5537 attack = TL2.attackUp,\
5538 place = turtle.placeUp,\
5539 side = 'top'\
5540 },\
5541 down = {\
5542 detect = turtle.detectDown,\
5543 dig = TL2.digDown,\
5544 move = TL2.down,\
5545 attack = TL2.attackDown,\
5546 place = turtle.placeDown,\
5547 side = 'bottom'\
5548 },\
5549 forward = {\
5550 detect = turtle.detect,\
5551 dig = TL2.dig,\
5552 move = TL2.forward,\
5553 attack = TL2.attack,\
5554 place = turtle.place,\
5555 side = 'front'\
5556 }\
5557}\
5558\
5559local function _place(action, slot)\
5560 return Util.tryTimed(5,\
5561 function()\
5562 if action.detect() then\
5563 action.dig()\
5564 end\
5565 TL2.select(slot)\
5566 if action.place() then\
5567 return true\
5568 end\
5569 _attack(action) \
5570 end)\
5571end\
5572\
5573function TL2.place(slot)\
5574 return _place(complexActions.forward, slot)\
5575end\
5576\
5577function TL2.placeUp(slot)\
5578 return _place(complexActions.up, slot)\
5579end\
5580\
5581function TL2.placeDown(slot)\
5582 return _place(complexActions.down, slot)\
5583end\
5584\
5585function TL2.getModes()\
5586 return modes\
5587end\
5588\
5589function TL2.getMode()\
5590 return state.mode\
5591end\
5592\
5593function TL2.setMode(mode)\
5594 if not modes[mode] then\
5595 error('TL2.setMode: invalid mode', 2)\
5596 end\
5597 state.mode = modes[mode]\
5598end\
5599\
5600function TL2.setDigStrategy(digStrategy)\
5601 if not TL2.digStrategies[digStrategy] then\
5602 error('TL2.setDigStrategy: invalid strategy', 2)\
5603 end\
5604 state.dig = TL2.digStrategies[digStrategy]\
5605end\
5606\
5607function TL2.reset()\
5608 state.gxOff = state.gxOff - state.x\
5609 state.gyOff = state.gyOff - state.y\
5610 state.gzOff = state.gzOff - state.z\
5611 state.x = 0\
5612 state.y = 0\
5613 state.z = 0\
5614end\
5615\
5616function TL2.isTurtleAt(side)\
5617 local sideType = peripheral.getType(side)\
5618 return sideType and sideType == 'turtle'\
5619end\
5620\
5621function TL2.saveLocation()\
5622 return Util.shallowCopy(state)\
5623end\
5624\
5625function TL2.getNamedLocation(name)\
5626 return memory.locations[name]\
5627end\
5628\
5629function TL2.gotoNamedLocation(name)\
5630 local nl = memory.locations[name]\
5631 if nl then\
5632 return TL2.goto(nl.x, nl.y, nl.z, nl.heading)\
5633 end\
5634end\
5635\
5636function TL2.getStoredPoint(name)\
5637 return Util.readTable(name .. '.pt')\
5638end\
5639\
5640function TL2.gotoStoredPoint(name)\
5641 local pt = TL2.getStoredPoint(name)\
5642 if pt then\
5643 return TL2.gotoPoint(pt)\
5644 end\
5645end\
5646\
5647function TL2.storePoint(name, pt)\
5648 Util.writeTable(name .. '.pt', pt)\
5649end\
5650\
5651function TL2.storeCurrentPoint(name)\
5652 local ray = TL2.getPoint()\
5653 TL2.storePoint(name, ray)\
5654end\
5655\
5656function TL2.saveNamedLocation(name, x, y, z, heading)\
5657 if x then\
5658 memory.locations[name] = {\
5659 x = x,\
5660 y = y,\
5661 z = z,\
5662 heading = heading\
5663 }\
5664 else\
5665 memory.locations[name] = {\
5666 x = state.x,\
5667 y = state.y,\
5668 z = state.z,\
5669 heading = state.heading\
5670 }\
5671 end\
5672end\
5673\
5674function TL2.normalizeCoords(x, y, z, heading)\
5675 return\
5676 x - state.gxOff,\
5677 y - state.gyOff,\
5678 z - state.gzOff,\
5679 heading\
5680end\
5681\
5682function TL2.gotoLocation(nloc)\
5683 if nloc.gps then\
5684 return TL2.goto(\
5685 nloc.x - state.gxOff,\
5686 nloc.y - state.gyOff,\
5687 nloc.z - state.gzOff,\
5688 nloc.heading)\
5689 else\
5690 return TL2.goto(nloc.x, nloc.y, nloc.z, nloc.heading)\
5691 end\
5692end\
5693\
5694function TL2.turnRight()\
5695 TL2.setHeading(state.heading + 1)\
5696end\
5697\
5698function TL2.turnLeft()\
5699 TL2.setHeading(state.heading - 1)\
5700end\
5701\
5702function TL2.turnAround()\
5703 TL2.setHeading(state.heading + 2)\
5704end\
5705\
5706function TL2.getHeadingInfo(heading)\
5707 heading = heading or state.heading\
5708 return TL2.headings[heading]\
5709end\
5710\
5711function TL2.setNamedHeading(headingName)\
5712 local heading = TL2.namedHeadings[headingName]\
5713 if heading then \
5714 TL2.setHeading(heading)\
5715 end\
5716 return false\
5717end\
5718\
5719function TL2.getHeading()\
5720 return state.heading\
5721end\
5722\
5723function TL2.setHeading(heading)\
5724 if heading ~= state.heading then\
5725 while heading < state.heading do\
5726 heading = heading + 4\
5727 end\
5728 if heading - state.heading == 3 then\
5729 turtle.turnLeft()\
5730 state.heading = state.heading - 1\
5731 else\
5732 turns = heading - state.heading\
5733 while turns > 0 do\
5734 turns = turns - 1\
5735 state.heading = state.heading + 1\
5736 turtle.turnRight()\
5737 end\
5738 end\
5739\
5740 if state.heading > 3 then\
5741 state.heading = state.heading - 4\
5742 elseif state.heading < 0 then\
5743 state.heading = state.heading + 4\
5744 end\
5745 end\
5746end\
5747\
5748function TL2.gotoPoint(pt)\
5749 return TL2.goto(pt.x, pt.y, pt.z, pt.heading)\
5750end\
5751\
5752function TL2.goto(ix, iy, iz, iheading)\
5753\
5754 if TL2.gotoEx(ix, iy, iz, iheading) then\
5755 return true\
5756 end\
5757\
5758 local moved\
5759 repeat\
5760 local x, y, z = state.x, state.y, state.z\
5761\
5762 -- try going the other way\
5763 if (state.heading % 2) == 1 then\
5764 TL2.headTowardsX(ix)\
5765 else\
5766 TL2.headTowardsY(iy)\
5767 end\
5768\
5769 if TL2.gotoEx(ix, iy, iz, iheading) then\
5770 return true\
5771 end\
5772\
5773 if iz then\
5774 TL2.gotoZ(iz)\
5775 end\
5776\
5777 moved = x ~= state.x or y ~= state.y or z ~= state.z\
5778 until not moved\
5779\
5780 return false\
5781end\
5782\
5783-- z and heading are optional\
5784function TL2.gotoEx(ix, iy, iz, iheading)\
5785\
5786 -- determine the heading to ensure the least amount of turns\
5787 -- first check is 1 turn needed - remaining require 2 turns\
5788 if state.heading == 0 and state.x <= ix or \
5789 state.heading == 2 and state.x >= ix or \
5790 state.heading == 1 and state.y <= iy or \
5791 state.heading == 3 and state.y >= iy then \
5792 -- maintain current heading\
5793 -- nop\
5794 elseif iy > state.y and state.heading == 0 or \
5795 iy < state.y and state.heading == 2 or\
5796 ix < state.x and state.heading == 1 or\
5797 ix > state.x and state.heading == 3 then\
5798 TL2.turnRight()\
5799 else\
5800 TL2.turnLeft()\
5801 end\
5802\
5803 if (state.heading % 2) == 1 then\
5804 if not TL2.gotoY(iy) then return false end\
5805 if not TL2.gotoX(ix) then return false end\
5806 else\
5807 if not TL2.gotoX(ix) then return false end\
5808 if not TL2.gotoY(iy) then return false end\
5809 end\
5810\
5811 if iz then\
5812 if not TL2.gotoZ(iz) then return false end\
5813 end\
5814\
5815 if iheading then\
5816 TL2.setHeading(iheading)\
5817 end\
5818 return true\
5819end\
5820\
5821function TL2.headTowardsX(ix)\
5822 if state.x ~= ix then\
5823 if state.x > ix then\
5824 TL2.setHeading(2)\
5825 else\
5826 TL2.setHeading(0)\
5827 end\
5828 end\
5829end\
5830\
5831function TL2.headTowardsY(iy)\
5832 if state.y ~= iy then\
5833 if state.y > iy then\
5834 TL2.setHeading(3)\
5835 else\
5836 TL2.setHeading(1)\
5837 end\
5838 end\
5839end\
5840\
5841function TL2.headTowards(pt)\
5842 if state.x ~= pt.x then\
5843 TL2.headTowardsX(pt.x)\
5844 else\
5845 TL2.headTowardsY(pt.y)\
5846 end\
5847end\
5848\
5849function TL2.gotoX(ix)\
5850 TL2.headTowardsX(ix)\
5851\
5852 while state.x ~= ix do\
5853 if not TL2.forward() then\
5854 return false\
5855 end\
5856 end\
5857 return true\
5858end\
5859\
5860function TL2.gotoY(iy)\
5861 TL2.headTowardsY(iy)\
5862\
5863 while state.y ~= iy do\
5864 if not TL2.forward() then\
5865 return false\
5866 end\
5867 end\
5868 return true\
5869end\
5870\
5871function TL2.gotoZ(iz)\
5872 while state.z > iz do\
5873 if not TL2.down() then\
5874 return false\
5875 end\
5876 end\
5877 \
5878 while state.z < iz do\
5879 if not TL2.up() then\
5880 return false\
5881 end\
5882 end\
5883 return true\
5884end\
5885\
5886function TL2.emptySlots(dropAction)\
5887 dropAction = dropAction or turtle.drop\
5888 for i = 1, 16 do\
5889 TL2.emptySlot(i, dropAction)\
5890 end\
5891end\
5892\
5893function TL2.emptySlot(slot, dropAction)\
5894 dropAction = dropAction or turtle.drop\
5895 local count = turtle.getItemCount(slot)\
5896 if count > 0 then\
5897 TL2.select(slot)\
5898 dropAction(count)\
5899 end\
5900end\
5901\
5902function TL2.getSlots(slots)\
5903 slots = slots or { }\
5904 for i = 1, 16 do\
5905 slots[i] = { qty = turtle.getItemCount(i) }\
5906 slots[i].slotNo = i\
5907 end\
5908 return slots\
5909end\
5910\
5911function TL2.getFilledSlots(startSlot)\
5912 startSlot = startSlot or 1\
5913\
5914 local slots = { }\
5915 for i = startSlot, 16 do\
5916 local count = turtle.getItemCount(i)\
5917 if count > 0 then\
5918 slots[i] = {\
5919 qty = turtle.getItemCount(i),\
5920 slotNo = i\
5921 }\
5922 end\
5923 end\
5924 return slots\
5925end\
5926\
5927-- deprecated\
5928function TL2.getInventory()\
5929 return TL2.getSlots()\
5930end\
5931\
5932function TL2.reconcileInventory(slots, dropAction)\
5933 dropAction = dropAction or turtle.drop\
5934 for i = 1, 16 do\
5935 local qty = turtle.getItemCount(i)\
5936 if qty > slots[i].qty then\
5937 TL2.select(i)\
5938 dropAction(qty-slots[i].qty)\
5939 end\
5940 end\
5941end\
5942\
5943function TL2.selectSlotWithItems(startSlot)\
5944 startSlot = startSlot or 1\
5945 for i = startSlot, 16 do\
5946 if turtle.getItemCount(i) > 0 then\
5947 TL2.select(i)\
5948 return i\
5949 end\
5950 end\
5951end\
5952\
5953function TL2.selectOpenSlot(startSlot)\
5954 return TL2.selectSlotWithQuantity(0, startSlot)\
5955end\
5956\
5957function TL2.selectSlotWithQuantity(qty, startSlot)\
5958 startSlot = startSlot or 1\
5959\
5960 for i = startSlot, 16 do\
5961 if turtle.getItemCount(i) == qty then\
5962 TL2.select(i)\
5963 return i\
5964 end\
5965 end\
5966end\
5967\
5968function TL2.getPoint()\
5969 return { x = state.x, y = state.y, z = state.z, heading = state.heading }\
5970end\
5971\
5972function TL2.setPoint(pt)\
5973 state.x = pt.x\
5974 state.y = pt.y\
5975 state.z = pt.z\
5976 if pt.heading then\
5977 state.heading = pt.heading\
5978 end\
5979end\
5980\
5981_G.GPS = { }\
5982function GPS.locate()\
5983 local pt = { }\
5984 pt.x, pt.z, pt.y = gps.locate(10)\
5985 if pt.x then\
5986 return pt\
5987 end\
5988end\
5989\
5990function GPS.isAvailable()\
5991 return Util.hasDevice(\"modem\") and GPS.locate()\
5992end\
5993\
5994function GPS.initialize()\
5995 --[[\
5996 TL2.getState().gps = GPS.getPoint()\
5997 --]]\
5998end\
5999\
6000function GPS.gotoPoint(pt)\
6001 --[[\
6002 local heading\
6003 if pt.heading then\
6004 heading = (pt.heading + state.gps.heading) % 4\
6005 end\
6006 return TL2.goto(\
6007 pt.x - state.gps.x,\
6008 pt.y - state.gps.y,\
6009 pt.z - state.gps.z,\
6010 heading)\
6011 --]]\
6012end\
6013\
6014function GPS.getPoint()\
6015 local ray = TL2.getPoint()\
6016 \
6017 local apt = GPS.locate()\
6018 if not apt then\
6019 error(\"GPS.getPoint: GPS not available\")\
6020 end\
6021\
6022 while not TL2.forward() do\
6023 TL2.turnRight()\
6024 if TL2.getHeading() == ray.heading then\
6025 error('GPS.getPoint: Unable to move forward')\
6026 end\
6027 end\
6028\
6029 local bpt = GPS.locate()\
6030 if not apt then\
6031 error(\"No GPS\")\
6032 end\
6033\
6034 if not TL2.back() then\
6035 error(\"GPS.getPoint: Unable to move back\")\
6036 end\
6037\
6038 if apt.x < bpt.x then\
6039 apt.heading = 0\
6040 elseif apt.y < bpt.y then\
6041 apt.heading = 1\
6042 elseif apt.x > bpt.x then\
6043 apt.heading = 2\
6044 else\
6045 apt.heading = 3\
6046 end\
6047\
6048 return apt\
6049end\
6050\
6051function GPS.storeCurrentPoint(name)\
6052 local ray = GPS.getPoint()\
6053 TL2.storePoint(name, ray)\
6054end\
6055 \
6056--[[\
6057 All pathfinding related follows\
6058\
6059 b = block\
6060 a = adjacent block\
6061 bb = bounding box\
6062 c = coordinates\
6063--]]\
6064local function addAdjacentBlock(blocks, b, dir, bb, a)\
6065 local key = a.x .. ':' .. a.y .. ':' .. a.z\
6066\
6067 if b.adj[dir] then\
6068 a = b.adj[dir]\
6069 else\
6070 local _a = blocks[key]\
6071 if _a then\
6072 a = _a\
6073 else\
6074 blocks[key] = a\
6075 end\
6076 end\
6077 local revDir = { 2, 3, 0, 1, 5, 4 }\
6078 b.adj[dir] = a\
6079 a.adj[revDir[dir+1]] = b\
6080--[[\
6081 -- too much time...\
6082 if dir == 4 and turtle.detectUp() then\
6083 a.blocked = true\
6084 elseif dir == 5 and turtle.detectDown() then\
6085 a.blocked = true\
6086 elseif dir == state.heading and turtle.detect() then\
6087 a.blocked = true\
6088--]]\
6089 if a.x < bb.ax or a.x > bb.bx or\
6090 a.y < bb.ay or a.y > bb.by or\
6091 a.z < bb.az or a.z > bb.bz then\
6092 a.blocked = true\
6093 end\
6094end\
6095\
6096local function addAdjacentBlocks(blocks, b, bb)\
6097 if not b.setAdj then\
6098 addAdjacentBlock(blocks, b, 0, bb,\
6099 { x = state.x+1, y = state.y , z = state.z , adj = {} })\
6100 addAdjacentBlock(blocks, b, 1, bb,\
6101 { x = state.x , y = state.y+1, z = state.z , adj = {} })\
6102 addAdjacentBlock(blocks, b, 2, bb,\
6103 { x = state.x-1, y = state.y , z = state.z , adj = {} })\
6104 addAdjacentBlock(blocks, b, 3, bb,\
6105 { x = state.x , y = state.y-1, z = state.z , adj = {} })\
6106 addAdjacentBlock(blocks, b, 4, bb,\
6107 { x = state.x , y = state.y , z = state.z + 1, adj = {} })\
6108 addAdjacentBlock(blocks, b, 5, bb,\
6109 { x = state.x , y = state.y , z = state.z - 1, adj = {} })\
6110 end\
6111 b.setAdj = true\
6112end\
6113\
6114local function getMovesTo(x, y, z)\
6115 local dest = { x = x, y = y, z = z }\
6116 return calculateMoves(state, dest, state.heading)\
6117end\
6118\
6119local function calculateMoves(p, dest, heading)\
6120 local d = calculateDistance(p, dest)\
6121print('bh: ' .. state.heading .. ' h: ' .. heading)\
6122 if state.heading ~= heading then\
6123 -- calculate the turns from current point to new point\
6124 d = d + calculateTurns(state.heading, heading)\
6125 end\
6126 local newHeading = calculateHeadingTowards(p, dest, heading) \
6127 d = d + calculateTurns(heading, newHeading)\
6128print(pp(p))\
6129print(pp(dest))\
6130print(string.format('h:%d t:%d t2:%d d:%d nh:%d',\
6131 heading, calculateTurns(state.heading, heading),\
6132 calculateTurns(heading, newHeading), d, newHeading))\
6133 return d\
6134end\
6135\
6136local function calculateAdjacentBlockDistances(b, dest)\
6137 for k,a in pairs(b.adj) do\
6138 if not a.blocked then\
6139 --a.distance = calculateMoves(a, dest, k)\
6140 a.distance = TL2.calculateDistance(a, dest)\
6141 else\
6142 a.distance = 9000\
6143 end\
6144--print(string.format('%d: %f %d,%d,%d, %s', k, a.distance, a.x, a.y, a.z, tostring(a.blocked)))\
6145--read()\
6146 end\
6147 -- read()\
6148end\
6149\
6150local function blockedIn(b)\
6151 for _,a in pairs(b.adj) do\
6152 if not a.blocked then\
6153 return false\
6154 end\
6155 end\
6156 return true\
6157end\
6158\
6159local function pathfindMove(b)\
6160 for _,a in Util.spairs(b.adj, function(a, b) return a.distance < b.distance end) do\
6161\
6162 --print('shortest: ' .. pc(a) .. ' distance: ' .. a.distance)\
6163 if not a.blocked then\
6164 local success = TL2.moveTowardsPoint(a)\
6165 if success then\
6166 return a\
6167 end\
6168 a.blocked = true\
6169 end\
6170 end\
6171end\
6172\
6173local function rpath(blocks, block, dest, boundingBox)\
6174 \
6175 addAdjacentBlocks(blocks, block, boundingBox)\
6176 calculateAdjacentBlockDistances(block, dest)\
6177\
6178 block.blocked = true\
6179 repeat\
6180 local newBlock = pathfindMove(block)\
6181 if newBlock then\
6182 if state.x == dest.x and state.y == dest.y and state.z == dest.z then\
6183 block.blocked = false\
6184 return true\
6185 end\
6186 --[[\
6187 if goto then return true end\
6188 block = getCurrentBlock() (gets or creates block)\
6189 but this might - will - move us into a block we marked as blockedin\
6190 if block.blocked then\
6191 goto newBlock\
6192 block = getCurrentBlock() (gets or creates block)\
6193 end\
6194 maybe check if we have a clear line of sight to the destination\
6195 ie. keep traveling towards destination building up blocked blocks\
6196 as we encounter them (instead of adding all blocks on the path)\
6197 --]]\
6198 if rpath(blocks, newBlock, dest, boundingBox) then\
6199 block.blocked = false\
6200 return true\
6201 end\
6202 if not TL2.moveTowardsPoint(block) then\
6203 return false\
6204 end\
6205 end\
6206 until blockedIn(block)\
6207 return false\
6208end\
6209\
6210--[[\
6211 goto will traverse towards the destination until it is blocked\
6212 by something on the x, y, or z coordinate of the destination\
6213\
6214 pathfinding will attempt to find a way around the blockage\
6215\
6216 goto example:\
6217 . . >-X B D stuck behind block\
6218 . . | . B .\
6219 . . | . . .\
6220 S >-^B. . .\
6221\
6222 pathfind example:\
6223 . . >-v B D when goto fails, pathfinding kicks in\
6224 . . | |-B-|\
6225 . . | >---^\
6226 S >-^B. . .\
6227\
6228--]]\
6229function TL2.pathtofreely(ix, iy, iz, iheading)\
6230 local boundingBox = {\
6231 ax = -9000,\
6232 ay = -9000,\
6233 az = -9000,\
6234 bx = 9000,\
6235 by = 9000,\
6236 bz = 9000\
6237 }\
6238 return TL2.pathto(ix, iy, iz, iheading, boundingBox)\
6239end\
6240\
6241local function pathfind(ix, iy, iz, iheading, boundingBox)\
6242\
6243 local blocks = { } -- memory.blocks\
6244 local dest = { x = ix, y = iy, z = iz }\
6245 local block = { x = state.x, y = state.y, z = state.z, adj = {} }\
6246 local key = block.x .. ':' .. block.y .. ':' .. block.z\
6247\
6248 blocks[key] = block\
6249\
6250 if rpath(blocks, block, dest, boundingBox) then\
6251 if iheading then\
6252 TL2.setHeading(iheading)\
6253 end\
6254 return true\
6255 end\
6256\
6257 return false\
6258end\
6259\
6260function TL2.pathto(ix, iy, iz, iheading, boundingBox)\
6261 local start = { x = state.x, y = state.y, z = state.z }\
6262 if TL2.goto(ix, iy, iz, iheading) then\
6263 return true\
6264 end\
6265\
6266 if not iz then\
6267 iz = state.z\
6268 end\
6269\
6270 if not boundingBox then\
6271 boundingBox = {\
6272 ax = math.min(ix, start.x),\
6273 ay = math.min(iy, start.y),\
6274 az = math.min(iz, start.z),\
6275 bx = math.max(ix, start.x),\
6276 by = math.max(iy, start.y),\
6277 bz = math.max(iz, start.z),\
6278 }\
6279 end\
6280 return pathfind(ix, iy, iz, iheading, boundingBox)\
6281end\
6282\
6283function TL2.moveTowardsPoint(c)\
6284\
6285 if c.z > state.z then\
6286 return TL2.up()\
6287 elseif c.z < state.z then\
6288 return TL2.down()\
6289 end\
6290 TL2.headTowards(c)\
6291 return TL2.forward()\
6292end\
6293\
6294--Import \
6295--[[\
6296 turtle action chains and communications\
6297--]]\
6298_G.TLC = { }\
6299local __requestor\
6300\
6301local registeredActions = {\
6302 goto = function(args)\
6303 if args.mode then\
6304 TL2.setMode(args.mode)\
6305 end\
6306 if args.digStrategy then\
6307 TL2.setDigStrategy(args.digStrategy)\
6308 end\
6309 return TL2.pathto(args.x, args.y, args.z, args.heading)\
6310 end,\
6311\
6312 gotoZ = function(args)\
6313 if args.mode then\
6314 TL2.setMode(args.mode)\
6315 end\
6316 if args.digStrategy then\
6317 TL2.setDigStrategy(args.digStrategy)\
6318 end\
6319 return TL2.gotoZ(args.z)\
6320 end,\
6321\
6322 move = function(args)\
6323 local result = false\
6324 if args.mode then\
6325 TL2.setMode(args.mode)\
6326 end\
6327 if args.digStrategy then\
6328 TL2.setDigStrategy(args.digStrategy)\
6329 end\
6330 for i = 1, args.moves do\
6331 if args.subaction == \"u\" then\
6332 result = TL2.up()\
6333 elseif args.subaction == \"d\" then\
6334 result = TL2.down()\
6335 elseif args.subaction == \"f\" then\
6336 result = TL2.forward()\
6337 elseif args.subaction == \"r\" then\
6338 result = TL2.turnRight()\
6339 elseif args.subaction == \"l\" then\
6340 result = TL2.turnLeft()\
6341 elseif args.subaction == \"b\" then\
6342 result = TL2.back()\
6343 end \
6344 if not result then\
6345 return false\
6346 end\
6347 end \
6348 return result\
6349 end,\
6350\
6351--[[\
6352 pushState = function(action)\
6353 TL2.getState().saveLoc = TL2.saveLocation()\
6354 TL2.getState().saveLoc.dig = TL2.loc.dig\
6355 TL2.getState().saveLoc.mode = TL2.loc.mode\
6356 end,\
6357\
6358 popState = function(action)\
6359 local saveLoc = TL2.getState().saveLoc\
6360 TL2.gotoLocation(saveLoc)\
6361 TL2.loc.dig = saveLoc.dig\
6362 TL2.loc.mode = saveLoc.mode\
6363 end,\
6364--]]\
6365\
6366 reset = function(args)\
6367 TL2.getState().x = 0\
6368 TL2.getState().y = 0\
6369 TL2.getState().z = 0\
6370 return true\
6371 end,\
6372\
6373 shutdown = function(args)\
6374 os.shutdown()\
6375 -- for consistency :)\
6376 return true\
6377 end,\
6378\
6379 reboot = function(args)\
6380 os.reboot()\
6381 return true\
6382 end,\
6383\
6384 gotoNamedLocation = function(args)\
6385 return TL2.gotoNamedLocation(args.name)\
6386 end,\
6387\
6388 adjustLocation = function(args)\
6389 if args.x then\
6390 TL2.getState().x = TL2.getState().x + args.x\
6391 end\
6392 if args.y then\
6393 TL2.getState().y = TL2.getState().y + args.y\
6394 end\
6395 if args.z then\
6396 TL2.getState().z = TL2.getState().z + args.z\
6397 end\
6398 return true\
6399 end,\
6400\
6401 setLocation = function(args)\
6402 TL2.getState().x = args.x\
6403 TL2.getState().y = args.y\
6404 TL2.getState().z = args.z\
6405 if args.heading then\
6406 TL2.getState().heading = args.heading\
6407 end\
6408 return true\
6409 end,\
6410\
6411 status = function(args, action)\
6412 TLC.sendStatus(action.requestor)\
6413 return true\
6414 end\
6415}\
6416\
6417function TLC.sendStatus(requestor)\
6418 local state = TL2.getState()\
6419\
6420 state.name = os.getComputerLabel()\
6421 state.fuel = turtle.getFuelLevel()\
6422 TL2.getMemory().lastStatus = os.clock()\
6423\
6424 requestor = requestor or __requestor\
6425 Logger.log('turtle', '>> Status to ' .. tostring(requestor) .. ' of ' .. state.status)\
6426 if requestor then\
6427 Message.send(requestor, 'turtleStatus', state)\
6428 elseif not __requestor then\
6429 Message.broadcast('turtleStatus', state)\
6430 end\
6431end\
6432\
6433function TLC.registerAction(action, f)\
6434 registeredActions[action] = f\
6435end\
6436\
6437function TLC.performActions(actions)\
6438\
6439 local function performSingleAction(action)\
6440 local actionName = action.action\
6441 local actionFunction = registeredActions[actionName]\
6442\
6443 if not actionFunction then\
6444 Logger.log('turtle', 'Unknown turtle action: ' .. tostring(actionName))\
6445 Logger.log('turtle', action)\
6446 error('Unknown turtle action: ' .. tostring(actionName))\
6447 end\
6448 -- perform action\
6449 Logger.log('turtle', '<< Action: ' .. actionName)\
6450\
6451 if actionName == 'status' then\
6452 return actionFunction(action.args, action)\
6453 end\
6454\
6455 local state = TL2.getState()\
6456 local previousStatus = state.status\
6457 state.status = 'busy'\
6458 result = actionFunction(action.args, action)\
6459 state.status = 'idle'\
6460 return result\
6461 end\
6462\
6463 local chain = {}\
6464\
6465 local chainStart = TL2.getMemory().chainStart\
6466 if chainStart then\
6467 table.insert(chain, { action = chainStart })\
6468 end\
6469\
6470 for _,action in ipairs(actions) do\
6471 table.insert(chain, action)\
6472 end\
6473\
6474 local chainEnd = TL2.getMemory().chainEnd\
6475 if chainEnd then\
6476 table.insert(chain, { action = chainEnd })\
6477 end\
6478\
6479 for _,action in ipairs(chain) do\
6480 if not action.result then\
6481 action.result = performSingleAction(action)\
6482 if not action.result then\
6483 Logger.log('turtle', action)\
6484 Logger.log('turtle', '**Action failed')\
6485 if not action.retryCount then\
6486 action.retryCount = 1\
6487 else\
6488 action.retryCount = action.retryCount + 1\
6489 end\
6490 if action.retryCount < 3 then\
6491 -- queue action chain for another attempt\
6492 TL2.getState().status = 'busy'\
6493 os.queueEvent('turtleActions', actions)\
6494 end\
6495 return false\
6496 end\
6497 end\
6498 end\
6499 return true\
6500end\
6501\
6502function TLC.performAction(actionName, actionArgs)\
6503 return TLC.performActions({{ action = actionName, args = actionArgs }})\
6504end\
6505\
6506function TLC.actionChainStart(chainStart)\
6507 TL2.getMemory().chainStart = chainStart\
6508end\
6509\
6510function TLC.actionChainEnd(chainEnd)\
6511 TL2.getMemory().chainEnd = chainEnd\
6512end\
6513\
6514function TLC.sendAction(id, actionName, actionArgs)\
6515 local action = {\
6516 action = actionName,\
6517 args = actionArgs\
6518 }\
6519 Logger.log('turtle', '>>Sending to ' .. tostring(id) .. ' ' .. actionName)\
6520 Message.send(id, 'turtle', { action })\
6521end\
6522\
6523function TLC.sendActions(id, actions)\
6524 local str = ''\
6525 for _,v in pairs(actions) do\
6526 str = str .. ' ' .. v.action\
6527 end\
6528 Logger.log('turtle', '>>Sending to ' .. id .. str)\
6529 Message.send(id, 'turtle', actions)\
6530end\
6531\
6532function TLC.requestState(id)\
6533 Logger.log('turtle', '>>Requesting state from ' .. id)\
6534 Message.send(id, 'turtleState')\
6535end\
6536\
6537function TLC.setRole(role)\
6538 TL2.getState().role = role\
6539end\
6540\
6541--[[\
6542 The following method should only be executed once\
6543 a different strategy should be implemented to allow\
6544 this to be called multiple times\
6545--]]\
6546function TLC.pullEvents(role, hasaheart)\
6547 \
6548 TL2.getState().status = 'idle'\
6549 TL2.getState().role = role\
6550 TL2.getMemory().lastStatus = os.clock()\
6551\
6552 local function heartbeat()\
6553 while true do\
6554 os.sleep(5)\
6555 os.queueEvent('heartbeat')\
6556 end \
6557 end\
6558\
6559 Message.addHandler('turtleState',\
6560 function(h, id)\
6561__requestor = id\
6562 TLC.sendStatus(id)\
6563 end\
6564 )\
6565\
6566 Message.addHandler('turtle', \
6567 function(h, id, msg, distance)\
6568\
6569 local actions = msg.contents\
6570 for _,action in ipairs(actions) do\
6571 action.distance = distance\
6572 action.requestor = id\
6573 end\
6574 TLC.performActions(actions)\
6575 end\
6576 )\
6577\
6578 Event.addHandler('turtleActions',\
6579 function(h, actions)\
6580 TL2.getMemory().lastStatus = os.clock()\
6581 TLC.performActions(actions)\
6582 end\
6583 )\
6584\
6585 Message.addHandler('alive',\
6586 function(e, id, msg)\
6587 if TL2.getState().status ~= 'busy' then\
6588__requestor = id\
6589 TL2.getMemory().lastStatus = os.clock()\
6590 msg.type = \"isAlive\"\
6591 msg.contents = os.getComputerLabel()\
6592 rednet.send(id, msg)\
6593 end\
6594 end\
6595 )\
6596\
6597 if hasaheart then\
6598 Event.addHandler('heartbeat',\
6599 function()\
6600 local state = TL2.getState()\
6601 if state.status == 'idle' and\
6602 os.clock() > TL2.getMemory().lastStatus + 5 then\
6603 TLC.sendStatus()\
6604 end\
6605 end\
6606 )\
6607 end\
6608\
6609 parallel.waitForAny(Event.pullEvents, heartbeat)\
6610end\
6611\
6612--[[\
6613 all the following code does not belong in this file\
6614 should be located in it's own class file or moved\
6615--]]\
6616function TLC.condence(slot)\
6617 local iQty = turtle.getItemCount(slot)\
6618 for i = 1, 16 do\
6619 if i ~= slot then\
6620 local qty = turtle.getItemCount(i)\
6621 if qty > 0 then\
6622 turtle.select(i)\
6623 if turtle.compareTo(slot) then\
6624 turtle.transferTo(slot, qty)\
6625 iQty = iQty + qty\
6626 if iQty >= 64 then\
6627 break\
6628 end\
6629 end\
6630 end\
6631 end\
6632 end\
6633end\
6634\
6635-- from stock gps API\
6636local function trilaterate( A, B, C ) \
6637 local a2b = B.position - A.position\
6638 local a2c = C.position - A.position\
6639\
6640 if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then\
6641 return nil \
6642 end \
6643\
6644 local d = a2b:length()\
6645 local ex = a2b:normalize( )\
6646 local i = ex:dot( a2c )\
6647 local ey = (a2c - (ex * i)):normalize()\
6648 local j = ey:dot( a2c )\
6649 local ez = ex:cross( ey )\
6650\
6651 local r1 = A.distance\
6652 local r2 = B.distance\
6653 local r3 = C.distance\
6654\
6655 local x = (r1*r1 - r2*r2 + d*d) / (2*d)\
6656 local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)\
6657 \
6658 local result = A.position + (ex * x) + (ey * y)\
6659\
6660 local zSquared = r1*r1 - x*x - y*y \
6661 if zSquared > 0 then\
6662 local z = math.sqrt( zSquared )\
6663 local result1 = result + (ez * z)\
6664 local result2 = result - (ez * z)\
6665 \
6666 local rounded1, rounded2 = result1:round(), result2:round()\
6667 if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then\
6668 return rounded1, rounded2\
6669 else\
6670 return rounded1\
6671 end \
6672 end \
6673 return result:round()\
6674end\
6675\
6676local function narrow( p1, p2, fix )\
6677 local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )\
6678 local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )\
6679\
6680 if math.abs(dist1 - dist2) < 0.05 then\
6681 return p1, p2\
6682 elseif dist1 < dist2 then\
6683 return p1:round()\
6684 else\
6685 return p2:round()\
6686 end \
6687end\
6688\
6689-- end stock gps api\
6690\
6691TLC.tFixes = {}\
6692\
6693Message.addHandler('position', function(h, id, msg, distance)\
6694 local tFix = {\
6695 position = vector.new(msg.contents.x, msg.contents.z, msg.contents.y),\
6696 distance = distance\
6697 }\
6698 table.insert(TLC.tFixes, tFix)\
6699\
6700 if #TLC.tFixes == 4 then\
6701 Logger.log('turtle', 'trilaterating')\
6702 local pos1, pos2 = trilaterate(TLC.tFixes[1], TLC.tFixes[2], TLC.tFixes[3])\
6703 pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[3])\
6704 if pos2 then\
6705 pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[4])\
6706 end\
6707\
6708 if pos1 and pos2 then\
6709 print(\"Ambiguous position\")\
6710 print(\"Could be \"..pos1.x..\",\"..pos1.y..\",\"..pos1.z..\" or \"..pos2.x..\",\"..pos2.y..\",\"..pos2.z )\
6711 elseif pos1 then\
6712 Logger.log('turtle', \"Position is \"..pos1.x..\",\"..pos1.y..\",\"..pos1.z)\
6713 TLC.performAction('setLocation', {\
6714 x = pos1.x,\
6715 y = pos1.z,\
6716 z = pos1.y\
6717 })\
6718 else\
6719 print(\"Could not determine position\")\
6720 end\
6721 end\
6722end)\
6723\
6724function TLC.getDistance(id)\
6725 for i = 1, 10 do\
6726 rednet.send(id, { type = 'alive' })\
6727 local _, _, _, distance = Message.waitForMessage('isAlive', 1, id)\
6728 if distance then\
6729 Logger.log('turtle', 'distance: ' .. distance)\
6730 return distance\
6731 end\
6732 end\
6733end\
6734\
6735local function locate(id, d1, boundingBox)\
6736\
6737 local function checkBB(boundingBox)\
6738 if boundingBox then\
6739 local heading = TL2.headings[TL2.getState().heading]\
6740 local x = TL2.getState().x + heading.xd\
6741 local y = TL2.getState().y + heading.yd\
6742 if x < boundingBox.ax or x > boundingBox.bx or\
6743 y < boundingBox.ay or y > boundingBox.by then\
6744 return true\
6745 end\
6746 end\
6747 return false\
6748 end\
6749\
6750 if checkBB(boundingBox) then\
6751 TL2.turnAround()\
6752 end\
6753\
6754 TL2.forward()\
6755\
6756 local d2 = TLC.getDistance(id)\
6757 if not d2 then return end\
6758 if d2 == 1 then return d2 end\
6759\
6760 if d2 > d1 then\
6761 TL2.turnAround()\
6762 end\
6763\
6764 d1 = d2\
6765\
6766 while true do\
6767 if checkBB(boundingBox) then\
6768 break\
6769 end\
6770 TL2.forward()\
6771\
6772 d2 = TLC.getDistance(id)\
6773 if not d2 then return end\
6774 if d2 == 1 then return d2 end\
6775\
6776 if d2 > d1 then\
6777 TL2.back()\
6778 return d1\
6779 end\
6780 d1 = d2\
6781 end\
6782 return d2\
6783end\
6784\
6785function TLC.tracker(id, d1, nozcheck, boundingBox)\
6786\
6787 d1 = locate(id, d1, boundingBox)\
6788 if not d1 then return end\
6789\
6790 TL2.turnRight()\
6791\
6792 d1 = locate(id, d1, boundingBox)\
6793 if not d1 then return end\
6794\
6795 if math.floor(d1) == d1 then\
6796 local z = d1\
6797\
6798 if not nozcheck then\
6799 TL2.up()\
6800\
6801 d2 = TLC.getDistance(id)\
6802 if not d2 then return end\
6803\
6804 TL2.down()\
6805\
6806 z = TL2.getState().z + math.floor(d1)\
6807 if d1 < d2 then\
6808 z = TL2.getState().z - math.floor(d1)\
6809 end\
6810 end\
6811\
6812 return { x = TL2.getState().x, y = TL2.getState().y, z = z }\
6813 end\
6814end\
6815\
6816end\
6817",},[5]={["name"]="miningStatus.lua",["contents"]="os.loadAPI('apis.lua')\
6818\
6819Peripheral.wrap('wireless_modem')\
6820\
6821local bossId\
6822local state\
6823local status\
6824\
6825Logger.disable()\
6826\
6827local terminal = UI.term\
6828\
6829if Peripheral.isPresent('openperipheral_glassesbridge') then\
6830 terminal = UI.Glasses({\
6831 --height = 30,\
6832 --width = 40,\
6833 textScale = .5,\
6834 })\
6835elseif Peripheral.isPresent('monitor') then\
6836 terminal = UI.Device({\
6837 deviceType = 'monitor',\
6838 textScale = .5\
6839 })\
6840end\
6841\
6842local window = UI.Window({ parent = terminal })\
6843window:clear()\
6844\
6845statusPage = UI.Page({\
6846 parent = window,\
6847 titleBar = UI.TitleBar({\
6848 title = 'Mining Status'\
6849 }),\
6850 statusInfo = UI.Window({\
6851 totalHolesProgressBar = UI.ProgressBar({\
6852 y = 7,\
6853 x = 2,\
6854 width = window.width - 2\
6855 }),\
6856 chunkHolesProgressBar = UI.ProgressBar({\
6857 y = 10,\
6858 x = 2,\
6859 width = window.width - 2\
6860 }),\
6861 }),\
6862 statusBar = UI.StatusBar({\
6863 backgroundColor = colors.blue\
6864 })\
6865})\
6866\
6867--statusPage.titleBar:draw()\
6868\
6869if terminal.height > 17 then\
6870 statusPage:add({\
6871 logTitleBar = UI.TitleBar({\
6872 title = 'Log Messages',\
6873 y = 13\
6874 }),\
6875 scrollingText = UI.ScrollingText({\
6876 backgroundColor = colors.green,\
6877 y = 14,\
6878 height = terminal.height - 13\
6879 })\
6880 })\
6881\
6882 Message.addHandler('log', function(h, id, msg)\
6883 if bossId and id == bossId then\
6884 statusPage.scrollingText:write(os.clock() .. ' ' .. msg.contents)\
6885 end\
6886 end)\
6887\
6888 Message.addHandler('logClient', function(h, id)\
6889 Message.send(id, 'logServer')\
6890 end)\
6891end\
6892\
6893function statusPage.statusBar:draw()\
6894 if state then\
6895 self:setValue('status', state.status)\
6896 end\
6897 UI.StatusBar.draw(self)\
6898end\
6899\
6900function statusPage.statusInfo:draw()\
6901 if not state or not status then\
6902 return\
6903 end\
6904 if state.chunks ~= -1 then\
6905 local totalHoles = state.chunks * 52 -- roughly\
6906 local percentDone = state.holes * 100 / totalHoles\
6907 self.totalHolesProgressBar:setProgress(percentDone)\
6908 self:centeredWrite(8, string.format('Total: %d of ~%d holes (%d%%)',\
6909 state.holes, totalHoles, percentDone))\
6910 else\
6911 self:centeredWrite(8, string.format('Total holes: %d', state.holes))\
6912 self.totalHolesProgressBar:setProgress(Util.random(100))\
6913 end\
6914\
6915 local currentChunk = math.pow(state.diameter-2, 2) + state.chunkIndex + 1\
6916 if state.diameter == 1 then\
6917 currentChunk = 1\
6918 end\
6919 if state.chunks == -1 then\
6920 self:write(2, 3, string.format('Chunk %d', currentChunk))\
6921 else\
6922 self:write(2, 3, string.format('Chunk %d of %d', currentChunk, state.chunks))\
6923 end\
6924 self:write(2, 4, string.format('Miners %d', status.count))\
6925\
6926 if state.depth ~= 9000 then\
6927 self:write(2, 5, string.format('Depth %d', state.depth))\
6928 end\
6929\
6930 local totalHoles = 52 -- roughly\
6931 local holesRemaining = #state.locations\
6932 if holesRemaining > totalHoles then\
6933 totalHoles = holesRemaining\
6934 end\
6935 local percentDone = 100 - (holesRemaining * 100 / totalHoles)\
6936 self.chunkHolesProgressBar:setProgress(percentDone)\
6937 self:centeredWrite(11, string.format('Current Chunk: %d%%', percentDone))\
6938\
6939 self.chunkHolesProgressBar:draw()\
6940 self.totalHolesProgressBar:draw()\
6941end\
6942\
6943Message.addHandler('miningStatus', function(h, id, msg)\
6944 bossId = id\
6945 state = msg.contents.state\
6946 status = msg.contents.status\
6947 statusPage.statusInfo:draw()\
6948 statusPage.statusBar:draw()\
6949end)\
6950\
6951Event.addHandler('heartbeat', function()\
6952 Message.send(bossId, 'getMiningStatus')\
6953end)\
6954\
6955Event.addHandler('char', function()\
6956 Event.exitPullEvents()\
6957end)\
6958\
6959statusPage:draw()\
6960Message.broadcast('getMiningStatus')\
6961\
6962Event.heartbeat(5)\
6963\
6964window:clear()\
6965",},[6]={["name"]="tracker",["contents"]="os.loadAPI('apis.lua')\
6966\
6967Peripheral.wrap('wireless_modem')\
6968\
6969TL2.setMode('destructive')\
6970TL2.setDigStrategy('cautious')\
6971\
6972local boundingBox = {\
6973 ax = -64,\
6974 ay = -64,\
6975 bx = 256,\
6976 by = 64 \
6977}\
6978\
6979function locateTurtle()\
6980 local info = Util.tryTimes(1, function()\
6981 Message.broadcast('alive')\
6982 _,id,_,distance = Message.waitForMessage('isAlive', 3)\
6983 if id and distance then\
6984 return { id = id, distance = distance }\
6985 end\
6986 end)\
6987\
6988 if not info then\
6989 print('Nothing found')\
6990 return\
6991 end\
6992 print('Turtle distance: ' .. info.distance)\
6993\
6994 local pt = TLC.tracker(info.id, info.distance) --, false, boundingBox)\
6995 if pt then\
6996 -- miners start out 1 below boss plane\
6997 TL2.back()\
6998 TLC.sendActions(info.id, {\
6999 { action = 'setLocation', args = pt },\
7000 { action = 'gotoZ', args = {\
7001 z = 0,\
7002 digStrategy = 'cautious',\
7003 mode = 'destructive' }\
7004 }\
7005 })\
7006 print('Found turtle - calling to surface')\
7007 print('depth is ' .. pt.z)\
7008 Logger.debug('x: ' .. pt.x .. ' z: ' .. pt.y)\
7009 print('x: ' .. pt.x .. ' z: ' .. pt.y)\
7010 for i = 1, 8 do\
7011 TL2.turnRight()\
7012 end\
7013 return true\
7014 end\
7015end\
7016\
7017-- quick check to see if any are within current range\
7018Message.broadcast('alive')\
7019local _,id = Message.waitForMessage('isAlive', 3)\
7020\
7021if not id then\
7022 --TL2.gotoZ(120)\
7023 --TL2.gotoZ(TL2.getState().z - 4)\
7024end\
7025\
7026Message.enableWirelessLogging()\
7027\
7028i = 0\
7029repeat\
7030 TL2.goto(i*16, 0)\
7031 if locateTurtle() then\
7032 break\
7033 end\
7034 i = i + 1\
7035until i >= 6\
7036\
7037TL2.gotoZ(0)\
7038TL2.goto(0, 0, 0, 0)\
7039",},}
7040
7041local directory = "swarm"
7042function setDirectory(directory)
7043 if not fs.exists(directory) then
7044 fs.makeDir(directory)
7045 end
7046
7047 shell.setDir(directory)
7048end
7049
7050function extractTableFile(ft, dir)
7051 --local file = io.open(dir .. '/' .. ft.name, "w")
7052 local file = io.open(ft.name, "w")
7053 if not file then
7054 error('failed to extract ', ft.name)
7055 end
7056 file:write(textutils.unserialize(ft.contents))
7057 file:close()
7058end
7059
7060--print('Extracting files to : ' .. directory)
7061
7062--setDirectory(directory)
7063for _,ft in pairs(fts) do
7064 print('extracting: ' .. ft.name)
7065 extractTableFile(ft, directory)
7066end
7067
7068print('Extracted all files successfully')
7069
7070shell.run(directory .. 'setup')
7071--shell.run('/' .. directory .. '/' .. directory .. 'setup')
7072--shell.setDir('/')