· 4 years ago · Mar 06, 2021, 09:00 AM
1<Developer:Dan200>
2<Name:colors.lua>
3<Begin>
4--[[- The Colors API allows you to manipulate sets of colors.
5
6This is useful in conjunction with Bundled Cables from the RedPower mod, RedNet
7Cables from the MineFactory Reloaded mod, and colors on Advanced Computers and
8Advanced Monitors.
9
10For the non-American English version just replace @{colors} with @{colours} and
11it will use the other API, colours which is exactly the same, except in British
12English (e.g. @{colors.gray} is spelt @{colours.grey}).
13
14On basic terminals (such as the Computer and Monitor), all the colors are
15converted to grayscale. This means you can still use all 16 colors on the
16screen, but they will appear as the nearest tint of gray. You can check if a
17terminal supports color by using the function @{term.isColor}.
18
19Grayscale colors are calculated by taking the average of the three components,
20i.e. `(red + green + blue) / 3`.
21
22<table class="pretty-table">
23<thead>
24 <tr><th colspan="8" align="center">Default Colors</th></tr>
25 <tr>
26 <th rowspan="2" align="center">Color</th>
27 <th colspan="3" align="center">Value</th>
28 <th colspan="4" align="center">Default Palette Color</th>
29 </tr>
30 <tr>
31 <th>Dec</th><th>Hex</th><th>Paint/Blit</th>
32 <th>Preview</th><th>Hex</th><th>RGB</th><th>Grayscale</th>
33 </tr>
34</thead>
35<tbody>
36 <tr>
37 <td><code>colors.white</code></td>
38 <td align="right">1</td><td align="right">0x1</td><td align="right">0</td>
39 <td style="background:#F0F0F0"></td><td>#F0F0F0</td><td>240, 240, 240</td>
40 <td style="background:#F0F0F0"></td>
41 </tr>
42 <tr>
43 <td><code>colors.orange</code></td>
44 <td align="right">2</td><td align="right">0x2</td><td align="right">1</td>
45 <td style="background:#F2B233"></td><td>#F2B233</td><td>242, 178, 51</td>
46 <td style="background:#9D9D9D"></td>
47 </tr>
48 <tr>
49 <td><code>colors.magenta</code></td>
50 <td align="right">4</td><td align="right">0x4</td><td align="right">2</td>
51 <td style="background:#E57FD8"></td><td>#E57FD8</td><td>229, 127, 216</td>
52 <td style="background:#BEBEBE"></td>
53 </tr>
54 <tr>
55 <td><code>colors.lightBlue</code></td>
56 <td align="right">8</td><td align="right">0x8</td><td align="right">3</td>
57 <td style="background:#99B2F2"></td><td>#99B2F2</td><td>153, 178, 242</td>
58 <td style="background:#BFBFBF"></td>
59 </tr>
60 <tr>
61 <td><code>colors.yellow</code></td>
62 <td align="right">16</td><td align="right">0x10</td><td align="right">4</td>
63 <td style="background:#DEDE6C"></td><td>#DEDE6C</td><td>222, 222, 108</td>
64 <td style="background:#B8B8B8"></td>
65 </tr>
66 <tr>
67 <td><code>colors.lime</code></td>
68 <td align="right">32</td><td align="right">0x20</td><td align="right">5</td>
69 <td style="background:#7FCC19"></td><td>#7FCC19</td><td>127, 204, 25</td>
70 <td style="background:#767676"></td>
71 </tr>
72 <tr>
73 <td><code>colors.pink</code></td>
74 <td align="right">64</td><td align="right">0x40</td><td align="right">6</td>
75 <td style="background:#F2B2CC"></td><td>#F2B2CC</td><td>242, 178, 204</td>
76 <td style="background:#D0D0D0"></td>
77 </tr>
78 <tr>
79 <td><code>colors.gray</code></td>
80 <td align="right">128</td><td align="right">0x80</td><td align="right">7</td>
81 <td style="background:#4C4C4C"></td><td>#4C4C4C</td><td>76, 76, 76</td>
82 <td style="background:#4C4C4C"></td>
83 </tr>
84 <tr>
85 <td><code>colors.lightGray</code></td>
86 <td align="right">256</td><td align="right">0x100</td><td align="right">8</td>
87 <td style="background:#999999"></td><td>#999999</td><td>153, 153, 153</td>
88 <td style="background:#999999"></td>
89 </tr>
90 <tr>
91 <td><code>colors.cyan</code></td>
92 <td align="right">512</td><td align="right">0x200</td><td align="right">9</td>
93 <td style="background:#4C99B2"></td><td>#4C99B2</td><td>76, 153, 178</td>
94 <td style="background:#878787"></td>
95 </tr>
96 <tr>
97 <td><code>colors.purple</code></td>
98 <td align="right">1024</td><td align="right">0x400</td><td align="right">a</td>
99 <td style="background:#B266E5"></td><td>#B266E5</td><td>178, 102, 229</td>
100 <td style="background:#A9A9A9"></td>
101 </tr>
102 <tr>
103 <td><code>colors.blue</code></td>
104 <td align="right">2048</td><td align="right">0x800</td><td align="right">b</td>
105 <td style="background:#3366CC"></td><td>#3366CC</td><td>51, 102, 204</td>
106 <td style="background:#777777"></td>
107 </tr>
108 <tr>
109 <td><code>colors.brown</code></td>
110 <td align="right">4096</td><td align="right">0x1000</td><td align="right">c</td>
111 <td style="background:#7F664C"></td><td>#7F664C</td><td>127, 102, 76</td>
112 <td style="background:#656565"></td>
113 </tr>
114 <tr>
115 <td><code>colors.green</code></td>
116 <td align="right">8192</td><td align="right">0x2000</td><td align="right">d</td>
117 <td style="background:#57A64E"></td><td>#57A64E</td><td>87, 166, 78</td>
118 <td style="background:#6E6E6E"></td>
119 </tr>
120 <tr>
121 <td><code>colors.red</code></td>
122 <td align="right">16384</td><td align="right">0x4000</td><td align="right">e</td>
123 <td style="background:#CC4C4C"></td><td>#CC4C4C</td><td>204, 76, 76</td>
124 <td style="background:#767676"></td>
125 </tr>
126 <tr>
127 <td><code>colors.black</code></td>
128 <td align="right">32768</td><td align="right">0x8000</td><td align="right">f</td>
129 <td style="background:#111111"></td><td>#111111</td><td>17, 17, 17</td>
130 <td style="background:#111111"></td>
131 </tr>
132</tbody>
133</table>
134
135@see colours
136@module colors
137]]
138
139local expect = dofile("rom/modules/main/cc/expect.lua").expect
140
141--- White: Written as `0` in paint files and @{term.blit}, has a default
142-- terminal colour of #F0F0F0.
143white = 0x1
144
145--- Orange: Written as `1` in paint files and @{term.blit}, has a
146-- default terminal colour of #F2B233.
147orange = 0x2
148
149--- Magenta: Written as `2` in paint files and @{term.blit}, has a
150-- default terminal colour of #E57FD8.
151magenta = 0x4
152
153--- Light blue: Written as `3` in paint files and @{term.blit}, has a
154-- default terminal colour of #99B2F2.
155lightBlue = 0x8
156
157--- Yellow: Written as `4` in paint files and @{term.blit}, has a
158-- default terminal colour of #DEDE6C.
159yellow = 0x10
160
161--- Lime: Written as `5` in paint files and @{term.blit}, has a default
162-- terminal colour of #7FCC19.
163lime = 0x20
164
165--- Pink: Written as `6` in paint files and @{term.blit}, has a default
166-- terminal colour of #F2B2CC.
167pink = 0x40
168
169--- Gray: Written as `7` in paint files and @{term.blit}, has a default
170-- terminal colour of #4C4C4C.
171gray = 0x80
172
173--- Light gray: Written as `8` in paint files and @{term.blit}, has a
174-- default terminal colour of #999999.
175lightGray = 0x100
176
177--- Cyan: Written as `9` in paint files and @{term.blit}, has a default
178-- terminal colour of #4C99B2.
179cyan = 0x200
180
181--- Purple: Written as `a` in paint files and @{term.blit}, has a
182-- default terminal colour of #B266E5.
183purple = 0x400
184
185--- Blue: Written as `b` in paint files and @{term.blit}, has a default
186-- terminal colour of #3366CC.
187blue = 0x800
188
189--- Brown: Written as `c` in paint files and @{term.blit}, has a default
190-- terminal colour of #7F664C.
191brown = 0x1000
192
193--- Green: Written as `d` in paint files and @{term.blit}, has a default
194-- terminal colour of #57A64E.
195green = 0x2000
196
197--- Red: Written as `e` in paint files and @{term.blit}, has a default
198-- terminal colour of #CC4C4C.
199red = 0x4000
200
201--- Black: Written as `f` in paint files and @{term.blit}, has a default
202-- terminal colour of #111111.
203black = 0x8000
204
205--- Combines a set of colors (or sets of colors) into a larger set. Useful for
206-- Bundled Cables.
207--
208-- @tparam number ... The colors to combine.
209-- @treturn number The union of the color sets given in `...`
210-- @usage
211-- ```lua
212-- colors.combine(colors.white, colors.magenta, colours.lightBlue)
213-- -- => 13
214-- ```
215function combine(...)
216 local r = 0
217 for i = 1, select('#', ...) do
218 local c = select(i, ...)
219 expect(i, c, "number")
220 r = bit32.bor(r, c)
221 end
222 return r
223end
224
225--- Removes one or more colors (or sets of colors) from an initial set. Useful
226-- for Bundled Cables.
227--
228-- Each parameter beyond the first may be a single color or may be a set of
229-- colors (in the latter case, all colors in the set are removed from the
230-- original set).
231--
232-- @tparam number colors The color from which to subtract.
233-- @tparam number ... The colors to subtract.
234-- @treturn number The resulting color.
235-- @usage
236-- ```lua
237-- colours.subtract(colours.lime, colours.orange, colours.white)
238-- -- => 32
239-- ```
240function subtract(colors, ...)
241 expect(1, colors, "number")
242 local r = colors
243 for i = 1, select('#', ...) do
244 local c = select(i, ...)
245 expect(i + 1, c, "number")
246 r = bit32.band(r, bit32.bnot(c))
247 end
248 return r
249end
250
251--- Tests whether `color` is contained within `colors`. Useful for Bundled
252-- Cables.
253--
254-- @tparam number colors A color, or color set
255-- @tparam number color A color or set of colors that `colors` should contain.
256-- @treturn boolean If `colors` contains all colors within `color`.
257-- @usage
258-- ```lua
259-- colors.test(colors.combine(colors.white, colors.magenta, colours.lightBlue), colors.lightBlue)
260-- -- => true
261-- ```
262function test(colors, color)
263 expect(1, colors, "number")
264 expect(2, color, "number")
265 return bit32.band(colors, color) == color
266end
267
268--- Combine a three-colour RGB value into one hexadecimal representation.
269--
270-- @tparam number r The red channel, should be between 0 and 1.
271-- @tparam number g The red channel, should be between 0 and 1.
272-- @tparam number b The blue channel, should be between 0 and 1.
273-- @treturn number The combined hexadecimal colour.
274-- @usage
275-- ```lua
276-- colors.packRGB(0.7, 0.2, 0.6)
277-- -- => 0xb23399
278-- ```
279function packRGB(r, g, b)
280 expect(1, r, "number")
281 expect(2, g, "number")
282 expect(3, b, "number")
283 return
284 bit32.band(r * 255, 0xFF) * 2 ^ 16 +
285 bit32.band(g * 255, 0xFF) * 2 ^ 8 +
286 bit32.band(b * 255, 0xFF)
287end
288
289--- Separate a hexadecimal RGB colour into its three constituent channels.
290--
291-- @tparam number rgb The combined hexadecimal colour.
292-- @treturn number The red channel, will be between 0 and 1.
293-- @treturn number The red channel, will be between 0 and 1.
294-- @treturn number The blue channel, will be between 0 and 1.
295-- @usage
296-- ```lua
297-- colors.unpackRGB(0xb23399)
298-- -- => 0.7, 0.2, 0.6
299-- ```
300-- @see colors.packRGB
301function unpackRGB(rgb)
302 expect(1, rgb, "number")
303 return
304 bit32.band(bit32.rshift(rgb, 16), 0xFF) / 255,
305 bit32.band(bit32.rshift(rgb, 8), 0xFF) / 255,
306 bit32.band(rgb, 0xFF) / 255
307end
308
309--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
310-- arguments it receives.
311--
312-- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}.
313-- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}.
314-- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}.
315-- @tparam[2] number rgb The combined hexadecimal color, as an argument to @{colors.unpackRGB}.
316-- @treturn[1] number The combined hexadecimal colour, as returned by @{colors.packRGB}.
317-- @treturn[2] number The red channel, as returned by @{colors.unpackRGB}
318-- @treturn[2] number The green channel, as returned by @{colors.unpackRGB}
319-- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB}
320-- @deprecated Use @{packRGB} or @{unpackRGB} directly.
321-- @usage
322-- ```lua
323-- colors.rgb8(0xb23399)
324-- -- => 0.7, 0.2, 0.6
325-- ```
326-- @usage
327-- ```lua
328-- colors.rgb8(0.7, 0.2, 0.6)
329-- -- => 0xb23399
330-- ```
331function rgb8(r, g, b)
332 if g == nil and b == nil then
333 return unpackRGB(r)
334 else
335 return packRGB(r, g, b)
336 end
337end
338
339-- Colour to hex lookup table for toBlit
340local color_hex_lookup = {}
341for i = 0, 15 do
342 color_hex_lookup[2 ^ i] = string.format("%x", i)
343end
344
345--- Converts the given color to a paint/blit hex character (0-9a-f).
346--
347-- This is equivalent to converting floor(log_2(color)) to hexadecimal.
348--
349-- @tparam number color The color to convert.
350-- @treturn string The blit hex code of the color.
351function toBlit(color)
352 expect(1, color, "number")
353 return color_hex_lookup[color] or
354 string.format("%x", math.floor(math.log(color) / math.log(2)))
355end
356<End>
357<Name:colours.lua>
358<Begin>
359--- Colours for lovers of British spelling.
360--
361-- @see colors
362-- @module colours
363
364local colours = _ENV
365for k, v in pairs(colors) do
366 colours[k] = v
367end
368
369--- Grey. Written as `7` in paint files and @{term.blit}, has a default
370-- terminal colour of #4C4C4C.
371--
372-- @see colors.gray
373colours.grey = colors.gray
374colours.gray = nil --- @local
375
376--- Light grey. Written as `8` in paint files and @{term.blit}, has a
377-- default terminal colour of #999999.
378--
379-- @see colors.lightGray
380colours.lightGrey = colors.lightGray
381colours.lightGray = nil --- @local
382<End>
383<Name:disk.lua>
384<Begin>
385--- The Disk API allows you to interact with disk drives.
386--
387-- These functions can operate on locally attached or remote disk drives. To use
388-- a locally attached drive, specify ?side? as one of the six sides
389-- (e.g. `left`); to use a remote disk drive, specify its name as printed when
390-- enabling its modem (e.g. `drive_0`).
391--
392-- **Note:** All computers (except command computers), turtles and pocket
393-- computers can be placed within a disk drive to access it's internal storage
394-- like a disk.
395--
396-- @module disk
397
398local function isDrive(name)
399 if type(name) ~= "string" then
400 error("bad argument #1 (expected string, got " .. type(name) .. ")", 3)
401 end
402 return peripheral.getType(name) == "drive"
403end
404
405--- Checks whether any item at all is in the disk drive
406--
407-- @tparam string name The name of the disk drive.
408-- @treturn boolean If something is in the disk drive.
409-- @usage disk.isPresent("top")
410function isPresent(name)
411 if isDrive(name) then
412 return peripheral.call(name, "isDiskPresent")
413 end
414 return false
415end
416
417--- Get the label of the floppy disk, record, or other media within the given
418-- disk drive.
419--
420-- If there is a computer or turtle within the drive, this will set the label as
421-- read by `os.getComputerLabel`.
422--
423-- @tparam string name The name of the disk drive.
424-- @treturn string|nil The name of the current media, or `nil` if the drive is
425-- not present or empty.
426-- @see disk.setLabel
427function getLabel(name)
428 if isDrive(name) then
429 return peripheral.call(name, "getDiskLabel")
430 end
431 return nil
432end
433
434--- Set the label of the floppy disk or other media
435--
436-- @tparam string name The name of the disk drive.
437-- @tparam string|nil label The new label of the disk
438function setLabel(name, label)
439 if isDrive(name) then
440 peripheral.call(name, "setDiskLabel", label)
441 end
442end
443
444--- Check whether the current disk provides a mount.
445--
446-- This will return true for disks and computers, but not records.
447--
448-- @tparam string name The name of the disk drive.
449-- @treturn boolean If the disk is present and provides a mount.
450-- @see disk.getMountPath
451function hasData(name)
452 if isDrive(name) then
453 return peripheral.call(name, "hasData")
454 end
455 return false
456end
457
458--- Find the directory name on the local computer where the contents of the
459-- current floppy disk (or other mount) can be found.
460--
461-- @tparam string name The name of the disk drive.
462-- @treturn string|nil The mount's directory, or `nil` if the drive does not
463-- contain a floppy or computer.
464-- @see disk.hasData
465function getMountPath(name)
466 if isDrive(name) then
467 return peripheral.call(name, "getMountPath")
468 end
469 return nil
470end
471
472--- Whether the current disk is a [music disk][disk] as opposed to a floppy disk
473-- or other item.
474--
475-- If this returns true, you will can @{disk.playAudio|play} the record.
476--
477-- [disk]: https://minecraft.gamepedia.com/Music_Disc
478--
479-- @tparam string name The name of the disk drive.
480-- @treturn boolean If the disk is present and has audio saved on it.
481function hasAudio(name)
482 if isDrive(name) then
483 return peripheral.call(name, "hasAudio")
484 end
485 return false
486end
487
488--- Get the title of the audio track from the music record in the drive.
489--
490-- This generally returns the same as @{disk.getLabel} for records.
491--
492-- @tparam string name The name of the disk drive.
493-- @treturn string|false|nil The track title, @{false} if there is not a music
494-- record in the drive or `nil` if no drive is present.
495function getAudioTitle(name)
496 if isDrive(name) then
497 return peripheral.call(name, "getAudioTitle")
498 end
499 return nil
500end
501
502--- Starts playing the music record in the drive.
503--
504-- If any record is already playing on any disk drive, it stops before the
505-- target drive starts playing. The record stops when it reaches the end of the
506-- track, when it is removed from the drive, when @{disk.stopAudio} is called, or
507-- when another record is started.
508--
509-- @tparam string name The name of the disk drive.
510-- @usage disk.playAudio("bottom")
511function playAudio(name)
512 if isDrive(name) then
513 peripheral.call(name, "playAudio")
514 end
515end
516
517--- Stops the music record in the drive from playing, if it was started with
518-- @{disk.playAudio}.
519--
520-- @tparam string name The name o the disk drive.
521function stopAudio(name)
522 if not name then
523 for _, sName in ipairs(peripheral.getNames()) do
524 stopAudio(sName)
525 end
526 else
527 if isDrive(name) then
528 peripheral.call(name, "stopAudio")
529 end
530 end
531end
532
533--- Ejects any item currently in the drive, spilling it into the world as a loose item.
534--
535-- @tparam string name The name of the disk drive.
536-- @usage disk.eject("bottom")
537function eject(name)
538 if isDrive(name) then
539 peripheral.call(name, "ejectDisk")
540 end
541end
542
543--- Returns a number which uniquely identifies the disk in the drive.
544--
545-- Note, unlike @{disk.getLabel}, this does not return anything for other media,
546-- such as computers or turtles.
547--
548-- @tparam string name The name of the disk drive.
549-- @treturn string|nil The disk ID, or `nil` if the drive does not contain a floppy disk.
550function getID(name)
551 if isDrive(name) then
552 return peripheral.call(name, "getDiskID")
553 end
554 return nil
555end
556
557--- Inserts a disk into the drive.
558--
559-- This function takes one of the following path formats:
560-- * A path to a directory to mount as a disk.
561-- * A path to an audio file to insert a music disc.
562-- * A number to mount a disk with an ID.
563-- * A path in the form "treasure:<name>/<program>" to mount a treasure disk if available.
564--
565-- @tparam string name The name of the disk drive.
566-- @tparam string path The path to mount as described above.
567function insertDisk(name, path)
568 if isDrive(name) then
569 peripheral.call(name, "insertDisk", path)
570 end
571end
572<End>
573<Name:gps.lua>
574<Begin>
575--- The GPS API provides a method for turtles and computers to retrieve their
576-- own locations.
577--
578-- It broadcasts a PING message over @{rednet} and wait for responses. In order
579-- for this system to work, there must be at least 4 computers used as gps hosts
580-- which will respond and allow trilateration. Three of these hosts should be in
581-- a plane, and the fourth should be either above or below the other three. The
582-- three in a plane should not be in a line with each other. You can set up
583-- hosts using the gps program.
584--
585-- **Note**: When entering in the coordinates for the host you need to put in
586-- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all
587-- rednet distances are measured from the block the computer is in.
588--
589-- Also note that you may choose which axes x, y, or z refers to - so long as
590-- your systems have the same definition as any GPS servers that're in range, it
591-- works just the same. For example, you might build a GPS cluster according to
592-- [this tutorial][1], using z to account for height, or you might use y to
593-- account for height in the way that Minecraft's debug screen displays.
594--
595-- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/
596--
597-- @module gps
598
599local expect = dofile("rom/modules/main/cc/expect.lua").expect
600
601--- The channel which GPS requests and responses are broadcast on.
602CHANNEL_GPS = 65534
603
604local function trilaterate(A, B, C)
605 local a2b = B.vPosition - A.vPosition
606 local a2c = C.vPosition - A.vPosition
607
608 if math.abs(a2b:normalize():dot(a2c:normalize())) > 0.999 then
609 return nil
610 end
611
612 local d = a2b:length()
613 local ex = a2b:normalize( )
614 local i = ex:dot(a2c)
615 local ey = (a2c - ex * i):normalize()
616 local j = ey:dot(a2c)
617 local ez = ex:cross(ey)
618
619 local r1 = A.nDistance
620 local r2 = B.nDistance
621 local r3 = C.nDistance
622
623 local x = (r1 * r1 - r2 * r2 + d * d) / (2 * d)
624 local y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j)
625
626 local result = A.vPosition + ex * x + ey * y
627
628 local zSquared = r1 * r1 - x * x - y * y
629 if zSquared > 0 then
630 local z = math.sqrt(zSquared)
631 local result1 = result + ez * z
632 local result2 = result - ez * z
633
634 local rounded1, rounded2 = result1:round(0.01), result2:round(0.01)
635 if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
636 return rounded1, rounded2
637 else
638 return rounded1
639 end
640 end
641 return result:round(0.01)
642
643end
644
645local function narrow(p1, p2, fix)
646 local dist1 = math.abs((p1 - fix.vPosition):length() - fix.nDistance)
647 local dist2 = math.abs((p2 - fix.vPosition):length() - fix.nDistance)
648
649 if math.abs(dist1 - dist2) < 0.01 then
650 return p1, p2
651 elseif dist1 < dist2 then
652 return p1:round(0.01)
653 else
654 return p2:round(0.01)
655 end
656end
657
658--- Tries to retrieve the computer or turtles own location.
659--
660-- @tparam[opt] number timeout The maximum time taken to establish our
661-- position. Defaults to 2 seconds if not specified.
662-- @tparam[opt] boolean debug Print debugging messages
663-- @treturn[1] number This computer's `x` position.
664-- @treturn[1] number This computer's `y` position.
665-- @treturn[1] number This computer's `z` position.
666-- @treturn[2] nil If the position could not be established.
667function locate(_nTimeout, _bDebug)
668 expect(1, _nTimeout, "number", "nil")
669 expect(2, _bDebug, "boolean", "nil")
670 -- Let command computers use their magic fourth-wall-breaking special abilities
671 if commands then
672 return commands.getBlockPosition()
673 end
674
675 -- Find a modem
676 local sModemSide = nil
677 for _, sSide in ipairs(rs.getSides()) do
678 if peripheral.getType(sSide) == "modem" and peripheral.call(sSide, "isWireless") then
679 sModemSide = sSide
680 break
681 end
682 end
683
684 if sModemSide == nil then
685 if _bDebug then
686 print("No wireless modem attached")
687 end
688 return nil
689 end
690
691 if _bDebug then
692 print("Finding position...")
693 end
694
695 -- Open GPS channel to listen for ping responses
696 local modem = peripheral.wrap(sModemSide)
697 local bCloseChannel = false
698 if not modem.isOpen(CHANNEL_GPS) then
699 modem.open(CHANNEL_GPS)
700 bCloseChannel = true
701 end
702
703 -- Send a ping to listening GPS hosts
704 modem.transmit(CHANNEL_GPS, CHANNEL_GPS, "PING")
705
706 -- Wait for the responses
707 local tFixes = {}
708 local pos1, pos2 = nil, nil
709 local timeout = os.startTimer(_nTimeout or 2)
710 while true do
711 local e, p1, p2, p3, p4, p5 = os.pullEvent()
712 if e == "modem_message" then
713 -- We received a reply from a modem
714 local sSide, sChannel, sReplyChannel, tMessage, nDistance = p1, p2, p3, p4, p5
715 if sSide == sModemSide and sChannel == CHANNEL_GPS and sReplyChannel == CHANNEL_GPS and nDistance then
716 -- Received the correct message from the correct modem: use it to determine position
717 if type(tMessage) == "table" and #tMessage == 3 and tonumber(tMessage[1]) and tonumber(tMessage[2]) and tonumber(tMessage[3]) then
718 local tFix = { vPosition = vector.new(tMessage[1], tMessage[2], tMessage[3]), nDistance = nDistance }
719 if _bDebug then
720 print(tFix.nDistance .. " metres from " .. tostring(tFix.vPosition))
721 end
722 if tFix.nDistance == 0 then
723 pos1, pos2 = tFix.vPosition, nil
724 else
725 table.insert(tFixes, tFix)
726 if #tFixes >= 3 then
727 if not pos1 then
728 pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[#tFixes])
729 else
730 pos1, pos2 = narrow(pos1, pos2, tFixes[#tFixes])
731 end
732 end
733 end
734 if pos1 and not pos2 then
735 break
736 end
737 end
738 end
739
740 elseif e == "timer" then
741 -- We received a timeout
742 local timer = p1
743 if timer == timeout then
744 break
745 end
746
747 end
748 end
749
750 -- Close the channel, if we opened one
751 if bCloseChannel then
752 modem.close(CHANNEL_GPS)
753 end
754
755 -- Return the response
756 if pos1 and pos2 then
757 if _bDebug then
758 print("Ambiguous position")
759 print("Could be " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z .. " or " .. pos2.x .. "," .. pos2.y .. "," .. pos2.z)
760 end
761 return nil
762 elseif pos1 then
763 if _bDebug then
764 print("Position is " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z)
765 end
766 return pos1.x, pos1.y, pos1.z
767 else
768 if _bDebug then
769 print("Could not determine position")
770 end
771 return nil
772 end
773end
774<End>
775<Name:help.lua>
776<Begin>
777--- Provides an API to read help files.
778--
779-- @module help
780
781local expect = dofile("rom/modules/main/cc/expect.lua").expect
782
783local sPath = "/rom/help"
784
785--- Returns a colon-separated list of directories where help files are searched
786-- for. All directories are absolute.
787--
788-- @treturn string The current help search path, separated by colons.
789-- @see help.setPath
790function path()
791 return sPath
792end
793
794--- Sets the colon-seperated list of directories where help files are searched
795-- for to `newPath`
796--
797-- @tparam string newPath The new path to use.
798-- @usage help.setPath( "/disk/help/" )
799-- @usage help.setPath( help.path() .. ":/myfolder/help/" )
800-- @see help.path
801function setPath(_sPath)
802 expect(1, _sPath, "string")
803 sPath = _sPath
804end
805
806--- Returns the location of the help file for the given topic.
807--
808-- @tparam string topic The topic to find
809-- @treturn string|nil The path to the given topic's help file, or `nil` if it
810-- cannot be found.
811-- @usage help.lookup("disk")
812function lookup(_sTopic)
813 expect(1, _sTopic, "string")
814 -- Look on the path variable
815 for sPath in string.gmatch(sPath, "[^:]+") do
816 sPath = fs.combine(sPath, _sTopic)
817 if fs.exists(sPath) and not fs.isDir(sPath) then
818 return sPath
819 elseif fs.exists(sPath .. ".txt") and not fs.isDir(sPath .. ".txt") then
820 return sPath .. ".txt"
821 end
822 end
823
824 -- Not found
825 return nil
826end
827
828--- Returns a list of topics that can be looked up and/or displayed.
829--
830-- @treturn table A list of topics in alphabetical order.
831-- @usage help.topics()
832function topics()
833 -- Add index
834 local tItems = {
835 ["index"] = true,
836 }
837
838 -- Add topics from the path
839 for sPath in string.gmatch(sPath, "[^:]+") do
840 if fs.isDir(sPath) then
841 local tList = fs.list(sPath)
842 for _, sFile in pairs(tList) do
843 if string.sub(sFile, 1, 1) ~= "." then
844 if not fs.isDir(fs.combine(sPath, sFile)) then
845 if #sFile > 4 and sFile:sub(-4) == ".txt" then
846 sFile = sFile:sub(1, -5)
847 end
848 tItems[sFile] = true
849 end
850 end
851 end
852 end
853 end
854
855 -- Sort and return
856 local tItemList = {}
857 for sItem in pairs(tItems) do
858 table.insert(tItemList, sItem)
859 end
860 table.sort(tItemList)
861 return tItemList
862end
863
864--- Returns a list of topic endings that match the prefix. Can be used with
865-- `read` to allow input of a help topic.
866--
867-- @tparam string prefix The prefix to match
868-- @treturn table A list of matching topics.
869function completeTopic(sText)
870 expect(1, sText, "string")
871 local tTopics = topics()
872 local tResults = {}
873 for n = 1, #tTopics do
874 local sTopic = tTopics[n]
875 if #sTopic > #sText and string.sub(sTopic, 1, #sText) == sText then
876 table.insert(tResults, string.sub(sTopic, #sText + 1))
877 end
878 end
879 return tResults
880end
881<End>
882<Name:io.lua>
883<Begin>
884--- Emulates Lua's standard [io library][io].
885--
886-- [io]: https://www.lua.org/manual/5.1/manual.html#5.7
887--
888-- @module io
889
890local expect, type_of = dofile("rom/modules/main/cc/expect.lua").expect, _G.type
891
892--- If we return nil then close the file, as we've reached the end.
893-- We use this weird wrapper function as we wish to preserve the varargs
894local function checkResult(handle, ...)
895 if ... == nil and handle._autoclose and not handle._closed then handle:close() end
896 return ...
897end
898
899--- A file handle which can be read or written to.
900--
901-- @type Handle
902local handleMetatable
903handleMetatable = {
904 __name = "FILE*",
905 __tostring = function(self)
906 if self._closed then
907 return "file (closed)"
908 else
909 local hash = tostring(self._handle):match("table: (%x+)")
910 return "file (" .. hash .. ")"
911 end
912 end,
913
914 __index = {
915 --- Close this file handle, freeing any resources it uses.
916 --
917 -- @treturn[1] true If this handle was successfully closed.
918 -- @treturn[2] nil If this file handle could not be closed.
919 -- @treturn[2] string The reason it could not be closed.
920 -- @throws If this handle was already closed.
921 close = function(self)
922 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
923 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
924 end
925 if self._closed then error("attempt to use a closed file", 2) end
926
927 local handle = self._handle
928 if handle.close then
929 self._closed = true
930 handle.close()
931 return true
932 else
933 return nil, "attempt to close standard stream"
934 end
935 end,
936
937 --- Flush any buffered output, forcing it to be written to the file
938 --
939 -- @throws If the handle has been closed
940 flush = function(self)
941 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
942 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
943 end
944 if self._closed then error("attempt to use a closed file", 2) end
945
946 local handle = self._handle
947 if handle.flush then handle.flush() end
948 return true
949 end,
950
951 lines = function(self, ...)
952 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
953 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
954 end
955 if self._closed then error("attempt to use a closed file", 2) end
956
957 local handle = self._handle
958 if not handle.read then return nil, "file is not readable" end
959
960 local args = table.pack(...)
961 return function()
962 if self._closed then error("file is already closed", 2) end
963 return checkResult(self, self:read(table.unpack(args, 1, args.n)))
964 end
965 end,
966
967 read = function(self, ...)
968 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
969 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
970 end
971 if self._closed then error("attempt to use a closed file", 2) end
972
973 local handle = self._handle
974 if not handle.read and not handle.readLine then return nil, "Not opened for reading" end
975
976 local n = select("#", ...)
977 local output = {}
978 for i = 1, n do
979 local arg = select(i, ...)
980 local res
981 if type_of(arg) == "number" then
982 if handle.read then res = handle.read(arg) end
983 elseif type_of(arg) == "string" then
984 local format = arg:gsub("^%*", ""):sub(1, 1)
985
986 if format == "l" then
987 if handle.readLine then res = handle.readLine() end
988 elseif format == "L" and handle.readLine then
989 if handle.readLine then res = handle.readLine(true) end
990 elseif format == "a" then
991 if handle.readAll then res = handle.readAll() or "" end
992 elseif format == "n" then
993 res = nil -- Skip this format as we can't really handle it
994 else
995 error("bad argument #" .. i .. " (invalid format)", 2)
996 end
997 else
998 error("bad argument #" .. i .. " (expected string, got " .. type_of(arg) .. ")", 2)
999 end
1000
1001 output[i] = res
1002 if not res then break end
1003 end
1004
1005 -- Default to "l" if possible
1006 if n == 0 and handle.readLine then return handle.readLine() end
1007 return table.unpack(output, 1, n)
1008 end,
1009
1010 seek = function(self, whence, offset)
1011 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
1012 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
1013 end
1014 if self._closed then error("attempt to use a closed file", 2) end
1015
1016 local handle = self._handle
1017 if not handle.seek then return nil, "file is not seekable" end
1018
1019 -- It's a tail call, so error positions are preserved
1020 return handle.seek(whence, offset)
1021 end,
1022
1023 setvbuf = function(self, mode, size) end,
1024
1025 --- Write one or more values to the file
1026 --
1027 -- @tparam string|number ... The values to write.
1028 -- @treturn[1] Handle The current file, allowing chained calls.
1029 -- @treturn[2] nil If the file could not be written to.
1030 -- @treturn[2] string The error message which occurred while writing.
1031 write = function(self, ...)
1032 if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
1033 error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
1034 end
1035 if self._closed then error("attempt to use a closed file", 2) end
1036
1037 local handle = self._handle
1038 if not handle.write then return nil, "file is not writable" end
1039
1040 for i = 1, select("#", ...) do
1041 local arg = select(i, ...)
1042 expect(i, arg, "string", "number")
1043 handle.write(arg)
1044 end
1045 return self
1046 end,
1047 },
1048}
1049
1050local defaultInput = setmetatable({
1051 _handle = { readLine = _G.read },
1052}, handleMetatable)
1053
1054local defaultOutput = setmetatable({
1055 _handle = { write = _G.write },
1056}, handleMetatable)
1057
1058local defaultError = setmetatable({
1059 _handle = {
1060 write = function(...)
1061 local oldColour
1062 if term.isColour() then
1063 oldColour = term.getTextColour()
1064 term.setTextColour(colors.red)
1065 end
1066 _G.write(...)
1067 if term.isColour() then term.setTextColour(oldColour) end
1068 end,
1069 },
1070}, handleMetatable)
1071
1072local currentInput = defaultInput
1073local currentOutput = defaultOutput
1074
1075--- A file handle representing the "standard input". Reading from this
1076-- file will prompt the user for input.
1077stdin = defaultInput
1078
1079--- A file handle representing the "standard output". Writing to this
1080-- file will display the written text to the screen.
1081stdout = defaultOutput
1082
1083--- A file handle representing the "standard error" stream.
1084--
1085-- One may use this to display error messages, writing to it will display
1086-- them on the terminal.
1087stderr = defaultError
1088
1089--- Closes the provided file handle.
1090--
1091-- @tparam[opt] Handle file The file handle to close, defaults to the
1092-- current output file.
1093--
1094-- @see Handle:close
1095-- @see io.output
1096function close(file)
1097 if file == nil then return currentOutput:close() end
1098
1099 if type_of(file) ~= "table" or getmetatable(file) ~= handleMetatable then
1100 error("bad argument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
1101 end
1102 return file:close()
1103end
1104
1105--- Flushes the current output file.
1106--
1107-- @see Handle:flush
1108-- @see io.output
1109function flush()
1110 return currentOutput:flush()
1111end
1112
1113--- Get or set the current input file.
1114--
1115-- @tparam[opt] Handle|string file The new input file, either as a file path or pre-existing handle.
1116-- @treturn Handle The current input file.
1117-- @throws If the provided filename cannot be opened for reading.
1118function input(file)
1119 if type_of(file) == "string" then
1120 local res, err = open(file, "rb")
1121 if not res then error(err, 2) end
1122 currentInput = res
1123 elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
1124 currentInput = file
1125 elseif file ~= nil then
1126 error("bad fileument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
1127 end
1128
1129 return currentInput
1130end
1131
1132--- Opens the given file name in read mode and returns an iterator that,
1133-- each time it is called, returns a new line from the file.
1134--
1135-- This can be used in a for loop to iterate over all lines of a file:
1136--
1137-- ```lua
1138-- for line in io.lines(filename) do print(line) end
1139-- ```
1140--
1141-- Once the end of the file has been reached, @{nil} will be
1142-- returned. The file is automatically closed.
1143--
1144-- If no file name is given, the @{io.input|current input} will be used
1145-- instead. In this case, the handle is not used.
1146--
1147-- @tparam[opt] string filename The name of the file to extract lines from
1148-- @param ... The argument to pass to @{Handle:read} for each line.
1149-- @treturn function():string|nil The line iterator.
1150-- @throws If the file cannot be opened for reading
1151--
1152-- @see Handle:lines
1153-- @see io.input
1154function lines(filename, ...)
1155 expect(1, filename, "string", "nil")
1156 if filename then
1157 local ok, err = open(filename, "rb")
1158 if not ok then error(err, 2) end
1159
1160 -- We set this magic flag to mark this file as being opened by io.lines and so should be
1161 -- closed automatically
1162 ok._autoclose = true
1163 return ok:lines(...)
1164 else
1165 return currentInput:lines(...)
1166 end
1167end
1168
1169--- Open a file with the given mode, either returning a new file handle
1170-- or @{nil}, plus an error message.
1171--
1172-- The `mode` string can be any of the following:
1173-- - **"r"**: Read mode
1174-- - **"w"**: Write mode
1175-- - **"a"**: Append mode
1176--
1177-- The mode may also have a `b` at the end, which opens the file in "binary
1178-- mode". This allows you to read binary files, as well as seek within a file.
1179--
1180-- @tparam string filename The name of the file to open.
1181-- @tparam[opt] string mode The mode to open the file with. This defaults to `rb`.
1182-- @treturn[1] Handle The opened file.
1183-- @treturn[2] nil In case of an error.
1184-- @treturn[2] string The reason the file could not be opened.
1185function open(filename, mode)
1186 expect(1, filename, "string")
1187 expect(2, mode, "string", "nil")
1188
1189 local sMode = mode and mode:gsub("%+", "") or "rb"
1190 local file, err = fs.open(filename, sMode)
1191 if not file then return nil, err end
1192
1193 return setmetatable({ _handle = file }, handleMetatable)
1194end
1195
1196--- Get or set the current output file.
1197--
1198-- @tparam[opt] Handle|string file The new output file, either as a file path or pre-existing handle.
1199-- @treturn Handle The current output file.
1200-- @throws If the provided filename cannot be opened for writing.
1201function output(file)
1202 if type_of(file) == "string" then
1203 local res, err = open(file, "wb")
1204 if not res then error(err, 2) end
1205 currentOutput = res
1206 elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
1207 currentOutput = file
1208 elseif file ~= nil then
1209 error("bad argument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
1210 end
1211
1212 return currentOutput
1213end
1214
1215--- Read from the currently opened input file.
1216--
1217-- This is equivalent to `io.input():read(...)`. See @{Handle:read|the
1218-- documentation} there for full details.
1219--
1220-- @tparam string ... The formats to read, defaulting to a whole line.
1221-- @treturn (string|nil)... The data read, or @{nil} if nothing can be read.
1222function read(...)
1223 return currentInput:read(...)
1224end
1225
1226--- Checks whether `handle` is a given file handle, and determine if it is open
1227-- or not.
1228--
1229-- @param obj The value to check
1230-- @treturn string|nil `"file"` if this is an open file, `"closed file"` if it
1231-- is a closed file handle, or `nil` if not a file handle.
1232function type(obj)
1233 if type_of(obj) == "table" and getmetatable(obj) == handleMetatable then
1234 if obj._closed then
1235 return "closed file"
1236 else
1237 return "file"
1238 end
1239 end
1240 return nil
1241end
1242
1243--- Write to the currently opened output file.
1244--
1245-- This is equivalent to `io.output():write(...)`. See @{Handle:write|the
1246-- documentation} there for full details.
1247--
1248-- @tparam string ... The strings to write
1249function write(...)
1250 return currentOutput:write(...)
1251end
1252<End>
1253<Name:keys.lua>
1254<Begin>
1255
1256-- Minecraft key code bindings
1257-- See http://www.minecraftwiki.net/wiki/Key_codes for more info
1258
1259local tKeys = {
1260 nil, "one", "two", "three", "four", -- 1
1261 "five", "six", "seven", "eight", "nine", -- 6
1262 "zero", "minus", "equals", "backspace","tab", -- 11
1263 "q", "w", "e", "r", "t", -- 16
1264 "y", "u", "i", "o", "p", -- 21
1265 "leftBracket","rightBracket","enter","leftCtrl","a", -- 26
1266 "s", "d", "f", "g", "h", -- 31
1267 "j", "k", "l", "semiColon","apostrophe", -- 36
1268 "grave", "leftShift","backslash","z", "x", -- 41
1269 "c", "v", "b", "n", "m", -- 46
1270 "comma", "period", "slash", "rightShift","multiply", -- 51
1271 "leftAlt", "space", "capsLock", "f1", "f2", -- 56
1272 "f3", "f4", "f5", "f6", "f7", -- 61
1273 "f8", "f9", "f10", "numLock", "scollLock", -- 66
1274 "numPad7", "numPad8", "numPad9", "numPadSubtract","numPad4", -- 71
1275 "numPad5", "numPad6", "numPadAdd","numPad1", "numPad2", -- 76
1276 "numPad3", "numPad0", "numPadDecimal",nil, nil, -- 81
1277 nil, "f11", "f12", nil, nil, -- 86
1278 nil, nil, nil, nil, nil, -- 91
1279 nil, nil, nil, nil, "f13", -- 96
1280 "f14", "f15", nil, nil, nil, -- 101
1281 nil, nil, nil, nil, nil, -- 106
1282 nil, "kana", nil, nil, nil, -- 111
1283 nil, nil, nil, nil, nil, -- 116
1284 "convert", nil, "noconvert",nil, "yen", -- 121
1285 nil, nil, nil, nil, nil, -- 126
1286 nil, nil, nil, nil, nil, -- 131
1287 nil, nil, nil, nil, nil, -- 136
1288 "numPadEquals",nil, nil, "cimcumflex","at", -- 141
1289 "colon", "underscore","kanji", "stop", "ax", -- 146
1290 nil, nil, nil, nil, nil, -- 151
1291 "numPadEnter","rightCtrl",nil, nil, nil, -- 156
1292 nil, nil, nil, nil, nil, -- 161
1293 nil, nil, nil, nil, nil, -- 166
1294 nil, nil, nil, nil, nil, -- 171
1295 nil, nil, nil, "numPadComma",nil, -- 176
1296 "numPadDivide",nil, nil, "rightAlt", nil, -- 181
1297 nil, nil, nil, nil, nil, -- 186
1298 nil, nil, nil, nil, nil, -- 191
1299 nil, "pause", nil, "home", "up", -- 196
1300 "pageUp", nil, "left", nil, "right", -- 201
1301 nil, "end", "down", "pageDown", "insert", -- 206
1302 "delete" -- 211
1303}
1304
1305local keys = _ENV
1306for nKey, sKey in pairs( tKeys ) do
1307 keys[sKey] = nKey
1308end
1309keys["return"] = keys.enter
1310
1311function getName( _nKey )
1312 if type( _nKey ) ~= "number" then
1313 error( "bad argument #1 (expected number, got " .. type( _nKey ) .. ")", 2 )
1314 end
1315 return tKeys[ _nKey ]
1316end
1317<End>
1318<Name:paintutils.lua>
1319<Begin>
1320--- An API for advanced systems which can draw pixels and lines, load and draw
1321-- image files. You can use the `colors` API for easier color manipulation. In
1322-- CraftOS-PC, this API can also be used in graphics mode.
1323--
1324-- @module paintutils
1325
1326local expect = dofile("rom/modules/main/cc/expect.lua").expect
1327
1328local function drawPixelInternal(xPos, yPos)
1329 if term.getGraphicsMode and term.getGraphicsMode() then
1330 term.setPixel(xPos - 1, yPos - 1, term.getBackgroundColor())
1331 else
1332 term.setCursorPos(xPos, yPos)
1333 term.write(" ")
1334 end
1335end
1336
1337local tColourLookup = {}
1338for n = 1, 16 do
1339 tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
1340end
1341
1342local function parseLine(tImageArg, sLine)
1343 local tLine = {}
1344 for x = 1, sLine:len() do
1345 tLine[x] = tColourLookup[string.byte(sLine, x, x)] or 0
1346 end
1347 table.insert(tImageArg, tLine)
1348end
1349
1350-- Sorts pairs of startX/startY/endX/endY such that the start is always the min
1351local function sortCoords(startX, startY, endX, endY)
1352 local minX, maxX, minY, maxY
1353
1354 if startX <= endX then
1355 minX, maxX = startX, endX
1356 else
1357 minX, maxX = endX, startX
1358 end
1359
1360 if startY <= endY then
1361 minY, maxY = startY, endY
1362 else
1363 minY, maxY = endY, startY
1364 end
1365
1366 return minX, maxX, minY, maxY
1367end
1368
1369--- Parses an image from a multi-line string
1370--
1371-- @tparam string image The string containing the raw-image data.
1372-- @treturn table The parsed image data, suitable for use with
1373-- @{paintutils.drawImage}.
1374function parseImage(image)
1375 expect(1, image, "string")
1376 local tImage = {}
1377 for sLine in (image .. "\n"):gmatch("(.-)\n") do
1378 parseLine(tImage, sLine)
1379 end
1380 return tImage
1381end
1382
1383--- Loads an image from a file.
1384--
1385-- You can create a file suitable for being loaded using the `paint` program.
1386--
1387-- @tparam string path The file to load.
1388--
1389-- @treturn table|nil The parsed image data, suitable for use with
1390-- @{paintutils.drawImage}, or `nil` if the file does not exist.
1391-- @usage Load an image and draw it.
1392--
1393-- local image = paintutils.loadImage("test-image.nfp")
1394-- paintutils.drawImage(image, term.getCursorPos())
1395function loadImage(path)
1396 expect(1, path, "string")
1397
1398 if fs.exists(path) then
1399 local file = io.open(path, "r")
1400 local sContent = file:read("*a")
1401 file:close()
1402 return parseImage(sContent)
1403 end
1404 return nil
1405end
1406
1407--- Draws a single pixel to the current term at the specified position.
1408--
1409-- Be warned, this may change the position of the cursor and the current
1410-- background colour. You should not expect either to be preserved.
1411--
1412-- @tparam number xPos The x position to draw at, where 1 is the far left.
1413-- @tparam number yPos The y position to draw at, where 1 is the very top.
1414-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
1415-- the current background colour if not specified.
1416function drawPixel(xPos, yPos, colour)
1417 expect(1, xPos, "number")
1418 expect(2, yPos, "number")
1419 expect(3, colour, "number", "nil")
1420
1421 if colour then
1422 term.setBackgroundColor(colour)
1423 end
1424 return drawPixelInternal(xPos, yPos)
1425end
1426
1427--- Draws a straight line from the start to end position.
1428--
1429-- Be warned, this may change the position of the cursor and the current
1430-- background colour. You should not expect either to be preserved.
1431--
1432-- @tparam number startX The starting x position of the line.
1433-- @tparam number startY The starting y position of the line.
1434-- @tparam number endX The end x position of the line.
1435-- @tparam number endY The end y position of the line.
1436-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
1437-- the current background colour if not specified.
1438-- @usage paintutils.drawLine(2, 3, 30, 7, colors.red)
1439function drawLine(startX, startY, endX, endY, colour)
1440 expect(1, startX, "number")
1441 expect(2, startY, "number")
1442 expect(3, endX, "number")
1443 expect(4, endY, "number")
1444 expect(5, colour, "number", "nil")
1445
1446 startX = math.floor(startX)
1447 startY = math.floor(startY)
1448 endX = math.floor(endX)
1449 endY = math.floor(endY)
1450
1451 if colour then
1452 term.setBackgroundColor(colour)
1453 end
1454 if startX == endX and startY == endY then
1455 drawPixelInternal(startX, startY)
1456 return
1457 end
1458
1459 local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
1460
1461 -- TODO: clip to screen rectangle?
1462
1463 local xDiff = maxX - minX
1464 local yDiff = maxY - minY
1465
1466 if xDiff > math.abs(yDiff) then
1467 local y = minY
1468 local dy = yDiff / xDiff
1469 for x = minX, maxX do
1470 drawPixelInternal(x, math.floor(y + 0.5))
1471 y = y + dy
1472 end
1473 else
1474 local x = minX
1475 local dx = xDiff / yDiff
1476 if maxY >= minY then
1477 for y = minY, maxY do
1478 drawPixelInternal(math.floor(x + 0.5), y)
1479 x = x + dx
1480 end
1481 else
1482 for y = minY, maxY, -1 do
1483 drawPixelInternal(math.floor(x + 0.5), y)
1484 x = x - dx
1485 end
1486 end
1487 end
1488end
1489
1490--- Draws the outline of a box on the current term from the specified start
1491-- position to the specified end position.
1492--
1493-- Be warned, this may change the position of the cursor and the current
1494-- background colour. You should not expect either to be preserved.
1495--
1496-- @tparam number startX The starting x position of the line.
1497-- @tparam number startY The starting y position of the line.
1498-- @tparam number endX The end x position of the line.
1499-- @tparam number endY The end y position of the line.
1500-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
1501-- the current background colour if not specified.
1502-- @usage paintutils.drawBox(2, 3, 30, 7, colors.red)
1503function drawBox(startX, startY, endX, endY, nColour)
1504 expect(1, startX, "number")
1505 expect(2, startY, "number")
1506 expect(3, endX, "number")
1507 expect(4, endY, "number")
1508 expect(5, nColour, "number", "nil")
1509
1510 startX = math.floor(startX)
1511 startY = math.floor(startY)
1512 endX = math.floor(endX)
1513 endY = math.floor(endY)
1514
1515 if nColour then
1516 term.setBackgroundColor(nColour) -- Maintain legacy behaviour
1517 else
1518 nColour = term.getBackgroundColour()
1519 end
1520
1521 if term.getGraphicsMode and term.getGraphicsMode() then
1522 local c, w, h = nColour or term.getBackgroundColor(), endX - startX, endY - startY
1523 term.drawPixels(startX, startY, c, w, 1)
1524 term.drawPixels(startX, startY, c, 1, h)
1525 term.drawPixels(endX, startY, c, w, 1)
1526 term.drawPixels(startX, endY, c, 1, h)
1527 else
1528 local colourHex = colours.toBlit(nColour)
1529
1530 if startX == endX and startY == endY then
1531 drawPixelInternal(startX, startY)
1532 return
1533 end
1534
1535 local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
1536 local width = maxX - minX + 1
1537
1538 for y = minY, maxY do
1539 if y == minY or y == maxY then
1540 term.setCursorPos(minX, y)
1541 term.blit((" "):rep(width), colourHex:rep(width), colourHex:rep(width))
1542 else
1543 term.setCursorPos(minX, y)
1544 term.blit(" ", colourHex, colourHex)
1545 term.setCursorPos(maxX, y)
1546 term.blit(" ", colourHex, colourHex)
1547 end
1548 end
1549 end
1550end
1551
1552--- Draws a filled box on the current term from the specified start position to
1553-- the specified end position.
1554--
1555-- Be warned, this may change the position of the cursor and the current
1556-- background colour. You should not expect either to be preserved.
1557--
1558-- @tparam number startX The starting x position of the line.
1559-- @tparam number startY The starting y position of the line.
1560-- @tparam number endX The end x position of the line.
1561-- @tparam number endY The end y position of the line.
1562-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
1563-- the current background colour if not specified.
1564-- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red)
1565function drawFilledBox(startX, startY, endX, endY, nColour)
1566 expect(1, startX, "number")
1567 expect(2, startY, "number")
1568 expect(3, endX, "number")
1569 expect(4, endY, "number")
1570 expect(5, nColour, "number", "nil")
1571
1572 startX = math.floor(startX)
1573 startY = math.floor(startY)
1574 endX = math.floor(endX)
1575 endY = math.floor(endY)
1576
1577 if nColour then
1578 term.setBackgroundColor(nColour) -- Maintain legacy behaviour
1579 else
1580 nColour = term.getBackgroundColour()
1581 end
1582
1583 if term.getGraphicsMode and term.getGraphicsMode() then
1584 local c = nColour or term.getBackgroundColor()
1585 term.drawPixels(startX, startY, c, endX - startX, endY - startY)
1586 else
1587 local colourHex = colours.toBlit(nColour)
1588
1589 if startX == endX and startY == endY then
1590 drawPixelInternal(startX, startY)
1591 return
1592 end
1593
1594 local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
1595 local width = maxX - minX + 1
1596
1597 for y = minY, maxY do
1598 term.setCursorPos(minX, y)
1599 term.blit((" "):rep(width), colourHex:rep(width), colourHex:rep(width))
1600 end
1601 end
1602end
1603
1604--- Draw an image loaded by @{paintutils.parseImage} or @{paintutils.loadImage}.
1605--
1606-- @tparam table image The parsed image data.
1607-- @tparam number xPos The x position to start drawing at.
1608-- @tparam number xPos The y position to start drawing at.
1609function drawImage(image, xPos, yPos)
1610 expect(1, image, "table")
1611 expect(2, xPos, "number")
1612 expect(3, yPos, "number")
1613 for y = 1, #image do
1614 local tLine = image[y]
1615 for x = 1, #tLine do
1616 if tLine[x] > 0 then
1617 term.setBackgroundColor(tLine[x])
1618 drawPixelInternal(x + xPos - 1, y + yPos - 1)
1619 end
1620 end
1621 end
1622end
1623<End>
1624<Name:parallel.lua>
1625<Begin>
1626--[[- Provides a simple implementation of multitasking.
1627
1628Functions are not actually executed simultaniously, but rather this API will
1629automatically switch between them whenever they yield (eg whenever they call
1630@{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
1631functions that call that, etc - basically, anything that causes the function
1632to "pause").
1633
1634Each function executed in "parallel" gets its own copy of the event queue,
1635and so "event consuming" functions (again, mostly anything that causes the
1636script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
1637etc) can safely be used in one without affecting the event queue accessed by
1638the other.
1639
1640@module parallel
1641]]
1642
1643local function create(...)
1644 local tFns = table.pack(...)
1645 local tCos = {}
1646 for i = 1, tFns.n, 1 do
1647 local fn = tFns[i]
1648 if type(fn) ~= "function" then
1649 error("bad argument #" .. i .. " (expected function, got " .. type(fn) .. ")", 3)
1650 end
1651
1652 tCos[i] = coroutine.create(fn)
1653 end
1654
1655 return tCos
1656end
1657
1658local function runUntilLimit(_routines, _limit)
1659 local count = #_routines
1660 local living = count
1661
1662 local tFilters = {}
1663 local eventData = { n = 0 }
1664 while true do
1665 for n = 1, count do
1666 local r = _routines[n]
1667 if r then
1668 if tFilters[r] == nil or tFilters[r] == eventData[1] or eventData[1] == "terminate" then
1669 local ok, param = coroutine.resume(r, table.unpack(eventData, 1, eventData.n))
1670 if not ok then
1671 error(param, 0)
1672 else
1673 tFilters[r] = param
1674 end
1675 if coroutine.status(r) == "dead" then
1676 _routines[n] = nil
1677 living = living - 1
1678 if living <= _limit then
1679 return n
1680 end
1681 end
1682 end
1683 end
1684 end
1685 for n = 1, count do
1686 local r = _routines[n]
1687 if r and coroutine.status(r) == "dead" then
1688 _routines[n] = nil
1689 living = living - 1
1690 if living <= _limit then
1691 return n
1692 end
1693 end
1694 end
1695 eventData = table.pack(os.pullEventRaw())
1696 end
1697end
1698
1699--[[- Switches between execution of the functions, until any of them
1700finishes. If any of the functions errors, the message is propagated upwards
1701from the @{parallel.waitForAny} call.
1702
1703@tparam function ... The functions this task will run
1704@usage Print a message every second until the `q` key is pressed.
1705
1706 local function tick()
1707 while true do
1708 os.sleep(1)
1709 print("Tick")
1710 end
1711 end
1712 local function wait_for_q()
1713 repeat
1714 local _, key = os.pullEvent("key")
1715 until key == keys.q
1716 print("Q was pressed!")
1717 end
1718
1719 parallel.waitForAny(tick, wait_for_q)
1720 print("Everything done!")
1721]]
1722function waitForAny(...)
1723 local routines = create(...)
1724 return runUntilLimit(routines, #routines - 1)
1725end
1726
1727--[[- Switches between execution of the functions, until all of them are
1728finished. If any of the functions errors, the message is propagated upwards
1729from the @{parallel.waitForAll} call.
1730
1731@tparam function ... The functions this task will run
1732@usage Start off two timers and wait for them both to run.
1733
1734 local function a()
1735 os.sleep(1)
1736 print("A is done")
1737 end
1738 local function b()
1739 os.sleep(3)
1740 print("B is done")
1741 end
1742
1743 parallel.waitForAll(a, b)
1744 print("Everything done!")
1745]]
1746function waitForAll(...)
1747 local routines = create(...)
1748 return runUntilLimit(routines, 0)
1749end
1750<End>
1751<Name:peripheral.lua>
1752<Begin>
1753--- The Peripheral API is for interacting with peripherals connected to the
1754-- computer, such as the Disk Drive, the Advanced Monitor and Monitor.
1755--
1756-- Each peripheral block has a name, either referring to the side the peripheral
1757-- can be found on, or a name on an adjacent wired network.
1758--
1759-- If the peripheral is next to the computer, its side is either `front`,
1760-- `back`, `left`, `right`, `top` or `bottom`. If the peripheral is attached by
1761-- a cable, its side will follow the format `type_id`, for example `printer_0`.
1762--
1763-- Peripheral functions are called *methods*, a term borrowed from Java.
1764--
1765-- @module peripheral
1766
1767local expect = dofile("rom/modules/main/cc/expect.lua").expect
1768
1769local native = peripheral
1770local sides = rs.getSides()
1771
1772--- Provides a list of all peripherals available.
1773--
1774-- If a device is located directly next to the system, then its name will be
1775-- listed as the side it is attached to. If a device is attached via a Wired
1776-- Modem, then it'll be reported according to its name on the wired network.
1777--
1778-- @treturn { string... } A list of the names of all attached peripherals.
1779function getNames()
1780 local results = {}
1781 for n = 1, #sides do
1782 local side = sides[n]
1783 if native.isPresent(side) then
1784 table.insert(results, side)
1785 if native.getType(side) == "modem" and not native.call(side, "isWireless") then
1786 local remote = native.call(side, "getNamesRemote")
1787 for _, name in ipairs(remote) do
1788 table.insert(results, name)
1789 end
1790 end
1791 end
1792 end
1793 return results
1794end
1795
1796--- Determines if a peripheral is present with the given name.
1797--
1798-- @tparam string name The side or network name that you want to check.
1799-- @treturn boolean If a peripheral is present with the given name.
1800-- @usage peripheral.isPresent("top")
1801-- @usage peripheral.isPresent("monitor_0")
1802function isPresent(name)
1803 expect(1, name, "string")
1804 if native.isPresent(name) then
1805 return true
1806 end
1807
1808 for n = 1, #sides do
1809 local side = sides[n]
1810 if native.getType(side) == "modem" and not native.call(side, "isWireless") and
1811 native.call(side, "isPresentRemote", name)
1812 then
1813 return true
1814 end
1815 end
1816 return false
1817end
1818
1819--- Get the type of a wrapped peripheral, or a peripheral with the given name.
1820--
1821-- @tparam string|table peripheral The name of the peripheral to find, or a
1822-- wrapped peripheral instance.
1823-- @treturn string|nil The peripheral's type, or `nil` if it is not present.
1824function getType(peripheral)
1825 expect(1, peripheral, "string", "table")
1826 if type(peripheral) == "string" then -- Peripheral name passed
1827 if native.isPresent(peripheral) then
1828 return native.getType(peripheral)
1829 end
1830 for n = 1, #sides do
1831 local side = sides[n]
1832 if native.getType(side) == "modem" and not native.call(side, "isWireless") and
1833 native.call(side, "isPresentRemote", peripheral)
1834 then
1835 return native.call(side, "getTypeRemote", peripheral)
1836 end
1837 end
1838 return nil
1839 else
1840 local mt = getmetatable(peripheral)
1841 if not mt or mt.__name ~= "peripheral" or type(mt.type) ~= "string" then
1842 error("bad argument #1 (table is not a peripheral)", 2)
1843 end
1844 return mt.type
1845 end
1846end
1847
1848--- Get all available methods for the peripheral with the given name.
1849--
1850-- @tparam string name The name of the peripheral to find.
1851-- @treturn { string... }|nil A list of methods provided by this peripheral, or `nil` if
1852-- it is not present.
1853function getMethods(name)
1854 expect(1, name, "string")
1855 if native.isPresent(name) then
1856 return native.getMethods(name)
1857 end
1858 for n = 1, #sides do
1859 local side = sides[n]
1860 if native.getType(side) == "modem" and not native.call(side, "isWireless") and
1861 native.call(side, "isPresentRemote", name)
1862 then
1863 return native.call(side, "getMethodsRemote", name)
1864 end
1865 end
1866 return nil
1867end
1868
1869--- Get the name of a peripheral wrapped with @{peripheral.wrap}.
1870--
1871-- @tparam table peripheral The peripheral to get the name of.
1872-- @treturn string The name of the given peripheral.
1873function getName(peripheral)
1874 expect(1, peripheral, "table")
1875 local mt = getmetatable(peripheral)
1876 if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
1877 error("bad argument #1 (table is not a peripheral)", 2)
1878 end
1879 return mt.name
1880end
1881
1882--- Call a method on the peripheral with the given name.
1883--
1884-- @tparam string name The name of the peripheral to invoke the method on.
1885-- @tparam string method The name of the method
1886-- @param ... Additional arguments to pass to the method
1887-- @return The return values of the peripheral method.
1888--
1889-- @usage Open the modem on the top of this computer.
1890--
1891-- peripheral.call("top", "open", 1)
1892function call(name, method, ...)
1893 expect(1, name, "string")
1894 expect(2, method, "string")
1895 if native.isPresent(name) then
1896 return native.call(name, method, ...)
1897 end
1898
1899 for n = 1, #sides do
1900 local side = sides[n]
1901 if native.getType(side) == "modem" and not native.call(side, "isWireless") and
1902 native.call(side, "isPresentRemote", name)
1903 then
1904 return native.call(side, "callRemote", name, method, ...)
1905 end
1906 end
1907 return nil
1908end
1909
1910--- Get a table containing functions pointing to the peripheral's methods, which
1911-- can then be called as if using @{peripheral.call}.
1912--
1913-- @tparam string name The name of the peripheral to wrap.
1914-- @treturn table|nil The table containing the peripheral's methods, or `nil` if
1915-- there is no peripheral present with the given name.
1916-- @usage peripheral.wrap("top").open(1)
1917function wrap(name)
1918 expect(1, name, "string")
1919
1920 local methods = peripheral.getMethods(name)
1921 if not methods then
1922 return nil
1923 end
1924
1925 local result = setmetatable({}, {
1926 __name = "peripheral",
1927 name = name,
1928 type = peripheral.getType(name),
1929 })
1930 for _, method in ipairs(methods) do
1931 result[method] = function(...)
1932 return peripheral.call(name, method, ...)
1933 end
1934 end
1935 return result
1936end
1937
1938--- Find all peripherals of a specific type, and return the
1939-- @{peripheral.wrap|wrapped} peripherals.
1940--
1941-- @tparam string ty The type of peripheral to look for.
1942-- @tparam[opt] function(name:string, wrapped:table):boolean filter A
1943-- filter function, which takes the peripheral's name and wrapped table
1944-- and returns if it should be included in the result.
1945-- @treturn table... 0 or more wrapped peripherals matching the given filters.
1946-- @usage { peripheral.find("monitor") }
1947-- @usage peripheral.find("modem", rednet.open)
1948function find(ty, filter)
1949 expect(1, ty, "string")
1950 expect(2, filter, "function", "nil")
1951
1952 local results = {}
1953 for _, name in ipairs(peripheral.getNames()) do
1954 if peripheral.getType(name) == ty then
1955 local wrapped = peripheral.wrap(name)
1956 if filter == nil or filter(name, wrapped) then
1957 table.insert(results, wrapped)
1958 end
1959 end
1960 end
1961 return table.unpack(results)
1962end
1963
1964if periphemu then
1965 function create(side, type, ...)
1966 return periphemu.create(side, type, ...)
1967 end
1968
1969 function remove(side)
1970 return periphemu.remove(side)
1971 end
1972end
1973<End>
1974<Name:rednet.lua>
1975<Begin>
1976--- The Rednet API allows systems to communicate between each other without
1977-- using redstone. It serves as a wrapper for the modem API, offering ease of
1978-- functionality (particularly in regards to repeating signals) with some
1979-- expense of fine control.
1980--
1981-- In order to send and receive data, a modem (either wired, wireless, or ender)
1982-- is required. The data reaches any possible destinations immediately after
1983-- sending it, but is range limited.
1984--
1985-- Rednet also allows you to use a "protocol" - simple string names indicating
1986-- what messages are about. Receiving systems may filter messages according to
1987-- their protocols, thereby automatically ignoring incoming messages which don't
1988-- specify an identical string. It's also possible to @{rednet.lookup|lookup}
1989-- which systems in the area use certain protocols, hence making it easier to
1990-- determine where given messages should be sent in the first place.
1991--
1992-- @module rednet
1993
1994local expect = dofile("rom/modules/main/cc/expect.lua").expect
1995
1996--- The channel used by the Rednet API to @{broadcast} messages.
1997CHANNEL_BROADCAST = 65535
1998
1999--- The channel used by the Rednet API to repeat messages.
2000CHANNEL_REPEAT = 65533
2001
2002local tReceivedMessages = {}
2003local tReceivedMessageTimeouts = {}
2004local tHostnames = {}
2005
2006--- Opens a modem with the given @{peripheral} name, allowing it to send and
2007-- receive messages over rednet.
2008--
2009-- This will open the modem on two channels: one which has the same
2010-- @{os.getComputerID|ID} as the computer, and another on
2011-- @{CHANNEL_BROADCAST|the broadcast channel}.
2012--
2013-- @tparam string modem The name of the modem to open.
2014-- @throws If there is no such modem with the given name
2015function open(modem)
2016 expect(1, modem, "string")
2017 if peripheral.getType(modem) ~= "modem" then
2018 error("No such modem: " .. modem, 2)
2019 end
2020 peripheral.call(modem, "open", os.getComputerID())
2021 peripheral.call(modem, "open", CHANNEL_BROADCAST)
2022end
2023
2024--- Close a modem with the given @{peripheral} name, meaning it can no longer
2025-- send and receive rednet messages.
2026--
2027-- @tparam[opt] string modem The side the modem exists on. If not given, all
2028-- open modems will be closed.
2029-- @throws If there is no such modem with the given name
2030function close(modem)
2031 expect(1, modem, "string", "nil")
2032 if modem then
2033 -- Close a specific modem
2034 if peripheral.getType(modem) ~= "modem" then
2035 error("No such modem: " .. modem, 2)
2036 end
2037 peripheral.call(modem, "close", os.getComputerID())
2038 peripheral.call(modem, "close", CHANNEL_BROADCAST)
2039 else
2040 -- Close all modems
2041 for _, modem in ipairs(peripheral.getNames()) do
2042 if isOpen(modem) then
2043 close(modem)
2044 end
2045 end
2046 end
2047end
2048
2049--- Determine if rednet is currently open.
2050--
2051-- @tparam[opt] string modem Which modem to check. If not given, all connected
2052-- modems will be checked.
2053-- @treturn boolean If the given modem is open.
2054function isOpen(modem)
2055 expect(1, modem, "string", "nil")
2056 if modem then
2057 -- Check if a specific modem is open
2058 if peripheral.getType(modem) == "modem" then
2059 return peripheral.call(modem, "isOpen", os.getComputerID()) and peripheral.call(modem, "isOpen", CHANNEL_BROADCAST)
2060 end
2061 else
2062 -- Check if any modem is open
2063 for _, modem in ipairs(peripheral.getNames()) do
2064 if isOpen(modem) then
2065 return true
2066 end
2067 end
2068 end
2069 return false
2070end
2071
2072--- Allows a computer or turtle with an attached modem to send a message
2073-- intended for a system with a specific ID. At least one such modem must first
2074-- be @{rednet.open|opened} before sending is possible.
2075--
2076-- Assuming the target was in range and also had a correctly opened modem, it
2077-- may then use @{rednet.receive} to collect the message.
2078--
2079-- @tparam number nRecipient The ID of the receiving computer.
2080-- @param message The message to send. This should not contain coroutines or
2081-- functions, as they will be converted to @{nil}.
2082-- @tparam[opt] string sProtocol The "protocol" to send this message under. When
2083-- using @{rednet.receive} one can filter to only receive messages sent under a
2084-- particular protocol.
2085-- @treturn boolean If this message was successfully sent (i.e. if rednet is
2086-- currently @{rednet.open|open}). Note, this does not guarantee the message was
2087-- actually _received_.
2088-- @see rednet.receive
2089function send(nRecipient, message, sProtocol)
2090 expect(1, nRecipient, "number")
2091 expect(3, sProtocol, "string", "nil")
2092 -- Generate a (probably) unique message ID
2093 -- We could do other things to guarantee uniqueness, but we really don't need to
2094 -- Store it to ensure we don't get our own messages back
2095 local nMessageID = math.random(1, 2147483647)
2096 tReceivedMessages[nMessageID] = true
2097 tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID
2098
2099 -- Create the message
2100 local nReplyChannel = os.getComputerID()
2101 local tMessage = {
2102 nMessageID = nMessageID,
2103 nRecipient = nRecipient,
2104 message = message,
2105 sProtocol = sProtocol,
2106 }
2107
2108 local sent = false
2109 if nRecipient == os.getComputerID() then
2110 -- Loopback to ourselves
2111 os.queueEvent("rednet_message", nReplyChannel, message, sProtocol)
2112 sent = true
2113 else
2114 -- Send on all open modems, to the target and to repeaters
2115 for _, sModem in ipairs(peripheral.getNames()) do
2116 if isOpen(sModem) then
2117 peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage)
2118 peripheral.call(sModem, "transmit", CHANNEL_REPEAT, nReplyChannel, tMessage)
2119 sent = true
2120 end
2121 end
2122 end
2123
2124 return sent
2125end
2126
2127--- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
2128-- channel. The message will be received by every device listening to rednet.
2129--
2130-- @param message The message to send. This should not contain coroutines or
2131-- functions, as they will be converted to @{nil}.
2132-- @tparam[opt] string sProtocol The "protocol" to send this message under. When
2133-- using @{rednet.receive} one can filter to only receive messages sent under a
2134-- particular protocol.
2135-- @see rednet.receive
2136function broadcast(message, sProtocol)
2137 expect(2, sProtocol, "string", "nil")
2138 send(CHANNEL_BROADCAST, message, sProtocol)
2139end
2140
2141--- Wait for a rednet message to be received, or until `nTimeout` seconds have
2142-- elapsed.
2143--
2144-- @tparam[opt] string sProtocolFilter The protocol the received message must be
2145-- sent with. If specified, any messages not sent under this protocol will be
2146-- discarded.
2147-- @tparam[opt] number nTimeout The number of seconds to wait if no message is
2148-- received.
2149-- @treturn[1] number The computer which sent this message
2150-- @return[1] The received message
2151-- @treturn[1] string|nil The protocol this message was sent under.
2152-- @treturn[2] nil If the timeout elapsed and no message was received.
2153-- @see rednet.broadcast
2154-- @see rednet.send
2155function receive(sProtocolFilter, nTimeout)
2156 -- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
2157 if type(sProtocolFilter) == "number" and nTimeout == nil then
2158 sProtocolFilter, nTimeout = nil, sProtocolFilter
2159 end
2160 expect(1, sProtocolFilter, "string", "nil")
2161 expect(2, nTimeout, "number", "nil")
2162
2163 -- Start the timer
2164 local timer = nil
2165 local sFilter = nil
2166 if nTimeout then
2167 timer = os.startTimer(nTimeout)
2168 sFilter = nil
2169 else
2170 sFilter = "rednet_message"
2171 end
2172
2173 -- Wait for events
2174 while true do
2175 local sEvent, p1, p2, p3 = os.pullEvent(sFilter)
2176 if sEvent == "rednet_message" then
2177 -- Return the first matching rednet_message
2178 local nSenderID, message, sProtocol = p1, p2, p3
2179 if sProtocolFilter == nil or sProtocol == sProtocolFilter then
2180 return nSenderID, message, sProtocol
2181 end
2182 elseif sEvent == "timer" then
2183 -- Return nil if we timeout
2184 if p1 == timer then
2185 return nil
2186 end
2187 end
2188 end
2189end
2190
2191--- Register the system as "hosting" the desired protocol under the specified
2192-- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
2193-- maybe name) on the same network, the registered system will automatically
2194-- respond via a background process, hence providing the system performing the
2195-- lookup with its ID number.
2196--
2197-- Multiple computers may not register themselves on the same network as having
2198-- the same names against the same protocols, and the title `localhost` is
2199-- specifically reserved. They may, however, share names as long as their hosted
2200-- protocols are different, or if they only join a given network after
2201-- "registering" themselves before doing so (eg while offline or part of a
2202-- different network).
2203--
2204-- @tparam string sProtocol The protocol this computer provides.
2205-- @tparam string sHostname The name this protocol exposes for the given protocol.
2206-- @throws If trying to register a hostname which is reserved, or currently in use.
2207-- @see rednet.unhost
2208-- @see rednet.lookup
2209function host(sProtocol, sHostname)
2210 expect(1, sProtocol, "string")
2211 expect(2, sHostname, "string")
2212 if sHostname == "localhost" then
2213 error("Reserved hostname", 2)
2214 end
2215 if tHostnames[sProtocol] ~= sHostname then
2216 if lookup(sProtocol, sHostname) ~= nil then
2217 error("Hostname in use", 2)
2218 end
2219 tHostnames[sProtocol] = sHostname
2220 end
2221end
2222
2223--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
2224-- respond to @{rednet.lookup} requests.
2225--
2226-- @tparam string sProtocol The protocol to unregister your self from.
2227function unhost(sProtocol)
2228 expect(1, sProtocol, "string")
2229 tHostnames[sProtocol] = nil
2230end
2231
2232--- Search the local rednet network for systems @{rednet.host|hosting} the
2233-- desired protocol and returns any computer IDs that respond as "registered"
2234-- against it.
2235--
2236-- If a hostname is specified, only one ID will be returned (assuming an exact
2237-- match is found).
2238--
2239-- @tparam string sProtocol The protocol to search for.
2240-- @tparam[opt] string sHostname The hostname to search for.
2241--
2242-- @treturn[1] { number }|nil A list of computer IDs hosting the given
2243-- protocol, or @{nil} if none exist.
2244-- @treturn[2] number|nil The computer ID with the provided hostname and protocol,
2245-- or @{nil} if none exists.
2246function lookup(sProtocol, sHostname)
2247 expect(1, sProtocol, "string")
2248 expect(2, sHostname, "string", "nil")
2249
2250 -- Build list of host IDs
2251 local tResults = nil
2252 if sHostname == nil then
2253 tResults = {}
2254 end
2255
2256 -- Check localhost first
2257 if tHostnames[sProtocol] then
2258 if sHostname == nil then
2259 table.insert(tResults, os.getComputerID())
2260 elseif sHostname == "localhost" or sHostname == tHostnames[sProtocol] then
2261 return os.getComputerID()
2262 end
2263 end
2264
2265 if not isOpen() then
2266 if tResults then
2267 return table.unpack(tResults)
2268 end
2269 return nil
2270 end
2271
2272 -- Broadcast a lookup packet
2273 broadcast({
2274 sType = "lookup",
2275 sProtocol = sProtocol,
2276 sHostname = sHostname,
2277 }, "dns")
2278
2279 -- Start a timer
2280 local timer = os.startTimer(2)
2281
2282 -- Wait for events
2283 while true do
2284 local event, p1, p2, p3 = os.pullEvent()
2285 if event == "rednet_message" then
2286 -- Got a rednet message, check if it's the response to our request
2287 local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
2288 if sMessageProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup response" then
2289 if tMessage.sProtocol == sProtocol then
2290 if sHostname == nil then
2291 table.insert(tResults, nSenderID)
2292 elseif tMessage.sHostname == sHostname then
2293 return nSenderID
2294 end
2295 end
2296 end
2297 else
2298 -- Got a timer event, check it's the end of our timeout
2299 if p1 == timer then
2300 break
2301 end
2302 end
2303 end
2304 if tResults then
2305 return table.unpack(tResults)
2306 end
2307 return nil
2308end
2309
2310local bRunning = false
2311
2312--- Listen for modem messages and converts them into rednet messages, which may
2313-- then be @{receive|received}.
2314--
2315-- This is automatically started in the background on computer startup, and
2316-- should not be called manually.
2317function run()
2318 if bRunning then
2319 error("rednet is already running", 2)
2320 end
2321 bRunning = true
2322
2323 while bRunning do
2324 local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
2325 if sEvent == "modem_message" then
2326 -- Got a modem message, process it and add it to the rednet event queue
2327 local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
2328 if isOpen(sModem) and (nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST) then
2329 if type(tMessage) == "table" and tMessage.nMessageID then
2330 if not tReceivedMessages[tMessage.nMessageID] then
2331 tReceivedMessages[tMessage.nMessageID] = true
2332 tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
2333 os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol)
2334 end
2335 end
2336 end
2337
2338 elseif sEvent == "rednet_message" then
2339 -- Got a rednet message (queued from above), respond to dns lookup
2340 local nSenderID, tMessage, sProtocol = p1, p2, p3
2341 if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then
2342 local sHostname = tHostnames[tMessage.sProtocol]
2343 if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
2344 rednet.send(nSenderID, {
2345 sType = "lookup response",
2346 sHostname = sHostname,
2347 sProtocol = tMessage.sProtocol,
2348 }, "dns")
2349 end
2350 end
2351
2352 elseif sEvent == "timer" then
2353 -- Got a timer event, use it to clear the event queue
2354 local nTimer = p1
2355 local nMessage = tReceivedMessageTimeouts[nTimer]
2356 if nMessage then
2357 tReceivedMessageTimeouts[nTimer] = nil
2358 tReceivedMessages[nMessage] = nil
2359 end
2360 end
2361 end
2362end
2363<End>
2364<Name:settings.lua>
2365<Begin>
2366--- The settings API allows to store values and save them to a file for
2367-- persistent configurations for CraftOS and your programs.
2368--
2369-- By default, the settings API will load its configuration from the
2370-- `/.settings` file. One can then use @{settings.save} to update the file.
2371--
2372-- @module settings
2373
2374local expect = dofile("rom/modules/main/cc/expect.lua")
2375local type, expect, field = type, expect.expect, expect.field
2376
2377local details, values = {}, {}
2378
2379local function reserialize(value)
2380 if type(value) ~= "table" then return value end
2381 return textutils.unserialize(textutils.serialize(value))
2382end
2383
2384local function copy(value)
2385 if type(value) ~= "table" then return value end
2386 local result = {}
2387 for k, v in pairs(value) do result[k] = copy(v) end
2388 return result
2389end
2390
2391local valid_types = { "number", "string", "boolean", "table" }
2392for _, v in ipairs(valid_types) do valid_types[v] = true end
2393
2394--- Define a new setting, optional specifying various properties about it.
2395--
2396-- While settings do not have to be added before being used, doing so allows
2397-- you to provide defaults and additional metadata.
2398--
2399-- @tparam string name The name of this option
2400-- @tparam[opt] { description? = string, default? = any, type? = string } options
2401-- Options for this setting. This table accepts the following fields:
2402--
2403-- - `description`: A description which may be printed when running the `set` program.
2404-- - `default`: A default value, which is returned by @{settings.get} if the
2405-- setting has not been changed.
2406-- - `type`: Require values to be of this type. @{set|Setting} the value to another type
2407-- will error.
2408function define(name, options)
2409 expect(1, name, "string")
2410 expect(2, options, "table", nil)
2411
2412 if options then
2413 options = {
2414 description = field(options, "description", "string", "nil"),
2415 default = reserialize(field(options, "default", "number", "string", "boolean", "table", "nil")),
2416 type = field(options, "type", "string", "nil"),
2417 }
2418
2419 if options.type and not valid_types[options.type] then
2420 error(("Unknown type %q. Expected one of %s."):format(options.type, table.concat(valid_types, ", ")), 2)
2421 end
2422 else
2423 options = {}
2424 end
2425
2426 details[name] = options
2427end
2428
2429--- Remove a @{define|definition} of a setting.
2430--
2431-- If a setting has been changed, this does not remove its value. Use @{settings.unset}
2432-- for that.
2433--
2434-- @tparam string name The name of this option
2435function undefine(name)
2436 expect(1, name, "string")
2437 details[name] = nil
2438end
2439
2440local function set_value(name, value)
2441 local new = reserialize(value)
2442 local old = values[name]
2443 if old == nil then
2444 local opt = details[name]
2445 old = opt and opt.default
2446 end
2447
2448 values[name] = new
2449 if old ~= new then
2450 -- This should be safe, as os.queueEvent copies values anyway.
2451 os.queueEvent("setting_changed", name, new, old)
2452 end
2453end
2454
2455--- Set the value of a setting.
2456--
2457-- @tparam string name The name of the setting to set
2458-- @param value The setting's value. This cannot be `nil`, and must be
2459-- serialisable by @{textutils.serialize}.
2460-- @throws If this value cannot be serialised
2461-- @see settings.unset
2462function set(name, value)
2463 expect(1, name, "string")
2464 expect(2, value, "number", "string", "boolean", "table")
2465
2466 local opt = details[name]
2467 if opt and opt.type then expect(2, value, opt.type) end
2468
2469 set_value(name, value)
2470end
2471
2472--- Get the value of a setting.
2473--
2474-- @tparam string name The name of the setting to get.
2475-- @param[opt] default The value to use should there be pre-existing value for
2476-- this setting. If not given, it will use the setting's default value if given,
2477-- or `nil` otherwise.
2478-- @return The setting's, or the default if the setting has not been changed.
2479function get(name, default)
2480 expect(1, name, "string")
2481 local result = values[name]
2482 if result ~= nil then
2483 return copy(result)
2484 elseif default ~= nil then
2485 return default
2486 else
2487 local opt = details[name]
2488 return opt and copy(opt.default)
2489 end
2490end
2491
2492--- Get details about a specific setting.
2493--
2494-- @tparam string name The name of the setting to get.
2495-- @treturn { description? = string, default? = any, type? = string, value? = any }
2496-- Information about this setting. This includes all information from @{settings.define},
2497-- as well as this setting's value.
2498function getDetails(name)
2499 expect(1, name, "string")
2500 local deets = copy(details[name]) or {}
2501 deets.value = values[name]
2502 deets.changed = deets.value ~= nil
2503 if deets.value == nil then deets.value = deets.default end
2504 return deets
2505end
2506
2507--- Remove the value of a setting, setting it to the default.
2508--
2509-- @{settings.get} will return the default value until the setting's value is
2510-- @{settings.set|set}, or the computer is rebooted.
2511--
2512-- @tparam string name The name of the setting to unset.
2513-- @see settings.set
2514-- @see settings.clear
2515function unset(name)
2516 expect(1, name, "string")
2517 set_value(name, nil)
2518end
2519
2520--- Resets the value of all settings. Equivalent to calling @{settings.unset}
2521--- on every setting.
2522--
2523-- @see settings.unset
2524function clear()
2525 for name in pairs(values) do
2526 set_value(name, nil)
2527 end
2528end
2529
2530--- Get the names of all currently defined settings.
2531--
2532-- @treturn { string } An alphabetically sorted list of all currently-defined
2533-- settings.
2534function getNames()
2535 local result, n = {}, 1
2536 for k in pairs(details) do
2537 result[n], n = k, n + 1
2538 end
2539 for k in pairs(values) do
2540 if not details[k] then result[n], n = k, n + 1 end
2541 end
2542 table.sort(result)
2543 return result
2544end
2545
2546--- Load settings from the given file.
2547--
2548-- Existing settings will be merged with any pre-existing ones. Conflicting
2549-- entries will be overwritten, but any others will be preserved.
2550--
2551-- @tparam[opt] string sPath The file to load from, defaulting to `.settings`.
2552-- @treturn boolean Whether settings were successfully read from this
2553-- file. Reasons for failure may include the file not existing or being
2554-- corrupted.
2555--
2556-- @see settings.save
2557function load(sPath)
2558 expect(1, sPath, "string", "nil")
2559 local file = fs.open(sPath or ".settings", "r")
2560 if not file then
2561 return false
2562 end
2563
2564 local sText = file.readAll()
2565 file.close()
2566
2567 local tFile = textutils.unserialize(sText)
2568 if type(tFile) ~= "table" then
2569 return false
2570 end
2571
2572 for k, v in pairs(tFile) do
2573 local ty_v = type(v)
2574 if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
2575 local opt = details[k]
2576 if not opt or not opt.type or ty_v == opt.type then
2577 set_value(k, v)
2578 end
2579 end
2580 end
2581
2582 return true
2583end
2584
2585--- Save settings to the given file.
2586--
2587-- This will entirely overwrite the pre-existing file. Settings defined in the
2588-- file, but not currently loaded will be removed.
2589--
2590-- @tparam[opt] string sPath The path to save settings to, defaulting to `.settings`.
2591-- @treturn boolean If the settings were successfully saved.
2592--
2593-- @see settings.load
2594function save(sPath)
2595 expect(1, sPath, "string", "nil")
2596 local file = fs.open(sPath or ".settings", "w")
2597 if not file then
2598 return false
2599 end
2600
2601 file.write(textutils.serialize(values))
2602 file.close()
2603
2604 return true
2605end
2606<End>
2607<Name:term.lua>
2608<Begin>
2609--- The Terminal API provides functions for writing text to the terminal and
2610-- monitors, and drawing ASCII graphics.
2611--
2612-- @module term
2613
2614local expect = dofile("rom/modules/main/cc/expect.lua").expect
2615
2616local native = term.native and term.native() or term
2617local redirectTarget = native
2618
2619local function wrap(_sFunction)
2620 return function(...)
2621 return redirectTarget[_sFunction](...)
2622 end
2623end
2624
2625local term = _ENV
2626
2627--- Redirects terminal output to a monitor, a @{window}, or any other custom
2628-- terminal object. Once the redirect is performed, any calls to a "term"
2629-- function - or to a function that makes use of a term function, as @{print} -
2630-- will instead operate with the new terminal object.
2631--
2632-- A "terminal object" is simply a table that contains functions with the same
2633-- names - and general features - as those found in the term table. For example,
2634-- a wrapped monitor is suitable.
2635--
2636-- The redirect can be undone by pointing back to the previous terminal object
2637-- (which this function returns whenever you switch).
2638--
2639-- @tparam Redirect target The terminal redirect the @{term} API will draw to.
2640-- @treturn Redirect The previous redirect object, as returned by
2641-- @{term.current}.
2642-- @usage
2643-- Redirect to a monitor on the right of the computer.
2644-- term.redirect(peripheral.wrap("right"))
2645term.redirect = function(target)
2646 expect(1, target, "table")
2647 if target == term or target == _G.term then
2648 error("term is not a recommended redirect target, try term.current() instead", 2)
2649 end
2650
2651 for _, method in ipairs {
2652 "setGraphicsMode",
2653 "getGraphicsMode",
2654 "setPixel",
2655 "getPixel",
2656 "drawPixels",
2657 "getPixels",
2658 "showMouse",
2659 "setFrozen",
2660 "getFrozen"
2661 } do
2662 if target[method] == nil then
2663 target[method] = native[method]
2664 end
2665 end
2666
2667 for k, v in pairs(native) do
2668 if type(k) == "string" and type(v) == "function" then
2669 if type(target[k]) ~= "function" then
2670 target[k] = function()
2671 error("Redirect object is missing method " .. k .. ".", 2)
2672 end
2673 end
2674 end
2675 end
2676 local oldRedirectTarget = redirectTarget
2677 redirectTarget = target
2678 return oldRedirectTarget
2679end
2680
2681--- Returns the current terminal object of the computer.
2682--
2683-- @treturn Redirect The current terminal redirect
2684-- @usage
2685-- Create a new @{window} which draws to the current redirect target
2686-- window.create(term.current(), 1, 1, 10, 10)
2687term.current = function()
2688 return redirectTarget
2689end
2690
2691--- Get the native terminal object of the current computer.
2692--
2693-- It is recommended you do not use this function unless you absolutely have
2694-- to. In a multitasked environment, @{term.native} will _not_ be the current
2695-- terminal object, and so drawing may interfere with other programs.
2696--
2697-- @treturn Redirect The native terminal redirect.
2698term.native = function()
2699 return native
2700end
2701
2702-- Some methods shouldn't go through redirects, so we move them to the main
2703-- term API.
2704for _, method in ipairs { "nativePaletteColor", "nativePaletteColour", "screenshot" } do
2705 term[method] = native[method]
2706 native[method] = nil
2707end
2708
2709for k, v in pairs(native) do
2710 if type(k) == "string" and type(v) == "function" and rawget(term, k) == nil then
2711 term[k] = wrap(k)
2712 end
2713end
2714<End>
2715<Name:textutils.lua>
2716<Begin>
2717--- The @{textutils} API provides helpful utilities for formatting and
2718-- manipulating strings.
2719--
2720-- @module textutils
2721
2722local expect = dofile("rom/modules/main/cc/expect.lua")
2723local expect, field = expect.expect, expect.field
2724
2725--- Slowly writes string text at current cursor position,
2726-- character-by-character.
2727--
2728-- Like @{_G.write}, this does not insert a newline at the end.
2729--
2730-- @tparam string sText The the text to write to the screen
2731-- @tparam[opt] number nRate The number of characters to write each second,
2732-- Defaults to 20.
2733-- @usage textutils.slowWrite("Hello, world!")
2734-- @usage textutils.slowWrite("Hello, world!", 5)
2735function slowWrite(sText, nRate)
2736 expect(2, nRate, "number", "nil")
2737 nRate = nRate or 20
2738 if nRate < 0 then
2739 error("Rate must be positive", 2)
2740 end
2741 local nSleep = 1 / nRate
2742
2743 sText = tostring(sText)
2744 local x, y = term.getCursorPos()
2745 local len = #sText
2746
2747 for n = 1, len do
2748 term.setCursorPos(x, y)
2749 sleep(nSleep)
2750 local nLines = write(string.sub(sText, 1, n))
2751 local _, newY = term.getCursorPos()
2752 y = newY - nLines
2753 end
2754end
2755
2756--- Slowly prints string text at current cursor position,
2757-- character-by-character.
2758--
2759-- Like @{print}, this inserts a newline after printing.
2760--
2761-- @tparam string sText The the text to write to the screen
2762-- @tparam[opt] number nRate The number of characters to write each second,
2763-- Defaults to 20.
2764-- @usage textutils.slowPrint("Hello, world!")
2765-- @usage textutils.slowPrint("Hello, world!", 5)
2766function slowPrint(sText, nRate)
2767 slowWrite(sText, nRate)
2768 print()
2769end
2770
2771--- Takes input time and formats it in a more readable format such as `6:30 PM`.
2772--
2773-- @tparam number nTime The time to format, as provided by @{os.time}.
2774-- @tparam[opt] boolean bTwentyFourHour Whether to format this as a 24-hour
2775-- clock (`18:30`) rather than a 12-hour one (`6:30 AM`)
2776-- @treturn string The formatted time
2777-- @usage textutils.formatTime(os.time())
2778function formatTime(nTime, bTwentyFourHour)
2779 expect(1, nTime, "number")
2780 expect(2, bTwentyFourHour, "boolean", "nil")
2781 local sTOD = nil
2782 if not bTwentyFourHour then
2783 if nTime >= 12 then
2784 sTOD = "PM"
2785 else
2786 sTOD = "AM"
2787 end
2788 if nTime >= 13 then
2789 nTime = nTime - 12
2790 end
2791 end
2792
2793 local nHour = math.floor(nTime)
2794 local nMinute = math.floor((nTime - nHour) * 60)
2795 if sTOD then
2796 return string.format("%d:%02d %s", nHour == 0 and 12 or nHour, nMinute, sTOD)
2797 else
2798 return string.format("%d:%02d", nHour, nMinute)
2799 end
2800end
2801
2802local function makePagedScroll(_term, _nFreeLines)
2803 local nativeScroll = _term.scroll
2804 local nFreeLines = _nFreeLines or 0
2805 return function(_n)
2806 for _ = 1, _n do
2807 nativeScroll(1)
2808
2809 if nFreeLines <= 0 then
2810 local _, h = _term.getSize()
2811 _term.setCursorPos(1, h)
2812 _term.write("Press any key to continue")
2813 os.pullEvent("key")
2814 _term.clearLine()
2815 _term.setCursorPos(1, h)
2816 else
2817 nFreeLines = nFreeLines - 1
2818 end
2819 end
2820 end
2821end
2822
2823--- Prints a given string to the display.
2824--
2825-- If the action can be completed without scrolling, it acts much the same as
2826-- @{print}; otherwise, it will throw up a "Press any key to continue" prompt at
2827-- the bottom of the display. Each press will cause it to scroll down and write
2828-- a single line more before prompting again, if need be.
2829--
2830-- @tparam string _sText The text to print to the screen.
2831-- @tparam[opt] number _nFreeLines The number of lines which will be
2832-- automatically scrolled before the first prompt appears (meaning _nFreeLines +
2833-- 1 lines will be printed). This can be set to the terminal's height - 2 to
2834-- always try to fill the screen. Defaults to 0, meaning only one line is
2835-- displayed before prompting.
2836-- @treturn number The number of lines printed.
2837-- @usage
2838-- local width, height = term.getSize()
2839-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2)
2840function pagedPrint(_sText, _nFreeLines)
2841 expect(2, _nFreeLines, "number", "nil")
2842 -- Setup a redirector
2843 local oldTerm = term.current()
2844 local newTerm = {}
2845 for k, v in pairs(oldTerm) do
2846 newTerm[k] = v
2847 end
2848 newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines)
2849 term.redirect(newTerm)
2850
2851 -- Print the text
2852 local result
2853 local ok, err = pcall(function()
2854 if _sText ~= nil then
2855 result = print(_sText)
2856 else
2857 result = print()
2858 end
2859 end)
2860
2861 -- Removed the redirector
2862 term.redirect(oldTerm)
2863
2864 -- Propogate errors
2865 if not ok then
2866 error(err, 0)
2867 end
2868 return result
2869end
2870
2871local function tabulateCommon(bPaged, ...)
2872 local tAll = table.pack(...)
2873 for i = 1, tAll.n do
2874 expect(i, tAll[i], "number", "table")
2875 end
2876
2877 local w, h = term.getSize()
2878 local nMaxLen = w / 8
2879 for n, t in ipairs(tAll) do
2880 if type(t) == "table" then
2881 for nu, sItem in pairs(t) do
2882 local ty = type(sItem)
2883 if ty ~= "string" and ty ~= "number" then
2884 error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. ty .. ")", 3)
2885 end
2886 nMaxLen = math.max(#tostring(sItem) + 1, nMaxLen)
2887 end
2888 end
2889 end
2890 local nCols = math.floor(w / nMaxLen)
2891 local nLines = 0
2892 local function newLine()
2893 if bPaged and nLines >= h - 3 then
2894 pagedPrint()
2895 else
2896 print()
2897 end
2898 nLines = nLines + 1
2899 end
2900
2901 local function drawCols(_t)
2902 local nCol = 1
2903 for _, s in ipairs(_t) do
2904 if nCol > nCols then
2905 nCol = 1
2906 newLine()
2907 end
2908
2909 local cx, cy = term.getCursorPos()
2910 cx = 1 + (nCol - 1) * nMaxLen
2911 term.setCursorPos(cx, cy)
2912 term.write(s)
2913
2914 nCol = nCol + 1
2915 end
2916 print()
2917 end
2918 for _, t in ipairs(tAll) do
2919 if type(t) == "table" then
2920 if #t > 0 then
2921 drawCols(t)
2922 end
2923 elseif type(t) == "number" then
2924 term.setTextColor(t)
2925 end
2926 end
2927end
2928
2929--- Prints tables in a structured form.
2930--
2931-- This accepts multiple arguments, either a table or a number. When
2932-- encountering a table, this will be treated as a table row, with each column
2933-- width being auto-adjusted.
2934--
2935-- When encountering a number, this sets the text color of the subsequent rows to it.
2936--
2937-- @tparam {string...}|number ... The rows and text colors to display.
2938-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
2939function tabulate(...)
2940 return tabulateCommon(false, ...)
2941end
2942
2943--- Prints tables in a structured form, stopping and prompting for input should
2944-- the result not fit on the terminal.
2945--
2946-- This functions identically to @{textutils.tabulate}, but will prompt for user
2947-- input should the whole output not fit on the display.
2948--
2949-- @tparam {string...}|number ... The rows and text colors to display.
2950-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
2951-- @see textutils.tabulate
2952-- @see textutils.pagedPrint
2953function pagedTabulate(...)
2954 return tabulateCommon(true, ...)
2955end
2956
2957local g_tLuaKeywords = {
2958 ["and"] = true,
2959 ["break"] = true,
2960 ["do"] = true,
2961 ["else"] = true,
2962 ["elseif"] = true,
2963 ["end"] = true,
2964 ["false"] = true,
2965 ["for"] = true,
2966 ["function"] = true,
2967 ["if"] = true,
2968 ["in"] = true,
2969 ["local"] = true,
2970 ["nil"] = true,
2971 ["not"] = true,
2972 ["or"] = true,
2973 ["repeat"] = true,
2974 ["return"] = true,
2975 ["then"] = true,
2976 ["true"] = true,
2977 ["until"] = true,
2978 ["while"] = true,
2979}
2980
2981local function serializeImpl(t, tTracking, sIndent)
2982 local sType = type(t)
2983 if sType == "table" then
2984 if tTracking[t] ~= nil then
2985 error("Cannot serialize table with recursive entries", 0)
2986 end
2987 tTracking[t] = true
2988
2989 if next(t) == nil then
2990 -- Empty tables are simple
2991 return "{}"
2992 else
2993 -- Other tables take more work
2994 local sResult = "{\n"
2995 local sSubIndent = sIndent .. " "
2996 local tSeen = {}
2997 for k, v in ipairs(t) do
2998 tSeen[k] = true
2999 sResult = sResult .. sSubIndent .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
3000 end
3001 for k, v in pairs(t) do
3002 if not tSeen[k] then
3003 local sEntry
3004 if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
3005 sEntry = k .. " = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
3006 else
3007 sEntry = "[ " .. serializeImpl(k, tTracking, sSubIndent) .. " ] = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
3008 end
3009 sResult = sResult .. sSubIndent .. sEntry
3010 end
3011 end
3012 sResult = sResult .. sIndent .. "}"
3013 return sResult
3014 end
3015
3016 elseif sType == "string" then
3017 return string.format("%q", t)
3018
3019 elseif sType == "number" or sType == "boolean" or sType == "nil" then
3020 return tostring(t)
3021
3022 else
3023 error("Cannot serialize type " .. sType, 0)
3024
3025 end
3026end
3027
3028local function mk_tbl(str, name)
3029 local msg = "attempt to mutate textutils." .. name
3030 return setmetatable({}, {
3031 __newindex = function() error(msg, 2) end,
3032 __tostring = function() return str end,
3033 })
3034end
3035
3036--- A table representing an empty JSON array, in order to distinguish it from an
3037-- empty JSON object.
3038--
3039-- The contents of this table should not be modified.
3040--
3041-- @usage textutils.serialiseJSON(textutils.empty_json_array)
3042-- @see textutils.serialiseJSON
3043-- @see textutils.unserialiseJSON
3044empty_json_array = mk_tbl("[]", "empty_json_array")
3045
3046--- A table representing the JSON null value.
3047--
3048-- The contents of this table should not be modified.
3049--
3050-- @usage textutils.serialiseJSON(textutils.json_null)
3051-- @see textutils.serialiseJSON
3052-- @see textutils.unserialiseJSON
3053json_null = mk_tbl("null", "json_null")
3054
3055local serializeJSONString
3056do
3057 local function hexify(c)
3058 return ("\\u00%02X"):format(c:byte())
3059 end
3060
3061 local map = {
3062 ["\""] = "\\\"",
3063 ["\\"] = "\\\\",
3064 ["\b"] = "\\b",
3065 ["\f"] = "\\f",
3066 ["\n"] = "\\n",
3067 ["\r"] = "\\r",
3068 ["\t"] = "\\t",
3069 }
3070 for i = 0, 0x1f do
3071 local c = string.char(i)
3072 if map[c] == nil then map[c] = hexify(c) end
3073 end
3074
3075 serializeJSONString = function(s)
3076 return ('"%s"'):format(s:gsub("[%z\1-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
3077 end
3078end
3079
3080local function serializeJSONImpl(t, tTracking, bNBTStyle)
3081 local sType = type(t)
3082 if t == empty_json_array then return "[]"
3083 elseif t == json_null then return "null"
3084
3085 elseif sType == "table" then
3086 if tTracking[t] ~= nil then
3087 error("Cannot serialize table with recursive entries", 0)
3088 end
3089 tTracking[t] = true
3090
3091 if next(t) == nil then
3092 -- Empty tables are simple
3093 return "{}"
3094 else
3095 -- Other tables take more work
3096 local sObjectResult = "{"
3097 local sArrayResult = "["
3098 local nObjectSize = 0
3099 local nArraySize = 0
3100 for k, v in pairs(t) do
3101 if type(k) == "string" then
3102 local sEntry
3103 if bNBTStyle then
3104 sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
3105 else
3106 sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
3107 end
3108 if nObjectSize == 0 then
3109 sObjectResult = sObjectResult .. sEntry
3110 else
3111 sObjectResult = sObjectResult .. "," .. sEntry
3112 end
3113 nObjectSize = nObjectSize + 1
3114 end
3115 end
3116 for _, v in ipairs(t) do
3117 local sEntry = serializeJSONImpl(v, tTracking, bNBTStyle)
3118 if nArraySize == 0 then
3119 sArrayResult = sArrayResult .. sEntry
3120 else
3121 sArrayResult = sArrayResult .. "," .. sEntry
3122 end
3123 nArraySize = nArraySize + 1
3124 end
3125 sObjectResult = sObjectResult .. "}"
3126 sArrayResult = sArrayResult .. "]"
3127 if nObjectSize > 0 or nArraySize == 0 then
3128 return sObjectResult
3129 else
3130 return sArrayResult
3131 end
3132 end
3133
3134 elseif sType == "string" then
3135 return serializeJSONString(t)
3136
3137 elseif sType == "number" or sType == "boolean" then
3138 return tostring(t)
3139
3140 else
3141 error("Cannot serialize type " .. sType, 0)
3142
3143 end
3144end
3145
3146local unserialise_json
3147do
3148 local sub, find, match, concat, tonumber = string.sub, string.find, string.match, table.concat, tonumber
3149
3150 --- Skip any whitespace
3151 local function skip(str, pos)
3152 local _, last = find(str, "^[ \n\r\t]+", pos)
3153 if last then return last + 1 else return pos end
3154 end
3155
3156 local escapes = {
3157 ["b"] = '\b', ["f"] = '\f', ["n"] = '\n', ["r"] = '\r', ["t"] = '\t',
3158 ["\""] = "\"", ["/"] = "/", ["\\"] = "\\",
3159 }
3160
3161 local mt = {}
3162
3163 local function error_at(pos, msg, ...)
3164 if select('#', ...) > 0 then msg = msg:format(...) end
3165 error(setmetatable({ pos = pos, msg = msg }, mt))
3166 end
3167
3168 local function expected(pos, actual, exp)
3169 if actual == "" then actual = "end of input" else actual = ("%q"):format(actual) end
3170 error_at(pos, "Unexpected %s, expected %s.", actual, exp)
3171 end
3172
3173 local function parse_string(str, pos, terminate)
3174 local buf, n = {}, 1
3175
3176 while true do
3177 local c = sub(str, pos, pos)
3178 if c == "" then error_at(pos, "Unexpected end of input, expected '\"'.") end
3179 if c == terminate then break end
3180
3181 if c == '\\' then
3182 -- Handle the various escapes
3183 c = sub(str, pos + 1, pos + 1)
3184 if c == "" then error_at(pos, "Unexpected end of input, expected escape sequence.") end
3185
3186 if c == "u" then
3187 local num_str = match(str, "^%x%x%x%x", pos + 2)
3188 if not num_str then error_at(pos, "Malformed unicode escape %q.", sub(str, pos + 2, pos + 5)) end
3189 buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6
3190 else
3191 local unesc = escapes[c]
3192 if not unesc then error_at(pos + 1, "Unknown escape character %q.", c) end
3193 buf[n], n, pos = unesc, n + 1, pos + 2
3194 end
3195 elseif c >= '\x20' then
3196 buf[n], n, pos = c, n + 1, pos + 1
3197 else
3198 error_at(pos + 1, "Unescaped whitespace %q.", c)
3199 end
3200 end
3201
3202 return concat(buf, "", 1, n - 1), pos + 1
3203 end
3204
3205 local num_types = { b = true, B = true, s = true, S = true, l = true, L = true, f = true, F = true, d = true, D = true }
3206 local function parse_number(str, pos, opts)
3207 local _, last, num_str = find(str, '^(-?%d+%.?%d*[eE]?[+-]?%d*)', pos)
3208 local val = tonumber(num_str)
3209 if not val then error_at(pos, "Malformed number %q.", num_str) end
3210
3211 if opts.nbt_style and num_types[sub(str, last + 1, last + 1)] then return val, last + 2 end
3212
3213 return val, last + 1
3214 end
3215
3216 local function parse_ident(str, pos)
3217 local _, last, val = find(str, '^([%a][%w_]*)', pos)
3218 return val, last + 1
3219 end
3220
3221 local arr_types = { I = true, L = true, B = true }
3222 local function decode_impl(str, pos, opts)
3223 local c = sub(str, pos, pos)
3224 if c == '"' then return parse_string(str, pos + 1, '"')
3225 elseif c == "'" and opts.nbt_style then return parse_string(str, pos + 1, "\'")
3226 elseif c == "-" or c >= "0" and c <= "9" then return parse_number(str, pos, opts)
3227 elseif c == "t" then
3228 if sub(str, pos + 1, pos + 3) == "rue" then return true, pos + 4 end
3229 elseif c == 'f' then
3230 if sub(str, pos + 1, pos + 4) == "alse" then return false, pos + 5 end
3231 elseif c == 'n' then
3232 if sub(str, pos + 1, pos + 3) == "ull" then
3233 if opts.parse_null then
3234 return json_null, pos + 4
3235 else
3236 return nil, pos + 4
3237 end
3238 end
3239 elseif c == "{" then
3240 local obj = {}
3241
3242 pos = skip(str, pos + 1)
3243 c = sub(str, pos, pos)
3244
3245 if c == "" then return error_at(pos, "Unexpected end of input, expected '}'.") end
3246 if c == "}" then return obj, pos + 1 end
3247
3248 while true do
3249 local key, value
3250 if c == "\"" then key, pos = parse_string(str, pos + 1, "\"")
3251 elseif opts.nbt_style then key, pos = parse_ident(str, pos)
3252 else return expected(pos, c, "object key")
3253 end
3254
3255 pos = skip(str, pos)
3256
3257 c = sub(str, pos, pos)
3258 if c ~= ":" then return expected(pos, c, "':'") end
3259
3260 value, pos = decode_impl(str, skip(str, pos + 1), opts)
3261 obj[key] = value
3262
3263 -- Consume the next delimiter
3264 pos = skip(str, pos)
3265 c = sub(str, pos, pos)
3266 if c == "}" then break
3267 elseif c == "," then pos = skip(str, pos + 1)
3268 else return expected(pos, c, "',' or '}'")
3269 end
3270
3271 c = sub(str, pos, pos)
3272 end
3273
3274 return obj, pos + 1
3275
3276 elseif c == "[" then
3277 local arr, n = {}, 1
3278
3279 pos = skip(str, pos + 1)
3280 c = sub(str, pos, pos)
3281
3282 if arr_types[c] and sub(str, pos + 1, pos + 1) == ";" and opts.nbt_style then
3283 pos = skip(str, pos + 2)
3284 c = sub(str, pos, pos)
3285 end
3286
3287 if c == "" then return expected(pos, c, "']'") end
3288 if c == "]" then return empty_json_array, pos + 1 end
3289
3290 while true do
3291 n, arr[n], pos = n + 1, decode_impl(str, pos, opts)
3292
3293 -- Consume the next delimiter
3294 pos = skip(str, pos)
3295 c = sub(str, pos, pos)
3296 if c == "]" then break
3297 elseif c == "," then pos = skip(str, pos + 1)
3298 else return expected(pos, c, "',' or ']'")
3299 end
3300 end
3301
3302 return arr, pos + 1
3303 elseif c == "" then error_at(pos, 'Unexpected end of input.')
3304 end
3305
3306 error_at(pos, "Unexpected character %q.", c)
3307 end
3308
3309 --- Converts a serialised JSON string back into a reassembled Lua object.
3310 --
3311 -- This may be used with @{textutils.serializeJSON}, or when communicating
3312 -- with command blocks or web APIs.
3313 --
3314 -- @tparam string s The serialised string to deserialise.
3315 -- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options
3316 -- Options which control how this JSON object is parsed.
3317 --
3318 -- - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings,
3319 -- as produced by many commands.
3320 -- - `parse_null`: When true, `null` will be parsed as @{json_null}, rather
3321 -- than `nil`.
3322 --
3323 -- [nbt]: https://minecraft.gamepedia.com/NBT_format
3324 -- @return[1] The deserialised object
3325 -- @treturn[2] nil If the object could not be deserialised.
3326 -- @treturn string A message describing why the JSON string is invalid.
3327 unserialise_json = function(s, options)
3328 expect(1, s, "string")
3329 expect(2, options, "table", "nil")
3330
3331 if options then
3332 field(options, "nbt_style", "boolean", "nil")
3333 field(options, "nbt_style", "boolean", "nil")
3334 else
3335 options = {}
3336 end
3337
3338 local ok, res, pos = pcall(decode_impl, s, skip(s, 1), options)
3339 if not ok then
3340 if type(res) == "table" and getmetatable(res) == mt then
3341 return nil, ("Malformed JSON at position %d: %s"):format(res.pos, res.msg)
3342 end
3343
3344 error(res, 0)
3345 end
3346
3347 pos = skip(s, pos)
3348 if pos <= #s then
3349 return nil, ("Malformed JSON at position %d: Unexpected trailing character %q."):format(pos, sub(s, pos, pos))
3350 end
3351 return res
3352
3353 end
3354end
3355
3356--- Convert a Lua object into a textual representation, suitable for
3357-- saving in a file or pretty-printing.
3358--
3359-- @param t The object to serialise
3360-- @treturn string The serialised representation
3361-- @throws If the object contains a value which cannot be
3362-- serialised. This includes functions and tables which appear multiple
3363-- times.
3364function serialize(t)
3365 local tTracking = {}
3366 return serializeImpl(t, tTracking, "")
3367end
3368
3369serialise = serialize -- GB version
3370
3371--- Converts a serialised string back into a reassembled Lua object.
3372--
3373-- This is mainly used together with @{textutils.serialize}.
3374--
3375-- @tparam string s The serialised string to deserialise.
3376-- @return[1] The deserialised object
3377-- @treturn[2] nil If the object could not be deserialised.
3378function unserialize(s)
3379 expect(1, s, "string")
3380 local func = load("return " .. s, "unserialize", "t", {})
3381 if func then
3382 local ok, result = pcall(func)
3383 if ok then
3384 return result
3385 end
3386 end
3387 return nil
3388end
3389
3390unserialise = unserialize -- GB version
3391
3392--- Returns a JSON representation of the given data.
3393--
3394-- This function attempts to guess whether a table is a JSON array or
3395-- object. However, empty tables are assumed to be empty objects - use
3396-- @{textutils.empty_json_array} to mark an empty array.
3397--
3398-- This is largely intended for interacting with various functions from the
3399-- @{commands} API, though may also be used in making @{http} requests.
3400--
3401-- @param t The value to serialise. Like @{textutils.serialise}, this should not
3402-- contain recursive tables or functions.
3403-- @tparam[opt] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
3404-- instead of standard JSON.
3405-- @treturn string The JSON representation of the input.
3406-- @throws If the object contains a value which cannot be
3407-- serialised. This includes functions and tables which appear multiple
3408-- times.
3409-- @usage textutils.serializeJSON({ values = { 1, "2", true } })
3410function serializeJSON(t, bNBTStyle)
3411 expect(1, t, "table", "string", "number", "boolean")
3412 expect(2, bNBTStyle, "boolean", "nil")
3413 local tTracking = {}
3414 return serializeJSONImpl(t, tTracking, bNBTStyle or false)
3415end
3416
3417serialiseJSON = serializeJSON -- GB version
3418
3419unserializeJSON = unserialise_json
3420unserialiseJSON = unserialise_json
3421
3422--- Replaces certain characters in a string to make it safe for use in URLs or POST data.
3423--
3424-- @tparam string str The string to encode
3425-- @treturn string The encoded string.
3426-- @usage print("https://example.com/?view=" .. textutils.urlEncode("some text&things"))
3427function urlEncode(str)
3428 expect(1, str, "string")
3429 if str then
3430 str = string.gsub(str, "\n", "\r\n")
3431 str = string.gsub(str, "([^A-Za-z0-9 %-%_%.])", function(c)
3432 local n = string.byte(c)
3433 if n < 128 then
3434 -- ASCII
3435 return string.format("%%%02X", n)
3436 else
3437 -- Non-ASCII (encode as UTF-8)
3438 return
3439 string.format("%%%02X", 192 + bit32.band(bit32.arshift(n, 6), 31)) ..
3440 string.format("%%%02X", 128 + bit32.band(n, 63))
3441 end
3442 end)
3443 str = string.gsub(str, " ", "+")
3444 end
3445 return str
3446end
3447
3448local tEmpty = {}
3449
3450--- Provides a list of possible completions for a partial Lua expression.
3451--
3452-- If the completed element is a table, suggestions will have `.` appended to
3453-- them. Similarly, functions have `(` appended to them.
3454--
3455-- @tparam string sSearchText The partial expression to complete, such as a
3456-- variable name or table index.
3457--
3458-- @tparam[opt] table tSearchTable The table to find variables in, defaulting to
3459-- the global environment (@{_G}). The function also searches the "parent"
3460-- environment via the `__index` metatable field.
3461--
3462-- @treturn { string... } The (possibly empty) list of completions.
3463-- @see shell.setCompletionFunction
3464-- @see _G.read
3465-- @usage textutils.complete( "pa", _ENV )
3466function complete(sSearchText, tSearchTable)
3467 expect(1, sSearchText, "string")
3468 expect(2, tSearchTable, "table", "nil")
3469
3470 if g_tLuaKeywords[sSearchText] then return tEmpty end
3471 local nStart = 1
3472 local nDot = string.find(sSearchText, ".", nStart, true)
3473 local tTable = tSearchTable or _ENV
3474 while nDot do
3475 local sPart = string.sub(sSearchText, nStart, nDot - 1)
3476 local value = tTable[sPart]
3477 if type(value) == "table" then
3478 tTable = value
3479 nStart = nDot + 1
3480 nDot = string.find(sSearchText, ".", nStart, true)
3481 else
3482 return tEmpty
3483 end
3484 end
3485 local nColon = string.find(sSearchText, ":", nStart, true)
3486 if nColon then
3487 local sPart = string.sub(sSearchText, nStart, nColon - 1)
3488 local value = tTable[sPart]
3489 if type(value) == "table" then
3490 tTable = value
3491 nStart = nColon + 1
3492 else
3493 return tEmpty
3494 end
3495 end
3496
3497 local sPart = string.sub(sSearchText, nStart)
3498 local nPartLength = #sPart
3499
3500 local tResults = {}
3501 local tSeen = {}
3502 while tTable do
3503 for k, v in pairs(tTable) do
3504 if not tSeen[k] and type(k) == "string" then
3505 if string.find(k, sPart, 1, true) == 1 then
3506 if not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
3507 local sResult = string.sub(k, nPartLength + 1)
3508 if nColon then
3509 if type(v) == "function" then
3510 table.insert(tResults, sResult .. "(")
3511 elseif type(v) == "table" then
3512 local tMetatable = getmetatable(v)
3513 if tMetatable and (type(tMetatable.__call) == "function" or type(tMetatable.__call) == "table") then
3514 table.insert(tResults, sResult .. "(")
3515 end
3516 end
3517 else
3518 if type(v) == "function" then
3519 sResult = sResult .. "("
3520 elseif type(v) == "table" and next(v) ~= nil then
3521 sResult = sResult .. "."
3522 end
3523 table.insert(tResults, sResult)
3524 end
3525 end
3526 end
3527 end
3528 tSeen[k] = true
3529 end
3530 local tMetatable = getmetatable(tTable)
3531 if tMetatable and type(tMetatable.__index) == "table" then
3532 tTable = tMetatable.__index
3533 else
3534 tTable = nil
3535 end
3536 end
3537
3538 table.sort(tResults)
3539 return tResults
3540end
3541<End>
3542<Name:vector.lua>
3543<Begin>
3544--- The vector API provides methods to create and manipulate vectors.
3545--
3546-- An introduction to vectors can be found on [Wikipedia][wiki].
3547--
3548-- [wiki]: http://en.wikipedia.org/wiki/Euclidean_vector
3549--
3550-- @module vector
3551
3552--- A 3-dimensional vector, with `x`, `y`, and `z` values.
3553--
3554-- This is suitable for representing both position and directional vectors.
3555--
3556-- @type Vector
3557local vector = {
3558 --- Adds two vectors together.
3559 --
3560 -- @tparam Vector self The first vector to add.
3561 -- @tparam Vector o The second vector to add.
3562 -- @treturn Vector The resulting vector
3563 -- @usage v1:add(v2)
3564 -- @usage v1 + v2
3565 add = function(self, o)
3566 return vector.new(
3567 self.x + o.x,
3568 self.y + o.y,
3569 self.z + o.z
3570 )
3571 end,
3572
3573 --- Subtracts one vector from another.
3574 --
3575 -- @tparam Vector self The vector to subtract from.
3576 -- @tparam Vector o The vector to subtract.
3577 -- @treturn Vector The resulting vector
3578 -- @usage v1:sub(v2)
3579 -- @usage v1 - v2
3580 sub = function(self, o)
3581 return vector.new(
3582 self.x - o.x,
3583 self.y - o.y,
3584 self.z - o.z
3585 )
3586 end,
3587
3588 --- Multiplies a vector by a scalar value.
3589 --
3590 -- @tparam Vector self The vector to multiply.
3591 -- @tparam number m The scalar value to multiply with.
3592 -- @treturn Vector A vector with value `(x * m, y * m, z * m)`.
3593 -- @usage v:mul(3)
3594 -- @usage v * 3
3595 mul = function(self, m)
3596 return vector.new(
3597 self.x * m,
3598 self.y * m,
3599 self.z * m
3600 )
3601 end,
3602
3603 --- Divides a vector by a scalar value.
3604 --
3605 -- @tparam Vector self The vector to divide.
3606 -- @tparam number m The scalar value to divide by.
3607 -- @treturn Vector A vector with value `(x / m, y / m, z / m)`.
3608 -- @usage v:div(3)
3609 -- @usage v / 3
3610 div = function(self, m)
3611 return vector.new(
3612 self.x / m,
3613 self.y / m,
3614 self.z / m
3615 )
3616 end,
3617
3618 --- Negate a vector
3619 --
3620 -- @tparam Vector self The vector to negate.
3621 -- @treturn Vector The negated vector.
3622 -- @usage -v
3623 unm = function(self)
3624 return vector.new(
3625 -self.x,
3626 -self.y,
3627 -self.z
3628 )
3629 end,
3630
3631 --- Compute the dot product of two vectors
3632 --
3633 -- @tparam Vector self The first vector to compute the dot product of.
3634 -- @tparam Vector o The second vector to compute the dot product of.
3635 -- @treturn Vector The dot product of `self` and `o`.
3636 -- @usage v1:dot(v2)
3637 dot = function(self, o)
3638 return self.x * o.x + self.y * o.y + self.z * o.z
3639 end,
3640
3641 --- Compute the cross product of two vectors
3642 --
3643 -- @tparam Vector self The first vector to compute the cross product of.
3644 -- @tparam Vector o The second vector to compute the cross product of.
3645 -- @treturn Vector The cross product of `self` and `o`.
3646 -- @usage v1:cross(v2)
3647 cross = function(self, o)
3648 return vector.new(
3649 self.y * o.z - self.z * o.y,
3650 self.z * o.x - self.x * o.z,
3651 self.x * o.y - self.y * o.x
3652 )
3653 end,
3654
3655 --- Get the length (also referred to as magnitude) of this vector.
3656 -- @tparam Vector self This vector.
3657 -- @treturn number The length of this vector.
3658 length = function(self)
3659 return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
3660 end,
3661
3662 --- Divide this vector by its length, producing with the same direction, but
3663 -- of length 1.
3664 --
3665 -- @tparam Vector self The vector to normalise
3666 -- @treturn Vector The normalised vector
3667 -- @usage v:normalize()
3668 normalize = function(self)
3669 return self:mul(1 / self:length())
3670 end,
3671
3672 --- Construct a vector with each dimension rounded to the nearest value.
3673 --
3674 -- @tparam Vector self The vector to round
3675 -- @tparam[opt] number tolerance The tolerance that we should round to,
3676 -- defaulting to 1. For instance, a tolerance of 0.5 will round to the
3677 -- nearest 0.5.
3678 -- @treturn Vector The rounded vector.
3679 round = function(self, tolerance)
3680 tolerance = tolerance or 1.0
3681 return vector.new(
3682 math.floor((self.x + tolerance * 0.5) / tolerance) * tolerance,
3683 math.floor((self.y + tolerance * 0.5) / tolerance) * tolerance,
3684 math.floor((self.z + tolerance * 0.5) / tolerance) * tolerance
3685 )
3686 end,
3687
3688 --- Convert this vector into a string, for pretty printing.
3689 --
3690 -- @tparam Vector self This vector.
3691 -- @treturn string This vector's string representation.
3692 -- @usage v:tostring()
3693 -- @usage tostring(v)
3694 tostring = function(self)
3695 return self.x .. "," .. self.y .. "," .. self.z
3696 end,
3697}
3698
3699local vmetatable = {
3700 __index = vector,
3701 __add = vector.add,
3702 __sub = vector.sub,
3703 __mul = vector.mul,
3704 __div = vector.div,
3705 __unm = vector.unm,
3706 __tostring = vector.tostring,
3707}
3708
3709--- Construct a new @{Vector} with the given coordinates.
3710--
3711-- @tparam number x The X coordinate or direction of the vector.
3712-- @tparam number y The Y coordinate or direction of the vector.
3713-- @tparam number z The Z coordinate or direction of the vector.
3714-- @treturn Vector The constructed vector.
3715function new(x, y, z)
3716 return setmetatable({
3717 x = tonumber(x) or 0,
3718 y = tonumber(y) or 0,
3719 z = tonumber(z) or 0,
3720 }, vmetatable)
3721end
3722<End>
3723<Name:window.lua>
3724<Begin>
3725--- The Window API allows easy definition of spaces within the display that can
3726-- be written/drawn to, then later redrawn/repositioned/etc as need be. The API
3727-- itself contains only one function, @{window.create}, which returns the
3728-- windows themselves.
3729--
3730-- Windows are considered terminal objects - as such, they have access to nearly
3731-- all the commands in the term API (plus a few extras of their own, listed
3732-- within said API) and are valid targets to redirect to.
3733--
3734-- Each window has a "parent" terminal object, which can be the computer's own
3735-- display, a monitor, another window or even other, user-defined terminal
3736-- objects. Whenever a window is rendered to, the actual screen-writing is
3737-- performed via that parent (or, if that has one too, then that parent, and so
3738-- forth). Bear in mind that the cursor of a window's parent will hence be moved
3739-- around etc when writing a given child window.
3740--
3741-- Windows retain a memory of everything rendered "through" them (hence acting
3742-- as display buffers), and if the parent's display is wiped, the window's
3743-- content can be easily redrawn later. A window may also be flagged as
3744-- invisible, preventing any changes to it from being rendered until it's
3745-- flagged as visible once more.
3746--
3747-- A parent terminal object may have multiple children assigned to it, and
3748-- windows may overlap. For example, the Multishell system functions by
3749-- assigning each tab a window covering the screen, each using the starting
3750-- terminal display as its parent, and only one of which is visible at a time.
3751--
3752-- @module window
3753
3754local expect = dofile("rom/modules/main/cc/expect.lua").expect
3755
3756local tHex = {
3757 [colors.white] = "0",
3758 [colors.orange] = "1",
3759 [colors.magenta] = "2",
3760 [colors.lightBlue] = "3",
3761 [colors.yellow] = "4",
3762 [colors.lime] = "5",
3763 [colors.pink] = "6",
3764 [colors.gray] = "7",
3765 [colors.lightGray] = "8",
3766 [colors.cyan] = "9",
3767 [colors.purple] = "a",
3768 [colors.blue] = "b",
3769 [colors.brown] = "c",
3770 [colors.green] = "d",
3771 [colors.red] = "e",
3772 [colors.black] = "f",
3773}
3774
3775local type = type
3776local string_rep = string.rep
3777local string_sub = string.sub
3778
3779--- Returns a terminal object that is a space within the specified parent
3780-- terminal object. This can then be used (or even redirected to) in the same
3781-- manner as eg a wrapped monitor. Refer to @{term|the term API} for a list of
3782-- functions available to it.
3783--
3784-- @{term} itself may not be passed as the parent, though @{term.native} is
3785-- acceptable. Generally, @{term.current} or a wrapped monitor will be most
3786-- suitable, though windows may even have other windows assigned as their
3787-- parents.
3788--
3789-- @tparam term.Redirect parent The parent terminal redirect to draw to.
3790-- @tparam number nX The x coordinate this window is drawn at in the parent terminal
3791-- @tparam number nY The y coordinate this window is drawn at in the parent terminal
3792-- @tparam number nWidth The width of this window
3793-- @tparam number nHeight The height of this window
3794-- @tparam[opt] boolean bStartVisible Whether this window is visible by
3795-- default. Defaults to `true`.
3796-- @treturn Window The constructed window
3797function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
3798 expect(1, parent, "table")
3799 expect(2, nX, "number")
3800 expect(3, nY, "number")
3801 expect(4, nWidth, "number")
3802 expect(5, nHeight, "number")
3803 expect(6, bStartVisible, "boolean", "nil")
3804
3805 if parent == term then
3806 error("term is not a recommended window parent, try term.current() instead", 2)
3807 end
3808
3809 local sEmptySpaceLine
3810 local tEmptyColorLines = {}
3811 local function createEmptyLines(nWidth)
3812 sEmptySpaceLine = string_rep(" ", nWidth)
3813 for n = 0, 15 do
3814 local nColor = 2 ^ n
3815 local sHex = tHex[nColor]
3816 tEmptyColorLines[nColor] = string_rep(sHex, nWidth)
3817 end
3818 end
3819
3820 createEmptyLines(nWidth)
3821
3822 -- Setup
3823 local bVisible = bStartVisible ~= false
3824 local nCursorX = 1
3825 local nCursorY = 1
3826 local bCursorBlink = false
3827 local nTextColor = colors.white
3828 local nBackgroundColor = colors.black
3829 local tLines = {}
3830 local tPalette = {}
3831 do
3832 local sEmptyText = sEmptySpaceLine
3833 local sEmptyTextColor = tEmptyColorLines[nTextColor]
3834 local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
3835 for y = 1, nHeight do
3836 tLines[y] = {
3837 text = sEmptyText,
3838 textColor = sEmptyTextColor,
3839 backgroundColor = sEmptyBackgroundColor,
3840 }
3841 end
3842
3843 for i = 0, 15 do
3844 local c = 2 ^ i
3845 tPalette[c] = { parent.getPaletteColour(c) }
3846 end
3847 end
3848
3849 -- Helper functions
3850 local function updateCursorPos()
3851 if nCursorX >= 1 and nCursorY >= 1 and
3852 nCursorX <= nWidth and nCursorY <= nHeight then
3853 parent.setCursorPos(nX + nCursorX - 1, nY + nCursorY - 1)
3854 else
3855 parent.setCursorPos(0, 0)
3856 end
3857 end
3858
3859 local function updateCursorBlink()
3860 parent.setCursorBlink(bCursorBlink)
3861 end
3862
3863 local function updateCursorColor()
3864 parent.setTextColor(nTextColor)
3865 end
3866
3867 local function redrawLine(n)
3868 local tLine = tLines[n]
3869 parent.setCursorPos(nX, nY + n - 1)
3870 parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
3871 end
3872
3873 local function redraw()
3874 for n = 1, nHeight do
3875 redrawLine(n)
3876 end
3877 end
3878
3879 local function updatePalette()
3880 for k, v in pairs(tPalette) do
3881 parent.setPaletteColour(k, v[1], v[2], v[3])
3882 end
3883 end
3884
3885 local function internalBlit(sText, sTextColor, sBackgroundColor)
3886 local nStart = nCursorX
3887 local nEnd = nStart + #sText - 1
3888 if nCursorY >= 1 and nCursorY <= nHeight then
3889 if nStart <= nWidth and nEnd >= 1 then
3890 -- Modify line
3891 local tLine = tLines[nCursorY]
3892 if nStart == 1 and nEnd == nWidth then
3893 tLine.text = sText
3894 tLine.textColor = sTextColor
3895 tLine.backgroundColor = sBackgroundColor
3896 else
3897 local sClippedText, sClippedTextColor, sClippedBackgroundColor
3898 if nStart < 1 then
3899 local nClipStart = 1 - nStart + 1
3900 local nClipEnd = nWidth - nStart + 1
3901 sClippedText = string_sub(sText, nClipStart, nClipEnd)
3902 sClippedTextColor = string_sub(sTextColor, nClipStart, nClipEnd)
3903 sClippedBackgroundColor = string_sub(sBackgroundColor, nClipStart, nClipEnd)
3904 elseif nEnd > nWidth then
3905 local nClipEnd = nWidth - nStart + 1
3906 sClippedText = string_sub(sText, 1, nClipEnd)
3907 sClippedTextColor = string_sub(sTextColor, 1, nClipEnd)
3908 sClippedBackgroundColor = string_sub(sBackgroundColor, 1, nClipEnd)
3909 else
3910 sClippedText = sText
3911 sClippedTextColor = sTextColor
3912 sClippedBackgroundColor = sBackgroundColor
3913 end
3914
3915 local sOldText = tLine.text
3916 local sOldTextColor = tLine.textColor
3917 local sOldBackgroundColor = tLine.backgroundColor
3918 local sNewText, sNewTextColor, sNewBackgroundColor
3919 if nStart > 1 then
3920 local nOldEnd = nStart - 1
3921 sNewText = string_sub(sOldText, 1, nOldEnd) .. sClippedText
3922 sNewTextColor = string_sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor
3923 sNewBackgroundColor = string_sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor
3924 else
3925 sNewText = sClippedText
3926 sNewTextColor = sClippedTextColor
3927 sNewBackgroundColor = sClippedBackgroundColor
3928 end
3929 if nEnd < nWidth then
3930 local nOldStart = nEnd + 1
3931 sNewText = sNewText .. string_sub(sOldText, nOldStart, nWidth)
3932 sNewTextColor = sNewTextColor .. string_sub(sOldTextColor, nOldStart, nWidth)
3933 sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
3934 end
3935
3936 tLine.text = sNewText
3937 tLine.textColor = sNewTextColor
3938 tLine.backgroundColor = sNewBackgroundColor
3939 end
3940
3941 -- Redraw line
3942 if bVisible then
3943 redrawLine(nCursorY)
3944 end
3945 end
3946 end
3947
3948 -- Move and redraw cursor
3949 nCursorX = nEnd + 1
3950 if bVisible then
3951 updateCursorColor()
3952 updateCursorPos()
3953 end
3954 end
3955
3956 --- The window object. Refer to the @{window|module's documentation} for
3957 -- a full description.
3958 --
3959 -- @type Window
3960 -- @see term.Redirect
3961 local window = {}
3962
3963 function window.write(sText)
3964 sText = tostring(sText)
3965 internalBlit(sText, string_rep(tHex[nTextColor], #sText), string_rep(tHex[nBackgroundColor], #sText))
3966 end
3967
3968 function window.blit(sText, sTextColor, sBackgroundColor)
3969 if type(sText) ~= "string" then expect(1, sText, "string") end
3970 if type(sTextColor) ~= "string" then expect(2, sTextColor, "string") end
3971 if type(sBackgroundColor) ~= "string" then expect(3, sBackgroundColor, "string") end
3972 if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then
3973 error("Arguments must be the same length", 2)
3974 end
3975 internalBlit(sText, sTextColor, sBackgroundColor)
3976 end
3977
3978 function window.clear()
3979 if term.getGraphicsMode and term.getGraphicsMode() then return term.native().clear() end
3980 local sEmptyText = sEmptySpaceLine
3981 local sEmptyTextColor = tEmptyColorLines[nTextColor]
3982 local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
3983 for y = 1, nHeight do
3984 tLines[y] = {
3985 text = sEmptyText,
3986 textColor = sEmptyTextColor,
3987 backgroundColor = sEmptyBackgroundColor,
3988 }
3989 end
3990 if bVisible then
3991 redraw()
3992 updateCursorColor()
3993 updateCursorPos()
3994 end
3995 end
3996
3997 function window.clearLine()
3998 if nCursorY >= 1 and nCursorY <= nHeight then
3999 local sEmptyText = sEmptySpaceLine
4000 local sEmptyTextColor = tEmptyColorLines[nTextColor]
4001 local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
4002 tLines[nCursorY] = {
4003 text = sEmptyText,
4004 textColor = sEmptyTextColor,
4005 backgroundColor = sEmptyBackgroundColor,
4006 }
4007 if bVisible then
4008 redrawLine(nCursorY)
4009 updateCursorColor()
4010 updateCursorPos()
4011 end
4012 end
4013 end
4014
4015 function window.getCursorPos()
4016 return nCursorX, nCursorY
4017 end
4018
4019 function window.setCursorPos(x, y)
4020 if type(x) ~= "number" then expect(1, x, "number") end
4021 if type(y) ~= "number" then expect(2, y, "number") end
4022 nCursorX = math.floor(x)
4023 nCursorY = math.floor(y)
4024 if bVisible then
4025 updateCursorPos()
4026 end
4027 end
4028
4029 function window.setCursorBlink(blink)
4030 if type(blink) ~= "boolean" then expect(1, blink, "boolean") end
4031 bCursorBlink = blink
4032 if bVisible then
4033 updateCursorBlink()
4034 end
4035 end
4036
4037 function window.getCursorBlink()
4038 return bCursorBlink
4039 end
4040
4041 local function isColor()
4042 return parent.isColor()
4043 end
4044
4045 function window.isColor()
4046 return isColor()
4047 end
4048
4049 function window.isColour()
4050 return isColor()
4051 end
4052
4053 local function setTextColor(color)
4054 if type(color) ~= "number" then expect(1, color, "number") end
4055 if tHex[color] == nil then
4056 error("Invalid color (got " .. color .. ")" , 2)
4057 end
4058
4059 nTextColor = color
4060 if bVisible then
4061 updateCursorColor()
4062 end
4063 end
4064
4065 window.setTextColor = setTextColor
4066 window.setTextColour = setTextColor
4067
4068 function window.setPaletteColour(colour, r, g, b)
4069 if type(colour) ~= "number" then expect(1, colour, "number") end
4070 if term.getGraphicsMode and term.getGraphicsMode() then return term.native().setPaletteColor(colour, r, g, b) end
4071
4072 if tHex[colour] == nil then
4073 error("Invalid color (got " .. colour .. ")" , 2)
4074 end
4075
4076 local tCol
4077 if type(r) == "number" and g == nil and b == nil then
4078 tCol = { colours.unpackRGB(r) }
4079 tPalette[colour] = tCol
4080 else
4081 if type(r) ~= "number" then expect(2, r, "number") end
4082 if type(g) ~= "number" then expect(3, g, "number") end
4083 if type(b) ~= "number" then expect(4, b, "number") end
4084
4085 tCol = tPalette[colour]
4086 tCol[1] = r
4087 tCol[2] = g
4088 tCol[3] = b
4089 end
4090
4091 if bVisible then
4092 return parent.setPaletteColour(colour, tCol[1], tCol[2], tCol[3])
4093 end
4094 end
4095
4096 window.setPaletteColor = window.setPaletteColour
4097
4098 function window.getPaletteColour(colour)
4099 if type(colour) ~= "number" then expect(1, colour, "number") end
4100 if term.getGraphicsMode and term.getGraphicsMode() then return term.native().getPaletteColor(colour) end
4101 if tHex[colour] == nil then
4102 error("Invalid color (got " .. colour .. ")" , 2)
4103 end
4104 local tCol = tPalette[colour]
4105 return tCol[1], tCol[2], tCol[3]
4106 end
4107
4108 window.getPaletteColor = window.getPaletteColour
4109
4110 local function setBackgroundColor(color)
4111 if type(color) ~= "number" then expect(1, color, "number") end
4112 if tHex[color] == nil then
4113 error("Invalid color (got " .. color .. ")", 2)
4114 end
4115 nBackgroundColor = color
4116 end
4117
4118 window.setBackgroundColor = setBackgroundColor
4119 window.setBackgroundColour = setBackgroundColor
4120
4121 function window.getSize()
4122 return nWidth, nHeight
4123 end
4124
4125 function window.scroll(n)
4126 if type(n) ~= "number" then expect(1, n, "number") end
4127 if n ~= 0 then
4128 local tNewLines = {}
4129 local sEmptyText = sEmptySpaceLine
4130 local sEmptyTextColor = tEmptyColorLines[nTextColor]
4131 local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
4132 for newY = 1, nHeight do
4133 local y = newY + n
4134 if y >= 1 and y <= nHeight then
4135 tNewLines[newY] = tLines[y]
4136 else
4137 tNewLines[newY] = {
4138 text = sEmptyText,
4139 textColor = sEmptyTextColor,
4140 backgroundColor = sEmptyBackgroundColor,
4141 }
4142 end
4143 end
4144 tLines = tNewLines
4145 if bVisible then
4146 redraw()
4147 updateCursorColor()
4148 updateCursorPos()
4149 end
4150 end
4151 end
4152
4153 function window.getTextColor()
4154 return nTextColor
4155 end
4156
4157 function window.getTextColour()
4158 return nTextColor
4159 end
4160
4161 function window.getBackgroundColor()
4162 return nBackgroundColor
4163 end
4164
4165 function window.getBackgroundColour()
4166 return nBackgroundColor
4167 end
4168
4169 --- Get the buffered contents of a line in this window.
4170 --
4171 -- @tparam number y The y position of the line to get.
4172 -- @treturn string The textual content of this line.
4173 -- @treturn string The text colours of this line, suitable for use with @{term.blit}.
4174 -- @treturn string The background colours of this line, suitable for use with @{term.blit}.
4175 -- @throws If `y` is not between 1 and this window's height.
4176 function window.getLine(y)
4177 if type(y) ~= "number" then expect(1, y, "number") end
4178
4179 if y < 1 or y > nHeight then
4180 error("Line is out of range.", 2)
4181 end
4182
4183 return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
4184 end
4185
4186 -- Other functions
4187
4188 --- Set whether this window is visible. Invisible windows will not be drawn
4189 -- to the screen until they are made visible again.
4190 --
4191 -- Making an invisible window visible will immediately draw it.
4192 --
4193 -- @tparam boolean visible Whether this window is visible.
4194 function window.setVisible(visible)
4195 if type(visible) ~= "boolean" then expect(1, visible, "boolean") end
4196 if bVisible ~= visible then
4197 bVisible = visible
4198 if bVisible then
4199 window.redraw()
4200 end
4201 end
4202 end
4203
4204 --- Get whether this window is visible. Invisible windows will not be
4205 -- drawn to the screen until they are made visible again.
4206 --
4207 -- @treturn boolean Whether this window is visible.
4208 -- @see Window:setVisible
4209 function window.isVisible()
4210 return bVisible
4211 end
4212 --- Draw this window. This does nothing if the window is not visible.
4213 --
4214 -- @see Window:setVisible
4215 function window.redraw()
4216 if bVisible then
4217 redraw()
4218 updatePalette()
4219 updateCursorBlink()
4220 updateCursorColor()
4221 updateCursorPos()
4222 end
4223 end
4224
4225 --- Set the current terminal's cursor to where this window's cursor is. This
4226 -- does nothing if the window is not visible.
4227 function window.restoreCursor()
4228 if bVisible then
4229 updateCursorBlink()
4230 updateCursorColor()
4231 updateCursorPos()
4232 end
4233 end
4234
4235 --- Get the position of the top left corner of this window.
4236 --
4237 -- @treturn number The x position of this window.
4238 -- @treturn number The y position of this window.
4239 function window.getPosition()
4240 return nX, nY
4241 end
4242
4243 --- Reposition or resize the given window.
4244 --
4245 -- This function also accepts arguments to change the size of this window.
4246 -- It is recommended that you fire a `term_resize` event after changing a
4247 -- window's, to allow programs to adjust their sizing.
4248 --
4249 -- @tparam number new_x The new x position of this window.
4250 -- @tparam number new_y The new y position of this window.
4251 -- @tparam[opt] number new_width The new width of this window.
4252 -- @tparam number new_height The new height of this window.
4253 -- @tparam[opt] term.Redirect new_parent The new redirect object this
4254 -- window should draw to.
4255 function window.reposition(new_x, new_y, new_width, new_height, new_parent)
4256 if type(new_x) ~= "number" then expect(1, new_x, "number") end
4257 if type(new_y) ~= "number" then expect(2, new_y, "number") end
4258 if new_width ~= nil or new_height ~= nil then
4259 expect(3, new_width, "number")
4260 expect(4, new_height, "number")
4261 end
4262 if new_parent ~= nil and type(new_parent) ~= "table" then expect(5, new_parent, "table") end
4263
4264 nX = new_x
4265 nY = new_y
4266
4267 if new_parent then parent = new_parent end
4268
4269 if new_width and new_height then
4270 local tNewLines = {}
4271 createEmptyLines(new_width)
4272 local sEmptyText = sEmptySpaceLine
4273 local sEmptyTextColor = tEmptyColorLines[nTextColor]
4274 local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
4275 for y = 1, new_height do
4276 if y > nHeight then
4277 tNewLines[y] = {
4278 text = sEmptyText,
4279 textColor = sEmptyTextColor,
4280 backgroundColor = sEmptyBackgroundColor,
4281 }
4282 else
4283 local tOldLine = tLines[y]
4284 if new_width == nWidth then
4285 tNewLines[y] = tOldLine
4286 elseif new_width < nWidth then
4287 tNewLines[y] = {
4288 text = string_sub(tOldLine.text, 1, new_width),
4289 textColor = string_sub(tOldLine.textColor, 1, new_width),
4290 backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
4291 }
4292 else
4293 tNewLines[y] = {
4294 text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
4295 textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
4296 backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
4297 }
4298 end
4299 end
4300 end
4301 nWidth = new_width
4302 nHeight = new_height
4303 tLines = tNewLines
4304 end
4305 if bVisible then
4306 window.redraw()
4307 end
4308 end
4309
4310 if bVisible then
4311 window.redraw()
4312 end
4313 return window
4314end
4315<End>
4316