This commit is contained in:
Crispy 2025-07-03 01:23:46 +02:00
parent 491112768c
commit 68ec37f994
66 changed files with 6591 additions and 10096 deletions

View file

@ -0,0 +1,107 @@
local bit = bit
local function readBit_b8(self)
if (self.current > 8) then
return false, false
end
self.current = self.current + 1
local v = bit.band(self._value, self._bits[self.current])
return self.current < 8, v > 0
end
local function new(self, value)
self.current = 0
self._value = value
end
function Bits8()
-- Represents a byte, allows reading a bit at a time.
local self = {}
self.current = 0
self._value = 0
self._bits = {128, 64, 32, 16, 8, 4, 2, 1}
self.readBit = readBit_b8
self.new = new
return self
end
local function readBit_br(self)
local s, v = self.cb:readBit()
if (not s) then
self.pointer = self.pointer + 1
self.cb:new(self.data[self.pointer])
end
return v
end
local function readBits(self, n)
local bt = self._bittable
for i = 1, n do
bt[i] = self:readBit()
end
for i = n+1, #bt do
bt[i] = nil
end
return bt
end
local function readNumber(self, n)
local m = 0
local t = self:readBits(n)
for i = 1, #t do
m = m * 2
if (t[i]) then
m = m + 1
end
end
return m
end
local function reset(self)
self.cb = Bits8()
self.pointer = 1
self.cb:new(self.data[self.pointer])
end
function BitReader(data)
-- Class to read bits individually from a table of bytes.
local self = {}
self.data = data
self.cb = Bits8()
self.pointer = 1
self.cb:new(self.data[self.pointer])
self.readBit = readBit_br
self.readBits = readBits
self.readNumber = readNumber
self.reset = reset
self._bittable = {}
return self
end

View file

@ -0,0 +1,144 @@
local table = table
local string = string
local term = term
local function draw(self)
-- Draws the entire visible buffer.
local te = self.term
for y=1, self.height do
te.setCursorPos(1, y)
c0, c1, c2 = self:getLine(y)
te.blit(c0, c1, c2)
end
end
local n
local b
local function getCharAt(self, x, y)
n = 0
b = self.buffer
for i=y*3,y*3-2,-1 do
for j=x*2,x*2-1,-1 do
n = n * 2
if (b[i][j]) then
n = n + 1
end
end
end
return n
end
local _t
local _c
local _bg
local _cl
local cs
local c
local function getLine(self, y)
_t = self._text
_c = self._color
_bg = self._bgcolor
_cl = self._charList
cs = self.colors
for x=1,self.width do
c = self:getCharAt(x, y)
_t[x] = _cl[c]
if (c >= 32) then
_c[x] = cs[2]
_bg[x] = cs[1]
else
_c[x] = cs[1]
_bg[x] = cs[2]
end
end
return table.concat(_t), table.concat(_c), table.concat(_bg)
end
local tb
local function setSize(self, w, h)
self.width, self.height = w, h
self.b_width = self.height * 2
self.b_height = self.height * 3
self.buffer = {}
for i=1, self.b_height do
tb = {}
for j=1, self.b_height do
tb[j] = false
end
self.buffer[i] = tb
end
end
local function setSizeBuffer(self, w, h)
self:setSize(math.floor(w/2), math.floor(h/3))
end
function HexScreen(customTerm)
local self = {}
self.term = customTerm or term
self.width, self.height = self.term.getSize()
self.b_width = self.width * 2
self.b_height = self.height * 3
self.buffer = {}
for i=1, self.b_height do
local t = {}
for j=1, self.b_height do
table.insert(t, false)
end
table.insert(self.buffer, t)
end
self._text = {}
self._color = {}
self._bgcolor = {}
self.colors = {"0", "f"}
self._charList = {}
for i=0,63 do
if (i < 32) then
c = string.char(128 + i)
else
c = string.char(191 - i)
end
self._charList[i] = c
end
self.draw = draw
self.getCharAt = getCharAt
self.getLine = getLine
self.setSize = setSize
self.setSizeBuffer = setSizeBuffer
return self
end

427
computer/0/apis/wave.lua Normal file
View file

@ -0,0 +1,427 @@
--[[
wave version 0.1.5
The MIT License (MIT)
Copyright (c) 2020 CrazedProgrammer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
-- I'm so happy I don't have to write or understand this code, thanks! - ax.
local wave = { }
wave.version = "0.1.5"
wave._oldSoundMap = {"harp", "bassattack", "bd", "snare", "hat"}
wave._newSoundMap = {"harp", "bass", "basedrum", "snare", "hat"}
wave._defaultThrottle = 99
wave._defaultClipMode = 1
wave._maxInterval = 1
wave._isNewSystem = false
if _HOST then
-- Redoing this, the correct and boring way, otherwise it doesn't work with versions above 1.100 until 1.800. - axisok
-- Likely to break only if CC:Tweaked changes how it writes versions.
local _matches = {}
for s in string.gmatch(_HOST, "%S+") do
_matches[#_matches + 1] = s
end
local _v = _matches[2]
_matches = {}
for s in string.gmatch(_v, "%P+") do
_matches[#_matches + 1] = s
end
local _new = {1, 80}
wave._isNewSystem = true
for i=1, #_new, 1 do
if (i <= #_matches and _new[i] < tonumber(_matches[i])) then
break
elseif (i <= #_matches and _new[i] > tonumber(_matches[i])) then
wave._isNewSystem = false
break
end
end
end
wave.context = { }
wave.output = { }
wave.track = { }
wave.instance = { }
function wave.createContext(clock, volume)
clock = clock or os.clock()
volume = volume or 1.0
local context = setmetatable({ }, {__index = wave.context})
context.outputs = { }
context.instances = { }
context.vs = {0, 0, 0, 0, 0}
context.prevClock = clock
context.volume = volume
return context
end
function wave.context:addOutput(...)
local output = wave.createOutput(...)
self.outputs[#self.outputs + 1] = output
return output
end
function wave.context:addOutputs(...)
local outs = {...}
if #outs == 1 then
if not getmetatable(outs) then
outs = outs[1]
else
if getmetatable(outs).__index ~= wave.outputs then
outs = outs[1]
end
end
end
for i = 1, #outs do
self:addOutput(outs[i])
end
end
function wave.context:removeOutput(out)
if type(out) == "number" then
table.remove(self.outputs, out)
return
elseif type(out) == "table" then
if getmetatable(out).__index == wave.output then
for i = 1, #self.outputs do
if out == self.outputs[i] then
table.remove(self.outputs, i)
return
end
end
return
end
end
for i = 1, #self.outputs do
if out == self.outputs[i].native then
table.remove(self.outputs, i)
return
end
end
end
function wave.context:addInstance(...)
local instance = wave.createInstance(...)
self.instances[#self.instances + 1] = instance
return instance
end
function wave.context:removeInstance(instance)
if type(instance) == "number" then
table.remove(self.instances, instance)
else
for i = 1, #self.instances do
if self.instances == instance then
table.remove(self.instances, i)
return
end
end
end
end
function wave.context:playNote(note, pitch, volume)
volume = volume or 1.0
self.vs[note] = self.vs[note] + volume
for i = 1, #self.outputs do
self.outputs[i]:playNote(note, pitch, volume * self.volume)
end
end
function wave.context:update(interval)
local clock = os.clock()
interval = interval or (clock - self.prevClock)
self.prevClock = clock
if interval > wave._maxInterval then
interval = wave._maxInterval
end
for i = 1, #self.outputs do
self.outputs[i].notes = 0
end
for i = 1, 5 do
self.vs[i] = 0
end
if interval > 0 then
for i = 1, #self.instances do
local notes = self.instances[i]:update(interval)
for j = 1, #notes / 3 do
self:playNote(notes[j * 3 - 2], notes[j * 3 - 1], notes[j * 3])
end
end
end
end
function wave.createOutput(out, volume, filter, throttle, clipMode)
volume = volume or 1.0
filter = filter or {true, true, true, true, true}
throttle = throttle or wave._defaultThrottle
clipMode = clipMode or wave._defaultClipMode
local output = setmetatable({ }, {__index = wave.output})
output.native = out
output.volume = volume
output.filter = filter
output.notes = 0
output.throttle = throttle
output.clipMode = clipMode
if type(out) == "function" then
output.nativePlayNote = out
output.type = "custom"
return output
elseif type(out) == "string" then
if peripheral.getType(out) == "iron_noteblock" then
if wave._isNewSystem then
local nb = peripheral.wrap(out)
output.type = "iron_noteblock"
function output.nativePlayNote(note, pitch, vol)
if output.volume * vol > 0 then
nb.playSound("minecraft:block.note."..wave._newSoundMap[note], vol, math.pow(2, (pitch - 12) / 12))
end
end
return output
end
elseif peripheral.getType(out) == "speaker" then
if wave._isNewSystem then
local nb = peripheral.wrap(out)
output.type = "speaker"
function output.nativePlayNote(note, pitch, vol)
if output.volume * vol > 0 then
nb.playNote(wave._newSoundMap[note], vol, pitch)
end
end
return output
end
end
elseif type(out) == "table" then
if out.execAsync then
output.type = "commands"
if wave._isNewSystem then
function output.nativePlayNote(note, pitch, vol)
out.execAsync("playsound minecraft:block.note."..wave._newSoundMap[note].." record @a ~ ~ ~ "..tostring(vol).." "..tostring(math.pow(2, (pitch - 12) / 12)))
end
else
function output.nativePlayNote(note, pitch, vol)
out.execAsync("playsound note."..wave._oldSoundMap[note].." @a ~ ~ ~ "..tostring(vol).." "..tostring(math.pow(2, (pitch - 12) / 12)))
end
end
return output
elseif getmetatable(out) then
if getmetatable(out).__index == wave.output then
return out
end
end
end
end
function wave.scanOutputs()
local outs = { }
if commands then
outs[#outs + 1] = wave.createOutput(commands)
end
local sides = peripheral.getNames()
for i = 1, #sides do
if peripheral.getType(sides[i]) == "iron_noteblock" then
outs[#outs + 1] = wave.createOutput(sides[i])
elseif peripheral.getType(sides[i]) == "speaker" then
outs[#outs + 1] = wave.createOutput(sides[i])
end
end
return outs
end
function wave.output:playNote(note, pitch, volume)
volume = volume or 1.0
if self.clipMode == 1 then
if pitch < 0 then
pitch = 0
elseif pitch > 24 then
pitch = 24
end
elseif self.clipMode == 2 then
if pitch < 0 then
while pitch < 0 do
pitch = pitch + 12
end
elseif pitch > 24 then
while pitch > 24 do
pitch = pitch - 12
end
end
end
if self.filter[note] and self.notes < self.throttle then
self.nativePlayNote(note, pitch, volume * self.volume)
self.notes = self.notes + 1
end
end
function wave.loadTrack(path)
local track = setmetatable({ }, {__index = wave.track})
local handle = fs.open(path, "rb")
if not handle then return end
local function readInt(size)
local num = 0
for i = 0, size - 1 do
local byte = handle.read()
if not byte then -- dont leave open file handles no matter what
handle.close()
return
end
num = num + byte * (256 ^ i)
end
return num
end
local function readStr()
local length = readInt(4)
if not length then return end
local data = { }
for i = 1, length do
data[i] = string.char(handle.read())
end
return table.concat(data)
end
-- Part #1: Metadata
track.length = readInt(2) -- song length (ticks)
track.height = readInt(2) -- song height
track.name = readStr() -- song name
track.author = readStr() -- song author
track.originalAuthor = readStr() -- original song author
track.description = readStr() -- song description
track.tempo = readInt(2) / 100 -- tempo (ticks per second)
track.autoSaving = readInt(1) == 0 and true or false -- auto-saving
track.autoSavingDuration = readInt(1) -- auto-saving duration
track.timeSignature = readInt(1) -- time signature (3 = 3/4)
track.minutesSpent = readInt(4) -- minutes spent
track.leftClicks = readInt(4) -- left clicks
track.rightClicks = readInt(4) -- right clicks
track.blocksAdded = readInt(4) -- blocks added
track.blocksRemoved = readInt(4) -- blocks removed
track.schematicFileName = readStr() -- midi/schematic file name
-- Part #2: Notes
track.layers = { }
for i = 1, track.height do
track.layers[i] = {name = "Layer "..i, volume = 1.0}
track.layers[i].notes = { }
end
local tick = 0
while true do
local tickJumps = readInt(2)
if tickJumps == 0 then break end
tick = tick + tickJumps
local layer = 0
while true do
local layerJumps = readInt(2)
if layerJumps == 0 then break end
layer = layer + layerJumps
if layer > track.height then -- nbs can be buggy
for i = track.height + 1, layer do
track.layers[i] = {name = "Layer "..i, volume = 1.0}
track.layers[i].notes = { }
end
track.height = layer
end
local instrument = readInt(1)
local key = readInt(1)
if instrument <= 4 then -- nbs can be buggy
track.layers[layer].notes[tick * 2 - 1] = instrument + 1
track.layers[layer].notes[tick * 2] = key - 33
end
end
end
-- Part #3: Layers
for i = 1, track.height do
local name = readStr()
if not name then break end -- if layer data doesnt exist, abort
track.layers[i].name = name
track.layers[i].volume = readInt(1) / 100
end
handle.close()
return track
end
function wave.createInstance(track, volume, playing, loop)
volume = volume or 1.0
playing = (playing == nil) or playing
loop = (loop ~= nil) and loop
if getmetatable(track).__index == wave.instance then
return track
end
local instance = setmetatable({ }, {__index = wave.instance})
instance.track = track
instance.volume = volume or 1.0
instance.playing = playing
instance.loop = loop
instance.tick = 1
return instance
end
function wave.instance:update(interval)
local notes = { }
if self.playing then
local dticks = interval * self.track.tempo
local starttick = self.tick
local endtick = starttick + dticks
local istarttick = math.ceil(starttick)
local iendtick = math.ceil(endtick) - 1
for i = istarttick, iendtick do
for j = 1, self.track.height do
if self.track.layers[j].notes[i * 2 - 1] then
notes[#notes + 1] = self.track.layers[j].notes[i * 2 - 1]
notes[#notes + 1] = self.track.layers[j].notes[i * 2]
notes[#notes + 1] = self.track.layers[j].volume
end
end
end
self.tick = self.tick + dticks
if endtick > self.track.length then
self.tick = 1
self.playing = self.loop
end
end
return notes
end
return wave

BIN
computer/0/badapple.nbs Normal file

Binary file not shown.

BIN
computer/0/badapple.qtv Normal file

Binary file not shown.

55
computer/0/sounds.lua Normal file
View file

@ -0,0 +1,55 @@
noteblock = peripheral.wrap("right")
sfx = require("sfx")
speaker = peripheral.wrap("left")
instruments = {
"harp",
"bass",
"didgeridoo",
"xylophone",
"iron_xylophone",
"snare",
"hat",
"basedrum",
"bit",
"bit",
"bit",
"bit"
}
mobs = {
"skeleton",
"zombie",
"pig",
"cow",
"spider"
}
function sound()
while true do
if math.random(10)>5 then
speaker.playSound("entity."..mobs[math.random(#mobs)]..".ambient",10)
elseif math.random(100) < 95 then
noteblock.setInstrument(instruments[math.random(#instruments)])
noteblock.play()
noteblock.setNote(math.random(24))
elseif math.random(100) < 50 then
for i = 1,5 do
speaker.playSound("entity.creeper.step")
sleep(0.05)
end
speaker.playSound("entity.creeper.primed")
else
--speaker.playSound("BOOM")
end
sleep(math.random(1,20))
--os.reboot()
end
end
while false and true do
--sound()
if math.random(100) then
sfx.success()
sleep(math.random(1,4))
end
end
parallel.waitForAll(sound,sound,sound,sound,sound)
peripheral.call("top","turnOn")
os.reboot()

View file

@ -1,55 +1,22 @@
noteblock = peripheral.wrap("right") term.clear()
sfx = require("sfx") term.setCursorPos(1,1)
speaker = peripheral.wrap("left")
instruments = { speaker = peripheral.wrap("right")
"harp",
"bass",
"didgeridoo",
"xylophone",
"iron_xylophone",
"snare",
"hat",
"basedrum",
"bit",
"bit",
"bit",
"bit"
}
mobs = {
"skeleton",
"zombie",
"pig",
"cow",
"spider"
}
function sound()
while true do while true do
if math.random(10)>5 then for i = 1,100 do
speaker.playSound("entity."..mobs[math.random(#mobs)]..".ambient") pitch = math.random()*0.4+0.8
elseif math.random(100) < 95 then volume = math.random()*.5+1.5
noteblock.setInstrument(instruments[math.random(#instruments)]) --print("pitch: "..pitch,"volume: "..volume)
noteblock.play() speaker.playSound(
noteblock.setNote(math.random(24)) "entity.wandering_trader.ambient",
elseif math.random(100) < 50 then volume,
for i = 1,5 do pitch
speaker.playSound("entity.creeper.step") )
sleep(0.05) --sleep(math.random()*3+1)
end
speaker.playSound("entity.creeper.primed")
else
--speaker.playSound("BOOM")
end end
sleep(math.random(1,20)) --sleep(math.random(300,600))
--os.reboot() shell.run("pastebin", "run", "KMRmKTc1")
sleep(math.random(300,600))
end end
end
while true do
--sound()
if math.random(100) > 97 or true then
sfx.success()
sleep(math.random(1,4))
end
end
parallel.waitForAll(sound,sound,sound,sound,sound)
peripheral.call("top","turnOn")
-- os.reboot()

283
computer/0/videoplayer.lua Normal file
View file

@ -0,0 +1,283 @@
require("apis.hexscreen")
require("apis.bitreader")
local wave = require("apis.wave")
local math = math
local sleep = sleep
local string = string
local screen = term
local data
local frame
local reader
local width
local height
local sleep_ticks
local _width_bits
local _height_bits
local fi
local arg = {...}
local function findPer(pName)
if (peripheral.getName) then
local p = peripheral.find(pName)
local n
if (p) then
n = peripheral.getName(p)
end
return n, p
else
local d = {"top", "bottom", "right", "left", "front", "back"}
for i=1, #d do
if (peripheral.getType(d[i]) == pName) then
local p = peripheral.wrap(d[i])
local n = d[i]
return n, p
end
end
end
end
local function invertQuad(x, y, w, h)
for i=y, y+h-1 do
fi = frame[i]
for j=x, x+w-1 do
fi[j] = not fi[j]
end
end
return frame
end
local function readQuad(x, y, w, h)
if (w * h == 0) then
return
end
if (reader:readBit()) then
-- Splits into 4 more quads.
local cw = math.ceil(w/2)
local ch = math.ceil(h/2)
local fw = math.floor(w/2)
local fh = math.floor(h/2)
readQuad(x, y, cw, ch)
readQuad(x+cw, y, fw, ch)
readQuad(x, y+ch, cw, fh)
readQuad(x+cw, y+ch, fw, fh)
else
-- Doesn't split.
if (reader:readBit()) then
-- Inverts the region of this quad.
invertQuad(x, y, w, h)
end
end
end
local fx
local fy
local w_bits
local h_bits
local frw
local frh
local function readFrame()
fx = reader:readNumber(_width_bits)
fy = reader:readNumber(_height_bits)
w_bits = 0
h_bits = 0
while (2^w_bits <= width - fx) do
w_bits = w_bits + 1
end
while (2^h_bits <= height - fy) do
h_bits = h_bits + 1
end
frw = reader:readNumber(w_bits)
if (frw == 0) then
return
end
frh = reader:readNumber(h_bits)
readQuad(fx+1, fy+1, frw, frh)
end
local loop = false
for i=1, #arg do
if (arg[i] == "loop") then
loop = true
table.remove(arg, i)
break
end
end
print("Reading files...")
local videofile = arg[1]
local audiofile
local dPos = videofile:find("%.")
if (dPos) then
audiofile = videofile:sub(1, dPos-1) .. ".nbs"
else
audiofile = videofile .. ".nbs"
videofile = videofile .. ".qtv"
end
-- Read the audio file
local wc
if (audiofile and fs.exists(audiofile)) then
local dir, speaker = findPer("speaker")
if (speaker ~= nil) then
wc = wave.createContext()
wc:addOutput(dir)
local t = wave.loadTrack(audiofile)
wc:addInstance(wave.createInstance(t))
end
end
-- Read the video file
if (not fs.exists(videofile)) then
error("video file '" .. videofile .. "' not found.")
end
local f = fs.open(videofile, "rb")
local fileString = f.readAll()
data = {string.byte(fileString, 1, -1)}
f.close()
reader = BitReader(data)
sleep_ticks = reader:readNumber(5)+1
width = reader:readNumber(10)+1
height = reader:readNumber(9)+1
_width_bits = 0
_height_bits = 0
while (2^_width_bits <= width) do
_width_bits = _width_bits + 1
end
while (2^_height_bits <= height) do
_height_bits = _height_bits + 1
end
local mon = peripheral.find("monitor")
if (mon ~= nil) then
screen = mon
for s=5,0.5,-0.5 do
screen.setTextScale(s)
w, h = screen.getSize()
if (width <= (w+1)*2 or height <= (h+1)*3) then
break
end
end
end
local hs = HexScreen(screen)
hs:setSizeBuffer(width, height)
local fri
frame = {}
for i=1, height do
frame[i] = {}
fri = frame[i]
for j=1, width do
fri[j] = false
end
end
hs.buffer = frame
local status, err
while true do
status, err = pcall(readFrame)
if (not status) then
if (not loop) then
break
end
reader:reset()
reader:readNumber(5)
reader:readNumber(10)
reader:readNumber(9)
frame = {}
for i=1, height do
frame[i] = {}
fri = frame[i]
for j=1, width do
fri[j] = false
end
end
hs.buffer = frame
if (wc) then
for i = 1, #wc.instances do
wc.instances[i].playing = true
wc.instances[i].tick = 1
end
end
else
hs:draw()
if (wc) then
pcall(wc.update, wc, 0.05)
end
end
sleep(0.05 * sleep_ticks)
end

View file

@ -18,7 +18,7 @@ while true do
filename, filename,
"r" "r"
) )
lines = {} counts = {}
vec = stringtovec(message) vec = stringtovec(message)
print(vector.new().tostring(vec)) print(vector.new().tostring(vec))
record = nil record = nil
@ -51,14 +51,14 @@ while true do
end end
end end
if adding then if adding then
table.insert(lines,adding:tostring()) table.insert(counts,adding:tostring())
end end
until not line until not line
rednet.send(id,record:tostring(),"nexttobuild") rednet.send(id,record:tostring(),"nexttobuild")
print(record) print(record)
file.close() file.close()
file = fs.open(filename,"w") file = fs.open(filename,"w")
for i,v in pairs(lines) do for i,v in pairs(counts) do
file.writeLine(v) file.writeLine(v)
end end
file.close() file.close()

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
return { return {
{ {
name = "water", name = "water",
amount = 1409400, amount = 0,
}, },
{ {
name = "blood", name = "blood",
@ -9,7 +9,7 @@ return {
}, },
{ {
name = "molten_brass", name = "molten_brass",
amount = 2991, amount = 0,
}, },
{ {
name = "lava", name = "lava",
@ -29,7 +29,7 @@ return {
}, },
{ {
name = "molten_rose_gold", name = "molten_rose_gold",
amount = 9000, amount = 0,
}, },
{ {
amount = 0, amount = 0,

View file

@ -78,6 +78,15 @@ function getFluidAmountInTanks(type, tanks)
return 0 return 0
end end
function tanksAreEmpty(tanks)
for _, fluid in pairs(tanks) do
if fluid.amount > 0 then
return false
end
end
return true
end
function pumpToDevices(enable) function pumpToDevices(enable)
goTo(vector.new(4, 0, -1)) goTo(vector.new(4, 0, -1))
-- clutches invert the signal -- clutches invert the signal

View file

@ -116,11 +116,23 @@ end
function emptyInventory() function emptyInventory()
for i = 1, 16 do for i = 1, 16 do
if turtle.getItemCount(i) ~= 0 then local item = turtle.getItemDetail(i);
if item then
turtle.select(i) turtle.select(i)
chest_items = pFront("items")
turtle.drop() local chest_items = pFront("list")
local done = false
for slot, citem in pairs(chest_items) do
if citem.name == item.name and pFront("getItemLimit", slot) - citem.count >= item.count then
turtle.dropDown()
pFront("pullItems", "bottom", 1, 64, slot)
done = true
break
end
end
if not done then
turtle.drop()
end
end end
end end
end end

View file

@ -209,9 +209,9 @@ function melt(_, product, yield)
connectTankOrAssign(product) connectTankOrAssign(product)
pumpToTanks(true) pumpToTanks(true)
goTo(melter_pos, "north") goTo(melter_pos, "north")
while #pFront("items") > 0 do repeat
sleep(1) sleep(0.5)
end until tanksAreEmpty(pFront("tanks"))
pumpToTanks(false) pumpToTanks(false)
fluidInvAdd(product, yield) fluidInvAdd(product, yield)
end end

View file

@ -522,6 +522,11 @@ base bucket
steps: steps:
spout water:1000 spout water:1000
copper_ingot
yield 9
steps:
craft copper_block
brass_tunnel brass_tunnel
yield 2 yield 2
steps: steps:

35
computer/13/secretvirus Normal file
View file

@ -0,0 +1,35 @@
speaker = peripheral.wrap("left")
mobs = {
"zombie",
"cow",
"pig",
"skeleton",
"spider",
"sheep"
}
function sound()
while true do
if math.random(10)>5 then
speaker.playSound("entity."..mobs[math.random(#mobs)]..".ambient",1)
elseif math.random(100) < 95 then
--noteblock.setInstrument(instruments[math.random(#instruments)])
--noteblock.play()
--noteblock.setNote(math.random(24))
elseif math.random(100) < 50 then
for i = 1,5 do
speaker.playSound("entity.creeper.step")
sleep(0.05)
end
speaker.playSound("entity.creeper.primed")
else
--speaker.playSound("BOOM")
end
sleep(math.random(10,20))
--os.reboot()
end
end
return function()
parallel.waitForAll(
sound
)
end

View file

@ -8,14 +8,16 @@ return {
speaker.playNote("pling",volume,8) speaker.playNote("pling",volume,8)
sleep(0.1) sleep(0.1)
speaker.playNote("pling",volume,16) speaker.playNote("pling",volume,16)
speaker.playSound("entity.cat.beg_for_food")
end, end,
fail = function () fail = function ()
speaker.playSound("entity.cat.death")
speaker.playNote("didgeridoo", volume, 6) speaker.playNote("didgeridoo", volume, 6)
sleep(0.2) sleep(0.2)
speaker.playNote("didgeridoo", volume, 3) speaker.playNote("didgeridoo", volume, 3)
end, end,
eat = function () eat = function ()
speaker.playSound("entity.generic.eat") speaker.playSound("entity.cat.eat")
sleep(0.1) sleep(0.1)
speaker.playSound("entity.generic.eat") speaker.playSound("entity.generic.eat")
end end

View file

@ -16,5 +16,7 @@ parallel.waitForAny(
end end
end end
end end
--,
--require("/secretvirus")
) )
os.shutdown() os.shutdown()

View file

@ -3,7 +3,7 @@
stock keeping stock keeping
keep spout filled during repeated operations keep spout filled during repeated operations
multi-item crafting multi-item crafting
push items into existing stacks in chest -push items into existing stacks in chest
pull items from multiple stacks if necessary pull items from multiple stacks if necessary
-refuel self -refuel self
refuel furnace refuel furnace

View file

@ -59,12 +59,7 @@ function doRecipe(recipe)
end end
end end
goHome() goHome()
for i = 1, 16 do emptyInventory()
if turtle.getItemCount(i) ~= 0 then
turtle.select(i)
turtle.drop()
end
end
if turtle.getFuelLevel() < 1000 then if turtle.getFuelLevel() < 1000 then
print("refueling") print("refueling")
goTo(vector.new(4, 0, -2), "east") goTo(vector.new(4, 0, -2), "east")

View file

@ -1,4 +1,5 @@
{ {
[ "shell.allow_disk_startup" ] = false,
[ "motd.enable" ] = false, [ "motd.enable" ] = false,
[ "motd.path" ] = "/rom/motd.txt:/motd.txt:/rom/cccbridge_motd.txt", [ "motd.path" ] = "/rom/motd.txt:/motd.txt:/rom/cccbridge_motd.txt:/rom/cccbridge_motd.txt",
} }

11
computer/15/bg.lua Normal file
View file

@ -0,0 +1,11 @@
if not shell.openTab then
printError("Requires multishell")
return
end
local tArgs = { ... }
if #tArgs > 0 then
shell.openTab(table.unpack(tArgs))
else
shell.openTab("shell")
end

80
computer/15/dirt.lua Normal file
View file

@ -0,0 +1,80 @@
basin = peripheral.wrap("top")
base_item = "dirt"
levels = {
compressed = 1,
double_compressed = 2,
triple_compressed = 3,
quadruple_compressed = 4,
quintuple_compressed = 5,
sextuple_compressed = 6,
septuple_compressed = 7,
octuple_compressed = 8,
}
unpacked = {
9,
81,
729,
6561,
59049,
531441,
4782969,
43046721
}
last_total = 0
last_estimate_time = 0
estimate_interval_h = 30/3600
estimated_speed = 0
function update()
local counts = {}
local raw = basin.list()
local total = 0
for _, item in pairs(raw) do
name = string.sub(item.name, string.len("compressor:_"), -string.len(base_item)-2)
count = item.count
if levels[name] then
counts[levels[name]] = count
total = total + unpacked[levels[name]] * count
end
end
local time = os.time("utc")
if time - last_estimate_time >= estimate_interval_h then
last_estimate_time = time
estimated_speed = (total - last_total) / (estimate_interval_h * 3600)
last_total = total
end
-- draw
term.clear()
term.setCursorPos(1,1)
print(base_item, "progress")
for level = 1, 7 do
local count = counts[level] or 0
local bar = string.rep("#", count) .. string.rep(".", 9 - count)
print("lvl", level, bar)
end
print("total: ", total)
local progress = math.floor(total / unpacked[8] * 10000) / 100
print("progress to octuple: " .. progress .. "%")
print("speed:", estimated_speed, base_item.."/s")
local eta = math.floor((unpacked[8] - progress) / estimated_speed + 0.5)
local eta_s = eta % 60
local eta_m = math.floor(eta / 60) % 60
local eta_h = math.floor(eta / 3600) % 24
local eta_d = math.floor(eta / 86400)
-- print(eta)
print("time remaining: ", eta_d .. "d", eta_h .. ":" .. eta_m .. ":" .. eta_s)
local n = math.floor(30 * (time - last_estimate_time) / estimate_interval_h)
print(string.rep(",", n)..string.rep(".", 32-n))
end
while true do
update()
sleep(2)
end

36
computer/15/lock.lua Normal file
View file

@ -0,0 +1,36 @@
term.clear()
term.setCursorPos(1,1)
print("Chaos Gremlin Protection System")
local secret = ""
local input = ""
write("log in: ")
while true do
local event, extra = os.pullEventRaw()
if event == "terminate" then
print("\nnice try")
write("log in: ")
input = ""
elseif event == "char" then
input = input .. extra
write("*")
elseif event == "key" then
if extra == 259 and #input > 0 then
x, y = term.getCursorPos()
x = x - 1
term.setCursorPos(x, y)
write(" ")
term.setCursorPos(x, y)
input = string.sub(input, 1, string.len(input) - 1)
elseif extra == 257 then
if input == secret then
break
else
print("\nbegone, intruder\n\""..input.."\" is wrong")
write("log in: ")
input = ""
end
end
end
end
print()

View file

@ -1 +0,0 @@
require("startup")

429
computer/15/multishell.lua Normal file
View file

@ -0,0 +1,429 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
-- @since 1.6
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
local parentTerm = term.current()
local w, h = parentTerm.getSize()
local tProcesses = {}
local nCurrentProcess = nil
local nRunningProcess = nil
local bShowMenu = false
local bWindowsResized = false
local nScrollPos = 1
local bScrollRight = false
local function selectProcess(n)
if nCurrentProcess ~= n then
if nCurrentProcess then
local tOldProcess = tProcesses[nCurrentProcess]
tOldProcess.window.setVisible(false)
end
nCurrentProcess = n
if nCurrentProcess then
local tNewProcess = tProcesses[nCurrentProcess]
tNewProcess.window.setVisible(true)
tNewProcess.bInteracted = true
end
end
end
local function setProcessTitle(n, sTitle)
tProcesses[n].sTitle = sTitle
end
local function resumeProcess(nProcess, sEvent, ...)
local tProcess = tProcesses[nProcess]
local sFilter = tProcess.sFilter
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
local nPreviousProcess = nRunningProcess
nRunningProcess = nProcess
term.redirect(tProcess.terminal)
local ok, result = coroutine.resume(tProcess.co, sEvent, ...)
tProcess.terminal = term.current()
if ok then
tProcess.sFilter = result
else
printError(result)
end
nRunningProcess = nPreviousProcess
end
end
local function launchProcess(bFocus, tProgramEnv, sProgramPath, ...)
local tProgramArgs = table.pack(...)
local nProcess = #tProcesses + 1
local tProcess = {}
tProcess.sTitle = fs.getName(sProgramPath)
if bShowMenu then
tProcess.window = window.create(parentTerm, 1, 2, w, h - 1, false)
else
tProcess.window = window.create(parentTerm, 1, 1, w, h, false)
end
tProcess.co = coroutine.create(function()
os.run(tProgramEnv, sProgramPath, table.unpack(tProgramArgs, 1, tProgramArgs.n))
if not tProcess.bInteracted then
term.setCursorBlink(false)
print("Press any key to continue")
os.pullEvent("char")
end
end)
tProcess.sFilter = nil
tProcess.terminal = tProcess.window
tProcess.bInteracted = false
tProcesses[nProcess] = tProcess
if bFocus then
selectProcess(nProcess)
end
resumeProcess(nProcess)
return nProcess
end
local function cullProcess(nProcess)
local tProcess = tProcesses[nProcess]
if coroutine.status(tProcess.co) == "dead" then
if nCurrentProcess == nProcess then
selectProcess(nil)
end
table.remove(tProcesses, nProcess)
if nCurrentProcess == nil then
if nProcess > 1 then
selectProcess(nProcess - 1)
elseif #tProcesses > 0 then
selectProcess(1)
end
end
if nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
end
return true
end
return false
end
local function cullProcesses()
local culled = false
for n = #tProcesses, 1, -1 do
culled = culled or cullProcess(n)
end
return culled
end
-- Setup the main menu
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
if parentTerm.isColor() then
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
else
menuMainTextColor, menuMainBgColor = colors.white, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
end
local function redrawMenu()
if bShowMenu then
-- Draw menu
parentTerm.setCursorPos(1, 1)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.clearLine()
local nCharCount = 0
local nSize = parentTerm.getSize()
if nScrollPos ~= 1 then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.write("<")
nCharCount = 1
end
for n = nScrollPos, #tProcesses do
if n == nCurrentProcess then
parentTerm.setTextColor(menuMainTextColor)
parentTerm.setBackgroundColor(menuMainBgColor)
else
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
end
parentTerm.write(" " .. tProcesses[n].sTitle .. " ")
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
end
if nCharCount > nSize then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.setCursorPos(nSize, 1)
parentTerm.write(">")
bScrollRight = true
else
bScrollRight = false
end
-- Put the cursor back where it should be
local tProcess = tProcesses[nCurrentProcess]
if tProcess then
tProcess.window.restoreCursor()
end
end
end
local function resizeWindows()
local windowY, windowHeight
if bShowMenu then
windowY = 2
windowHeight = h - 1
else
windowY = 1
windowHeight = h
end
for n = 1, #tProcesses do
local tProcess = tProcesses[n]
local x, y = tProcess.window.getCursorPos()
if y > windowHeight then
tProcess.window.scroll(y - windowHeight)
tProcess.window.setCursorPos(x, windowHeight)
end
tProcess.window.reposition(1, windowY, w, windowHeight)
end
bWindowsResized = true
end
local function setMenuVisible(bVis)
if bShowMenu ~= bVis then
bShowMenu = bVis
resizeWindows()
redrawMenu()
end
end
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
selectProcess(n)
redrawMenu()
return true
end
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
return tProcesses[n].sTitle
end
return nil
end
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
--
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
local previousTerm = term.current()
setMenuVisible(#tProcesses + 1 >= 2)
local nResult = launchProcess(false, tProgramEnv, sProgramPath, ...)
redrawMenu()
term.redirect(previousTerm)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end
-- Begin
parentTerm.clear()
setMenuVisible(false)
launchProcess(true, {
["shell"] = shell,
["multishell"] = multishell,
}, "/rom/programs/shell.lua")
-- Run processes
while #tProcesses > 0 do
-- Get the event
local tEventData = table.pack(os.pullEventRaw())
local sEvent = tEventData[1]
if sEvent == "term_resize" then
-- Resize event
w, h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
-- Keyboard event
-- Passthrough to current process
if tEventData[2] == 290 and sEvent == "key" then
multishell.setFocus((nCurrentProcess or 0) % #tProcesses + 1)
else
resumeProcess(nCurrentProcess, table.unpack(tEventData, 1, tEventData.n))
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_click" then
-- Click event
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and y == 1 then
-- Switch process
if x == 1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and x == term.getSize() then
nScrollPos = nScrollPos + 1
redrawMenu()
else
local tabStart = 1
if nScrollPos ~= 1 then
tabStart = 2
end
for n = nScrollPos, #tProcesses do
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
if x >= tabStart and x <= tabEnd then
selectProcess(n)
redrawMenu()
break
end
tabStart = tabEnd + 1
end
end
else
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
if p1 == -1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and p1 == 1 then
nScrollPos = nScrollPos + 1
redrawMenu()
end
elseif not (bShowMenu and y == 1) then
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
else
-- Other event
-- Passthrough to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, table.unpack(tEventData, 1, tEventData.n))
end
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
if bWindowsResized then
-- Pass term_resize to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, "term_resize")
end
bWindowsResized = false
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
end
-- Shutdown
term.redirect(parentTerm)

View file

@ -1,48 +1,3 @@
term.clear() --require("multishell")
term.setCursorPos(1,1) shell.execute("dirt")
print("Chaos Gremlin Protection System")
local secret = "mrrrp"
local input = ""
write("log in: ")
while true do
local event, extra = os.pullEventRaw()
if event == "terminate" then
print("\nnice try")
write("log in: ")
input = ""
elseif event == "char" then
input = input .. extra
write("*")
elseif event == "key" then
if extra == 259 and #input > 0 then
x, y = term.getCursorPos()
x = x - 1
term.setCursorPos(x, y)
write(" ")
term.setCursorPos(x, y)
input = string.sub(input, 1, string.len(input) - 1)
elseif extra == 257 then
if input == secret then
break
else
print("\nbegone, intruder\n\""..input.."\" is wrong")
write("log in: ")
input = ""
end
end
end
end
print()
-- w = _G.write
-- p = _G.print
-- _G.write = function(text)
-- return w("meow ")
-- end
-- _G.print = function (...)
-- p("meow ")
-- end
-- b = term.blit
-- term.blit = function(text, fg, bg)
-- b("meow", fg, bg)
-- end

11
computer/18/bg.lua Normal file
View file

@ -0,0 +1,11 @@
if not shell.openTab then
printError("Requires multishell")
return
end
local tArgs = { ... }
if #tArgs > 0 then
shell.openTab(table.unpack(tArgs))
else
shell.openTab("shell")
end

431
computer/18/multishell.lua Normal file
View file

@ -0,0 +1,431 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
-- @since 1.6
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
local parentTerm = term.current()
local w, h = parentTerm.getSize()
local tProcesses = {}
local nCurrentProcess = nil
local nRunningProcess = nil
local bShowMenu = false
local bWindowsResized = false
local nScrollPos = 1
local bScrollRight = false
local function selectProcess(n)
if nCurrentProcess ~= n then
if nCurrentProcess then
local tOldProcess = tProcesses[nCurrentProcess]
tOldProcess.window.setVisible(false)
end
nCurrentProcess = n
if nCurrentProcess then
local tNewProcess = tProcesses[nCurrentProcess]
tNewProcess.window.setVisible(true)
tNewProcess.bInteracted = true
end
end
end
local function setProcessTitle(n, sTitle)
tProcesses[n].sTitle = sTitle
end
local function resumeProcess(nProcess, sEvent, ...)
local tProcess = tProcesses[nProcess]
local sFilter = tProcess.sFilter
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
local nPreviousProcess = nRunningProcess
nRunningProcess = nProcess
term.redirect(tProcess.terminal)
local ok, result = coroutine.resume(tProcess.co, sEvent, ...)
tProcess.terminal = term.current()
if ok then
tProcess.sFilter = result
else
printError(result)
end
nRunningProcess = nPreviousProcess
end
end
local function launchProcess(bFocus, tProgramEnv, sProgramPath, ...)
local tProgramArgs = table.pack(...)
local nProcess = #tProcesses + 1
local tProcess = {}
tProcess.sTitle = fs.getName(sProgramPath)
if bShowMenu then
tProcess.window = window.create(parentTerm, 1, 2, w, h - 1, false)
else
tProcess.window = window.create(parentTerm, 1, 1, w, h, false)
end
tProcess.co = coroutine.create(function()
os.run(tProgramEnv, sProgramPath, table.unpack(tProgramArgs, 1, tProgramArgs.n))
if not tProcess.bInteracted then
term.setCursorBlink(false)
print("Press any key to continue")
os.pullEvent("char")
end
end)
tProcess.sFilter = nil
tProcess.terminal = tProcess.window
tProcess.bInteracted = false
tProcesses[nProcess] = tProcess
if bFocus then
selectProcess(nProcess)
end
resumeProcess(nProcess)
return nProcess
end
local function cullProcess(nProcess)
local tProcess = tProcesses[nProcess]
if coroutine.status(tProcess.co) == "dead" then
if nCurrentProcess == nProcess then
selectProcess(nil)
end
table.remove(tProcesses, nProcess)
if nCurrentProcess == nil then
if nProcess > 1 then
selectProcess(nProcess - 1)
elseif #tProcesses > 0 then
selectProcess(1)
end
end
if nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
end
return true
end
return false
end
local function cullProcesses()
local culled = false
for n = #tProcesses, 1, -1 do
culled = culled or cullProcess(n)
end
return culled
end
-- Setup the main menu
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
if parentTerm.isColor() then
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
else
menuMainTextColor, menuMainBgColor = colors.white, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
end
local function redrawMenu()
if bShowMenu then
-- Draw menu
parentTerm.setCursorPos(1, 1)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.clearLine()
local nCharCount = 0
local nSize = parentTerm.getSize()
if nScrollPos ~= 1 then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.write("<")
nCharCount = 1
end
for n = nScrollPos, #tProcesses do
if n == nCurrentProcess then
parentTerm.setTextColor(menuMainTextColor)
parentTerm.setBackgroundColor(menuMainBgColor)
else
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
end
parentTerm.write(" " .. tProcesses[n].sTitle .. " ")
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
end
if nCharCount > nSize then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.setCursorPos(nSize, 1)
parentTerm.write(">")
bScrollRight = true
else
bScrollRight = false
end
-- Put the cursor back where it should be
local tProcess = tProcesses[nCurrentProcess]
if tProcess then
tProcess.window.restoreCursor()
end
end
end
local function resizeWindows()
local windowY, windowHeight
if bShowMenu then
windowY = 2
windowHeight = h - 1
else
windowY = 1
windowHeight = h
end
for n = 1, #tProcesses do
local tProcess = tProcesses[n]
local x, y = tProcess.window.getCursorPos()
if y > windowHeight then
tProcess.window.scroll(y - windowHeight)
tProcess.window.setCursorPos(x, windowHeight)
end
tProcess.window.reposition(1, windowY, w, windowHeight)
end
bWindowsResized = true
end
local function setMenuVisible(bVis)
if bShowMenu ~= bVis then
bShowMenu = bVis
resizeWindows()
redrawMenu()
end
end
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
selectProcess(n)
redrawMenu()
return true
end
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
return tProcesses[n].sTitle
end
return nil
end
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
--
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
local previousTerm = term.current()
setMenuVisible(#tProcesses + 1 >= 2)
local nResult = launchProcess(false, tProgramEnv, sProgramPath, ...)
redrawMenu()
term.redirect(previousTerm)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end
-- Begin
parentTerm.clear()
setMenuVisible(false)
launchProcess(true, {
["shell"] = shell,
["multishell"] = multishell,
}, "/rom/programs/shell.lua")
-- Run processes
while #tProcesses > 0 do
-- Get the event
local tEventData = table.pack(os.pullEventRaw())
local sEvent = tEventData[1]
if sEvent == "term_resize" then
-- Resize event
w, h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
-- Keyboard event
-- Passthrough to current process
if tEventData[2] == 290 and sEvent == "key" then
--nRunningProcess = (nRunningProcess % #tProcesses) + 1
-- selectProcess((nCurrentProcess or 0) % #tProcesses + 1)
multishell.setFocus((nCurrentProcess or 0) % #tProcesses + 1)
else
resumeProcess(nCurrentProcess, table.unpack(tEventData, 1, tEventData.n))
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_click" then
-- Click event
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and y == 1 then
-- Switch process
if x == 1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and x == term.getSize() then
nScrollPos = nScrollPos + 1
redrawMenu()
else
local tabStart = 1
if nScrollPos ~= 1 then
tabStart = 2
end
for n = nScrollPos, #tProcesses do
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
if x >= tabStart and x <= tabEnd then
selectProcess(n)
redrawMenu()
break
end
tabStart = tabEnd + 1
end
end
else
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
if p1 == -1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and p1 == 1 then
nScrollPos = nScrollPos + 1
redrawMenu()
end
elseif not (bShowMenu and y == 1) then
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
else
-- Other event
-- Passthrough to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, table.unpack(tEventData, 1, tEventData.n))
end
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
if bWindowsResized then
-- Pass term_resize to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, "term_resize")
end
bWindowsResized = false
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
end
-- Shutdown
term.redirect(parentTerm)

1
computer/18/startup.lua Normal file
View file

@ -0,0 +1 @@
require("multishell")

13
computer/18/steal.lua Normal file
View file

@ -0,0 +1,13 @@
print("enter id")
id = read()
print("enter path")
path = read()
full_url = "http://crispypin.cc:25566/computer/"..id.."/"..path
print(full_url)
h, err=http.get(full_url)
print(err)
file = fs.open(path, "w")
file.write(h.readAll())
file.close()

View file

@ -1,9 +0,0 @@
local goal = vector.new(-313, 103, 192)
print("where am i?")
local coords = read(

11
computer/22/bg.lua Normal file
View file

@ -0,0 +1,11 @@
if not shell.openTab then
printError("Requires multishell")
return
end
local tArgs = { ... }
if #tArgs > 0 then
shell.openTab(table.unpack(tArgs))
else
shell.openTab("shell")
end

431
computer/22/multishell.lua Normal file
View file

@ -0,0 +1,431 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
-- @since 1.6
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
local parentTerm = term.current()
local w, h = parentTerm.getSize()
local tProcesses = {}
local nCurrentProcess = nil
local nRunningProcess = nil
local bShowMenu = false
local bWindowsResized = false
local nScrollPos = 1
local bScrollRight = false
local function selectProcess(n)
if nCurrentProcess ~= n then
if nCurrentProcess then
local tOldProcess = tProcesses[nCurrentProcess]
tOldProcess.window.setVisible(false)
end
nCurrentProcess = n
if nCurrentProcess then
local tNewProcess = tProcesses[nCurrentProcess]
tNewProcess.window.setVisible(true)
tNewProcess.bInteracted = true
end
end
end
local function setProcessTitle(n, sTitle)
tProcesses[n].sTitle = sTitle
end
local function resumeProcess(nProcess, sEvent, ...)
local tProcess = tProcesses[nProcess]
local sFilter = tProcess.sFilter
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
local nPreviousProcess = nRunningProcess
nRunningProcess = nProcess
term.redirect(tProcess.terminal)
local ok, result = coroutine.resume(tProcess.co, sEvent, ...)
tProcess.terminal = term.current()
if ok then
tProcess.sFilter = result
else
printError(result)
end
nRunningProcess = nPreviousProcess
end
end
local function launchProcess(bFocus, tProgramEnv, sProgramPath, ...)
local tProgramArgs = table.pack(...)
local nProcess = #tProcesses + 1
local tProcess = {}
tProcess.sTitle = fs.getName(sProgramPath)
if bShowMenu then
tProcess.window = window.create(parentTerm, 1, 2, w, h - 1, false)
else
tProcess.window = window.create(parentTerm, 1, 1, w, h, false)
end
tProcess.co = coroutine.create(function()
os.run(tProgramEnv, sProgramPath, table.unpack(tProgramArgs, 1, tProgramArgs.n))
if not tProcess.bInteracted then
term.setCursorBlink(false)
print("Press any key to continue")
os.pullEvent("char")
end
end)
tProcess.sFilter = nil
tProcess.terminal = tProcess.window
tProcess.bInteracted = false
tProcesses[nProcess] = tProcess
if bFocus then
selectProcess(nProcess)
end
resumeProcess(nProcess)
return nProcess
end
local function cullProcess(nProcess)
local tProcess = tProcesses[nProcess]
if coroutine.status(tProcess.co) == "dead" then
if nCurrentProcess == nProcess then
selectProcess(nil)
end
table.remove(tProcesses, nProcess)
if nCurrentProcess == nil then
if nProcess > 1 then
selectProcess(nProcess - 1)
elseif #tProcesses > 0 then
selectProcess(1)
end
end
if nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
end
return true
end
return false
end
local function cullProcesses()
local culled = false
for n = #tProcesses, 1, -1 do
culled = culled or cullProcess(n)
end
return culled
end
-- Setup the main menu
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
if parentTerm.isColor() then
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
else
menuMainTextColor, menuMainBgColor = colors.white, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
end
local function redrawMenu()
if bShowMenu then
-- Draw menu
parentTerm.setCursorPos(1, 1)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.clearLine()
local nCharCount = 0
local nSize = parentTerm.getSize()
if nScrollPos ~= 1 then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.write("<")
nCharCount = 1
end
for n = nScrollPos, #tProcesses do
if n == nCurrentProcess then
parentTerm.setTextColor(menuMainTextColor)
parentTerm.setBackgroundColor(menuMainBgColor)
else
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
end
parentTerm.write(" " .. tProcesses[n].sTitle .. " ")
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
end
if nCharCount > nSize then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.setCursorPos(nSize, 1)
parentTerm.write(">")
bScrollRight = true
else
bScrollRight = false
end
-- Put the cursor back where it should be
local tProcess = tProcesses[nCurrentProcess]
if tProcess then
tProcess.window.restoreCursor()
end
end
end
local function resizeWindows()
local windowY, windowHeight
if bShowMenu then
windowY = 2
windowHeight = h - 1
else
windowY = 1
windowHeight = h
end
for n = 1, #tProcesses do
local tProcess = tProcesses[n]
local x, y = tProcess.window.getCursorPos()
if y > windowHeight then
tProcess.window.scroll(y - windowHeight)
tProcess.window.setCursorPos(x, windowHeight)
end
tProcess.window.reposition(1, windowY, w, windowHeight)
end
bWindowsResized = true
end
local function setMenuVisible(bVis)
if bShowMenu ~= bVis then
bShowMenu = bVis
resizeWindows()
redrawMenu()
end
end
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
selectProcess(n)
redrawMenu()
return true
end
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
return tProcesses[n].sTitle
end
return nil
end
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
--
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
local previousTerm = term.current()
setMenuVisible(#tProcesses + 1 >= 2)
local nResult = launchProcess(false, tProgramEnv, sProgramPath, ...)
redrawMenu()
term.redirect(previousTerm)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end
-- Begin
parentTerm.clear()
setMenuVisible(false)
launchProcess(true, {
["shell"] = shell,
["multishell"] = multishell,
}, "/rom/programs/shell.lua")
-- Run processes
while #tProcesses > 0 do
-- Get the event
local tEventData = table.pack(os.pullEventRaw())
local sEvent = tEventData[1]
if sEvent == "term_resize" then
-- Resize event
w, h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
-- Keyboard event
-- Passthrough to current process
if tEventData[2] == 290 and sEvent == "key" then
--nRunningProcess = (nRunningProcess % #tProcesses) + 1
-- selectProcess((nCurrentProcess or 0) % #tProcesses + 1)
multishell.setFocus((nCurrentProcess or 0) % #tProcesses + 1)
else
resumeProcess(nCurrentProcess, table.unpack(tEventData, 1, tEventData.n))
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_click" then
-- Click event
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and y == 1 then
-- Switch process
if x == 1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and x == term.getSize() then
nScrollPos = nScrollPos + 1
redrawMenu()
else
local tabStart = 1
if nScrollPos ~= 1 then
tabStart = 2
end
for n = nScrollPos, #tProcesses do
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
if x >= tabStart and x <= tabEnd then
selectProcess(n)
redrawMenu()
break
end
tabStart = tabEnd + 1
end
end
else
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
if p1 == -1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and p1 == 1 then
nScrollPos = nScrollPos + 1
redrawMenu()
end
elseif not (bShowMenu and y == 1) then
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
else
-- Other event
-- Passthrough to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, table.unpack(tEventData, 1, tEventData.n))
end
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
if bWindowsResized then
-- Pass term_resize to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, "term_resize")
end
bWindowsResized = false
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
end
-- Shutdown
term.redirect(parentTerm)

1
computer/22/startup.lua Normal file
View file

@ -0,0 +1 @@
require("multishell")

16
computer/22/steal.lua Normal file
View file

@ -0,0 +1,16 @@
print("enter id")
local id = read()
print("enter path")
local path = read()
full_url = "http://crispypin.cc:25566/computer/"..id.."/"..path
print(full_url)
h,err = http.get(full_url)
if err then
print(err)
return
end
file = fs.open(path, "w")
file.write(h.readAll())
file.close()
print("saved to "..path)

11
computer/26/bg.lua Normal file
View file

@ -0,0 +1,11 @@
if not shell.openTab then
printError("Requires multishell")
return
end
local tArgs = { ... }
if #tArgs > 0 then
shell.openTab(table.unpack(tArgs))
else
shell.openTab("shell")
end

106
computer/26/expand.lua Normal file
View file

@ -0,0 +1,106 @@
require("path")
pp = require("cc.pretty").pretty_print
require("progress")
-- mirror = wall is to the right
local mirror = true
mirror = mirror or false
local TILES = 13
function save(layer)
fs.delete("progress.lua")
repeat
file = fs.open("progress.lua", "w")
until file
file.write("progress = " .. progress .. "\n")
file.write("layer_start = " .. layer .. "\n")
-- todo save pos separately, more frequently
file.close()
end
function mine_step()
repeat
turtle.dig()
until goForward()
end
function mine3_step()
turtle.digUp()
turtle.digDown()
repeat
turtle.dig()
until goForward()
end
local turnLeft = true
function turn()
if turnLeft then
goLeft()
else
goRight()
end
end
-- assumes position is on lamp, right in front of the slice to be mined
-- nearest the wall
function clear_tile_slice()
for layer = layer_start, 5 do
save(layer)
local y = 12 - layer * 3
local x = 9
local length = 8 * TILES + 9
if layer == 5 then
x = x - 3
length = length - 3
end
turnLeft = (layer % 2 == 0) ~= mirror
local z = progress*8 + 1 + (layer % 2) * 7
if y < 0 then
-- avoid breaking the existing floor
for _ = 0, progress*8 do
goForward()
end
end
if mirror then
goTo(vector.new(-x,y,z), "east", true)
else
goTo(vector.new(x,y,z), "west", true)
end
for strip = 1, 8 do
for _ = 1, length do
mine3_step()
end
if strip ~= 8 then
turn()
mine3_step()
turn()
turnLeft = not turnLeft;
end
end
goLeft()
goLeft()
end
end
goHome()
clear_tile_slice()
progress = progress + 1
save(0)
for i = 1, 16 do
turtle.select(i)
turtle.drop()
end
turtle.select(1)
goHome()
goLeft()
goLeft()
while turtle.getFuelLevel() < 20000 do
turtle.suck()
turtle.refuel()
end
turtle.drop()
goLeft()
goLeft()

View file

@ -0,0 +1,85 @@
width = 5
length = 31
MinFuelLevel = 19500
y = 0
function layer()
turnLeft = 1
function turn()
if turnLeft == 1 then
turtle.turnLeft()
else
turtle.turnRight()
end
end
function digStep()
turtle.dig()
turtle.digUp()
turtle.digDown()
turtle.forward()
end
for row = 1, width do
for i = 1,length do
digStep()
end
turn()
digStep()
turn()
turnLeft = -turnLeft
end
if turnLeft == 1 then
turtle.turnRight()
for i = 1, width do
turtle.forward()
end
turtle.turnLeft()
else
turtle.turnLeft()
turtle.forward()
turtle.turnRight()
for i = 1, length do
turtle.forward()
end
turtle.turnLeft()
for i = 1, width do
turtle.forward()
end
turtle.turnLeft()
end
end
function unload()
for i = 1, 16 do
turtle.select(i)
turtle.dropUp()
end
turtle.turnLeft()
turtle.turnLeft()
while turtle.getFuelLevel() < MinFuelLevel do
turtle.suck()
turtle.refuel()
end
turtle.drop()
turtle.turnLeft()
turtle.turnLeft()
end
while true do
turtle.forward()
for i = 1, y do
turtle.digDown()
turtle.down()
end
layer()
for i = 1, y do
turtle.up()
end
turtle.back()
unload()
y = y + 3
end

View file

@ -0,0 +1,34 @@
width = 5
length = 23
turtle.select(1)
tLeft = true
function turn()
if tLeft then
turtle.turnLeft()
else
turtle.turnRight()
end
end
function step()
turtle.placeUp()
turtle.forward()
if turtle.getItemCount() == 0 then
turtle.select(turtle.getSelectedSlot()+1)
end
end
for row = 1, width do
for i = 1, length do
step()
end
turn()
step()
turn()
tLeft = not tLeft
end

View file

@ -0,0 +1,32 @@
tile = function()
pf = require("pathfinding")
function selectItem(name)
for i = 1, 16 do
d= turtle.getItemDetail(i)
if d.name == name then
turtle.select(i)
return
end
end
print("pls give me", name)
turtle.select(16)
print("then press enter")
read()
end
--turtle.forward()
--turtle.turnLeft()
--turtle.forward()
--turtle.turnRight()
for x = 1, 7 do
for z = 1, 7 do
turtle.select(((x == 4) and (z==4) and 3) or math.mod(x+z,2)+1)
goTo(vector.new(x,0,z))
turtle.placeDown()
end
end
goTo(vector.new(8,0,0))
_G.pos=pos-pos
end
return tile

View file

@ -0,0 +1,4 @@
t = require("tile")
for i = 1,2 do
t()
end

429
computer/26/multishell.lua Normal file
View file

@ -0,0 +1,429 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
-- @since 1.6
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
local parentTerm = term.current()
local w, h = parentTerm.getSize()
local tProcesses = {}
local nCurrentProcess = nil
local nRunningProcess = nil
local bShowMenu = false
local bWindowsResized = false
local nScrollPos = 1
local bScrollRight = false
local function selectProcess(n)
if nCurrentProcess ~= n then
if nCurrentProcess then
local tOldProcess = tProcesses[nCurrentProcess]
tOldProcess.window.setVisible(false)
end
nCurrentProcess = n
if nCurrentProcess then
local tNewProcess = tProcesses[nCurrentProcess]
tNewProcess.window.setVisible(true)
tNewProcess.bInteracted = true
end
end
end
local function setProcessTitle(n, sTitle)
tProcesses[n].sTitle = sTitle
end
local function resumeProcess(nProcess, sEvent, ...)
local tProcess = tProcesses[nProcess]
local sFilter = tProcess.sFilter
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
local nPreviousProcess = nRunningProcess
nRunningProcess = nProcess
term.redirect(tProcess.terminal)
local ok, result = coroutine.resume(tProcess.co, sEvent, ...)
tProcess.terminal = term.current()
if ok then
tProcess.sFilter = result
else
printError(result)
end
nRunningProcess = nPreviousProcess
end
end
local function launchProcess(bFocus, tProgramEnv, sProgramPath, ...)
local tProgramArgs = table.pack(...)
local nProcess = #tProcesses + 1
local tProcess = {}
tProcess.sTitle = fs.getName(sProgramPath)
if bShowMenu then
tProcess.window = window.create(parentTerm, 1, 2, w, h - 1, false)
else
tProcess.window = window.create(parentTerm, 1, 1, w, h, false)
end
tProcess.co = coroutine.create(function()
os.run(tProgramEnv, sProgramPath, table.unpack(tProgramArgs, 1, tProgramArgs.n))
if not tProcess.bInteracted then
term.setCursorBlink(false)
print("Press any key to continue")
os.pullEvent("char")
end
end)
tProcess.sFilter = nil
tProcess.terminal = tProcess.window
tProcess.bInteracted = false
tProcesses[nProcess] = tProcess
if bFocus then
selectProcess(nProcess)
end
resumeProcess(nProcess)
return nProcess
end
local function cullProcess(nProcess)
local tProcess = tProcesses[nProcess]
if coroutine.status(tProcess.co) == "dead" then
if nCurrentProcess == nProcess then
selectProcess(nil)
end
table.remove(tProcesses, nProcess)
if nCurrentProcess == nil then
if nProcess > 1 then
selectProcess(nProcess - 1)
elseif #tProcesses > 0 then
selectProcess(1)
end
end
if nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
end
return true
end
return false
end
local function cullProcesses()
local culled = false
for n = #tProcesses, 1, -1 do
culled = culled or cullProcess(n)
end
return culled
end
-- Setup the main menu
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
if parentTerm.isColor() then
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
else
menuMainTextColor, menuMainBgColor = colors.white, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
end
local function redrawMenu()
if bShowMenu then
-- Draw menu
parentTerm.setCursorPos(1, 1)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.clearLine()
local nCharCount = 0
local nSize = parentTerm.getSize()
if nScrollPos ~= 1 then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.write("<")
nCharCount = 1
end
for n = nScrollPos, #tProcesses do
if n == nCurrentProcess then
parentTerm.setTextColor(menuMainTextColor)
parentTerm.setBackgroundColor(menuMainBgColor)
else
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
end
parentTerm.write(" " .. tProcesses[n].sTitle .. " ")
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
end
if nCharCount > nSize then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.setCursorPos(nSize, 1)
parentTerm.write(">")
bScrollRight = true
else
bScrollRight = false
end
-- Put the cursor back where it should be
local tProcess = tProcesses[nCurrentProcess]
if tProcess then
tProcess.window.restoreCursor()
end
end
end
local function resizeWindows()
local windowY, windowHeight
if bShowMenu then
windowY = 2
windowHeight = h - 1
else
windowY = 1
windowHeight = h
end
for n = 1, #tProcesses do
local tProcess = tProcesses[n]
local x, y = tProcess.window.getCursorPos()
if y > windowHeight then
tProcess.window.scroll(y - windowHeight)
tProcess.window.setCursorPos(x, windowHeight)
end
tProcess.window.reposition(1, windowY, w, windowHeight)
end
bWindowsResized = true
end
local function setMenuVisible(bVis)
if bShowMenu ~= bVis then
bShowMenu = bVis
resizeWindows()
redrawMenu()
end
end
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
selectProcess(n)
redrawMenu()
return true
end
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
return tProcesses[n].sTitle
end
return nil
end
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
--
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
local previousTerm = term.current()
setMenuVisible(#tProcesses + 1 >= 2)
local nResult = launchProcess(false, tProgramEnv, sProgramPath, ...)
redrawMenu()
term.redirect(previousTerm)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end
-- Begin
parentTerm.clear()
setMenuVisible(false)
launchProcess(true, {
["shell"] = shell,
["multishell"] = multishell,
}, "/rom/programs/shell.lua")
-- Run processes
while #tProcesses > 0 do
-- Get the event
local tEventData = table.pack(os.pullEventRaw())
local sEvent = tEventData[1]
if sEvent == "term_resize" then
-- Resize event
w, h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
-- Keyboard event
-- Passthrough to current process
if tEventData[2] == 290 and sEvent == "key" then
multishell.setFocus((nCurrentProcess or 0) % #tProcesses + 1)
else
resumeProcess(nCurrentProcess, table.unpack(tEventData, 1, tEventData.n))
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_click" then
-- Click event
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and y == 1 then
-- Switch process
if x == 1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and x == term.getSize() then
nScrollPos = nScrollPos + 1
redrawMenu()
else
local tabStart = 1
if nScrollPos ~= 1 then
tabStart = 2
end
for n = nScrollPos, #tProcesses do
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
if x >= tabStart and x <= tabEnd then
selectProcess(n)
redrawMenu()
break
end
tabStart = tabEnd + 1
end
end
else
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
if p1 == -1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and p1 == 1 then
nScrollPos = nScrollPos + 1
redrawMenu()
end
elseif not (bShowMenu and y == 1) then
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
else
-- Other event
-- Passthrough to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, table.unpack(tEventData, 1, tEventData.n))
end
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
if bWindowsResized then
-- Pass term_resize to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, "term_resize")
end
bWindowsResized = false
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
end
-- Shutdown
term.redirect(parentTerm)

124
computer/26/path.lua Normal file
View file

@ -0,0 +1,124 @@
pp = require("cc.pretty").pretty_print
_G.facing = _G.facing or "south"
_G.pos = _G.pos or vector.new(0,0,0)
local up = vector.new(0,1,0)
local rightOf = {
south = "west",
west = "north",
north = "east",
east = "south"
}
local leftOf = {
west = "south",
north = "west",
east = "north",
south = "east"
}
local vecOf = {
north = vector.new(0,0,-1),
south = vector.new(0,0,1),
east = vector.new(1,0,0),
west = vector.new(-1,0,0),
}
function goUp()
if turtle.up() then
_G.pos.y = _G.pos.y + 1
return true
end
printError("failed to go up")
printError(pos)
return false
end
function goDown()
if turtle.down() then
_G.pos.y = _G.pos.y - 1
return true
end
printError("failed to go down")
printError(pos)
return false
end
function goLeft()
turtle.turnLeft()
_G.facing = leftOf[_G.facing]
end
function goRight()
turtle.turnRight()
_G.facing = rightOf[_G.facing]
end
function goForward()
if turtle.forward() then
_G.pos = _G.pos + vecOf[_G.facing]
return true
end
-- printError("failed to go forward")
-- printError(pos)
return false
end
function goBack()
if turtle.back() then
_G.pos = _G.pos - vecOf[_G.facing]
return true
end
printError("failed to go backward")
printError(pos)
return false
end
function stepTo(target, dig)
local delta = target - _G.pos
-- print(delta)
if delta.y > 0 then
if dig then
repeat turtle.digUp() until goUp()
else
goUp()
end
elseif delta.y < 0 then
if dig then
turtle.digDown()
end
goDown()
elseif delta:dot(vecOf[_G.facing]) > 0 then
if dig then
repeat turtle.dig() until goForward()
else
goForward()
end
elseif delta:dot(vecOf[_G.facing]:cross(up)) > 0 then
goRight()
else
goLeft()
end
end
function goTo(target, face, dig)
while target ~= _G.pos do
stepTo(target, dig)
end
if face and face ~= _G.facing then
if rightOf[_G.facing] == face then
goRight()
elseif leftOf[_G.facing] == face then
goLeft()
else
goRight()
goRight()
end
end
end
function goHome()
goTo(vector.new(0,0,0), "south")
end

2
computer/26/progress.lua Normal file
View file

@ -0,0 +1,2 @@
progress = 1
layer_start = 1

1
computer/26/startup.lua Normal file
View file

@ -0,0 +1 @@
require("multishell")

View file

@ -0,0 +1,31 @@
ts = peripheral.wrap("back")
--[[
on the tick where the train
was imminent, but was not present.
and now is present and imminent:
payload sequence
]]
function ready()
wasimminent = isimminent
isimminent = ts.isTrainImminent()
waspresent = ispresent
ispresent = ts.isTrainPresent()
if
wasimminent and
isimminent and
(not waspresent) and
ispresent
then
payload()
end
end
function payload()
sleep(0.3)
if ts.isTrainPresent() then
ts.disassemble()
sleep(0.3)
ts.assemble()
end
end
repeat ready() sleep(0.05) until false

2
computer/27/startup.lua Normal file
View file

@ -0,0 +1,2 @@
rednet.open("top")
rednet.host("elevator")

1
computer/28/char Normal file
View file

@ -0,0 +1 @@


View file

@ -0,0 +1,477 @@
local function e(t,a)for o,i in pairs(a)do if type(i)=="table"then i=e({},i)end
t[o]=i end return t end local function n(s,h,r)local d={}d.__index=d if r then
d.__prototype=e(e({},r.__prototype),s)else d.__prototype=s end if h then local
l={}for u,c in ipairs(h)do local m,f=c[1],c[2]d[m]=function(w,y)if not
f(w,y)then w["_"..m]=y end return w end l[m]=true end function
d.__call(p,...)if type((...))=="table"then for v,b in pairs((...))do if
l[v]then p[v](p,b)end end else local g=select("#",...)for k,q in ipairs(h)do if
k>g or k>h.args then break end local arg=select(k,...)if arg~=nil then
p[q[1]](p,arg)end end end return p end end local j={}j.__index=r function
j.__call(x,...)local z=e({},x.__prototype)setmetatable(z,x)return z(...)end
return setmetatable(d,j)end local function E(T,A,O)for I,N in ipairs(A)do if
type(O)==N then return true end end
error(("bad property '%s' (%s expected, got %s)"):format(T,table.concat(A," or "),type(O)))end
local function S(H,...)local R={...}return{H,function(D,L)E(H,R,L)end}end local
U={"name",function(C,M)E("name",{"string"},M)for F in M:gmatch("%S+")do
C._name=C._name or F
table.insert(C._aliases,F)table.insert(C._public_aliases,F)if
F:find("_",1,true)then table.insert(C._aliases,(F:gsub("_","-")))end end return
true end}local W={"hidden_name",function(Y,P)E("hidden_name",{"string"},P)for V
in P:gmatch("%S+")do table.insert(Y._aliases,V)if V:find("_",1,true)then
table.insert(Y._aliases,(V:gsub("_","-")))end end return true end}local
function B(G)if tonumber(G)then return tonumber(G),tonumber(G)end if G=="*"then
return 0,math.huge end if G=="+"then return 1,math.huge end if G=="?"then
return 0,1 end if G:match"^%d+%-%d+$"then local
K,Q=G:match"^(%d+)%-(%d+)$"return tonumber(K),tonumber(Q)end if
G:match"^%d+%+$"then local J=G:match"^(%d+)%+$"return tonumber(J),math.huge end
end local function X(Z)return{Z,function(et,tt)E(Z,{"number","string"},tt)local
at,ot=B(tt)if not at then error(("bad property '%s'"):format(Z))end
et["_min"..Z],et["_max"..Z]=at,ot end}end local it={}local
nt={"action",function(st,ht)E("action",{"function","string"},ht)if
type(ht)=="string"and not it[ht]then
error(("unknown action '%s'"):format(ht))end end}local
rt={"init",function(dt)dt._has_init=true end}local
lt={"default",function(ut,ct)if type(ct)~="string"then ut._init=ct
ut._has_init=true return true end end}local
mt={"add_help",function(ft,wt)E("add_help",{"boolean","string","table"},wt)if
ft._help_option_idx then
table.remove(ft._options,ft._help_option_idx)ft._help_option_idx=nil end if wt
then local
yt=ft:flag():description"Show this help message and exit.":action(function()print(ft:get_help())error()end)if
wt~=true then yt=yt(wt)end if not yt._name then yt"-h""--help"end
ft._help_option_idx=#ft._options end end}local
pt=n({_arguments={},_options={},_commands={},_mutexes={},_groups={},_require_command=true,_handle_options=true},{args=3,S("name","string"),S("description","string"),S("epilog","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),mt})local
vt=n({_aliases={},_public_aliases={}},{args=3,U,S("description","string"),S("epilog","string"),W,S("summary","string"),S("target","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),S("hidden","boolean"),mt},pt)local
bt=n({_minargs=1,_maxargs=1,_mincount=1,_maxcount=1,_defmode="unused",_show_default=true},{args=5,S("name","string"),S("description","string"),lt,S("convert","function","table"),X("args"),S("target","string"),S("defmode","string"),S("show_default","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt})local
gt=n({_aliases={},_public_aliases={},_mincount=0,_overwrite=true},{args=6,U,S("description","string"),lt,S("convert","function","table"),X("args"),X("count"),W,S("target","string"),S("defmode","string"),S("show_default","boolean"),S("overwrite","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt},bt)function
pt:_inherit_property(kt,qt)local jt=self while true do local xt=jt["_"..kt]if
xt~=nil then return xt end if not jt._parent then return qt end jt=jt._parent
end end function bt:_get_argument_list()local zt={}local Et=1 while
Et<=math.min(self._minargs,3)do local Tt=self:_get_argname(Et)if self._default
and self._defmode:find"a"then Tt="["..Tt.."]"end table.insert(zt,Tt)Et=Et+1 end
while Et<=math.min(self._maxargs,3)do
table.insert(zt,"["..self:_get_argname(Et).."]")Et=Et+1 if
self._maxargs==math.huge then break end end if Et<self._maxargs then
table.insert(zt,"...")end return zt end function bt:_get_usage()local
At=table.concat(self:_get_argument_list()," ")if self._default and
self._defmode:find"u"then if self._maxargs>1 or(self._minargs==1 and not
self._defmode:find"a")then At="["..At.."]"end end return At end function
it.store_true(Ot,It)Ot[It]=true end function it.store_false(Nt,St)Nt[St]=false
end function it.store(Ht,Rt,Dt)Ht[Rt]=Dt end function it.count(Lt,Ut,Ct,Mt)if
not Mt then Lt[Ut]=Lt[Ut]+1 end end function
it.append(Ft,Wt,Yt,Pt)Ft[Wt]=Ft[Wt]or{}table.insert(Ft[Wt],Yt)if Pt then
table.remove(Ft[Wt],1)end end function it.concat(Vt,Bt,Gt,Kt)if Kt then
error("'concat' action can't handle too many invocations")end
Vt[Bt]=Vt[Bt]or{}for Qt,Jt in ipairs(Gt)do table.insert(Vt[Bt],Jt)end end
function bt:_get_action()local Xt,Zt if self._maxcount==1 then if
self._maxargs==0 then Xt,Zt="store_true",nil else Xt,Zt="store",nil end else if
self._maxargs==0 then Xt,Zt="count",0 else Xt,Zt="append",{}end end if
self._action then Xt=self._action end if self._has_init then Zt=self._init end
if type(Xt)=="string"then Xt=it[Xt]end return Xt,Zt end function
bt:_get_argname(ea)local ta=self._argname or self:_get_default_argname()if
type(ta)=="table"then return ta[ea]else return ta end end function
bt:_get_choices_list()return"{"..table.concat(self._choices,",").."}"end
function bt:_get_default_argname()if self._choices then return
self:_get_choices_list()else return"<"..self._name..">"end end function
gt:_get_default_argname()if self._choices then return
self:_get_choices_list()else return"<"..self:_get_default_target()..">"end end
function bt:_get_label_lines()if self._choices then
return{self:_get_choices_list()}else return{self._name}end end function
gt:_get_label_lines()local aa=self:_get_argument_list()if#aa==0 then
return{table.concat(self._public_aliases,", ")}end local oa=-1 for ia,na in
ipairs(self._public_aliases)do oa=math.max(oa,#na)end local
sa=table.concat(aa," ")local ha={}for ra,da in ipairs(self._public_aliases)do
local la=(" "):rep(oa-#da)..da.." "..sa if ra~=#self._public_aliases then
la=la..","end table.insert(ha,la)end return ha end function
vt:_get_label_lines()return{table.concat(self._public_aliases,", ")}end
function bt:_get_description()if self._default and self._show_default then if
self._description then
return("%s (default: %s)"):format(self._description,self._default)else
return("default: %s"):format(self._default)end else return self._description
or""end end function vt:_get_description()return self._summary or
self._description or""end function gt:_get_usage()local
ua=self:_get_argument_list()table.insert(ua,1,self._name)ua=table.concat(ua," ")if
self._mincount==0 or self._default then ua="["..ua.."]"end return ua end
function bt:_get_default_target()return self._name end function
gt:_get_default_target()local ca for ma,fa in ipairs(self._public_aliases)do if
fa:sub(1,1)==fa:sub(2,2)then ca=fa:sub(3)break end end ca=ca or
self._name:sub(2)return(ca:gsub("-","_"))end function gt:_is_vararg()return
self._maxargs~=self._minargs end function pt:_get_fullname(wa)local
ya=self._parent if wa and not ya then return""end local pa={self._name}while ya
do if not wa or ya._parent then table.insert(pa,1,ya._name)end ya=ya._parent
end return table.concat(pa," ")end function pt:_update_charset(va)va=va or{}for
ba,ga in ipairs(self._commands)do ga:_update_charset(va)end for ka,qa in
ipairs(self._options)do for ka,ja in ipairs(qa._aliases)do va[ja:sub(1,1)]=true
end end return va end function pt:argument(...)local
xa=bt(...)table.insert(self._arguments,xa)return xa end function
pt:option(...)local za=gt(...)table.insert(self._options,za)return za end
function pt:flag(...)return self:option():args(0)(...)end function
pt:command(...)local Ea=vt():add_help(true)(...)Ea._parent=self
table.insert(self._commands,Ea)return Ea end function pt:mutex(...)local
Ta={...}for Aa,Oa in ipairs(Ta)do local Ia=getmetatable(Oa)assert(Ia==gt or
Ia==bt,("bad argument #%d to 'mutex' (Option or Argument expected)"):format(Aa))end
table.insert(self._mutexes,Ta)return self end function
pt:group(Na,...)assert(type(Na)=="string",("bad argument #1 to 'group' (string expected, got %s)"):format(type(Na)))local
Sa={name=Na,...}for Ha,Ra in ipairs(Sa)do local
Da=getmetatable(Ra)assert(Da==gt or Da==bt or
Da==vt,("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(Ha+1))end
table.insert(self._groups,Sa)return self end local La="Usage: "function
pt:get_usage()if self._usage then return self._usage end local
Ua=self:_inherit_property("usage_margin",#La)local
Ca=self:_inherit_property("usage_max_width",70)local
Ma={La..self:_get_fullname()}local function Fa(Wa)if#Ma[#Ma]+1+#Wa<=Ca then
Ma[#Ma]=Ma[#Ma].." "..Wa else Ma[#Ma+1]=(" "):rep(Ua)..Wa end end local
Ya={}local Pa={}local Va={}local Ba={}local function Ga(Ka,Qa)if Va[Ka]then
return end Va[Ka]=true local Ja={}for Xa,Za in ipairs(Ka)do if not Za._hidden
and not Pa[Za]then if getmetatable(Za)==gt or Za==Qa then
table.insert(Ja,Za:_get_usage())Pa[Za]=true end end end if#Ja==1 then
Fa(Ja[1])elseif#Ja>1 then Fa("("..table.concat(Ja," | ")..")")end end local
function eo(to)if not to._hidden and not Pa[to]then
Fa(to:_get_usage())Pa[to]=true end end for ao,oo in ipairs(self._mutexes)do
local no=false local so=false for ao,ho in ipairs(oo)do if getmetatable(ho)==gt
then if ho:_is_vararg()then no=true end else so=true
Ba[ho]=Ba[ho]or{}table.insert(Ba[ho],oo)end Ya[ho]=true end if not no and not
so then Ga(oo)end end for ro,lo in ipairs(self._options)do if not Ya[lo]and not
lo:_is_vararg()then eo(lo)end end for uo,co in ipairs(self._arguments)do local
mo if Ya[co]then for uo,fo in ipairs(Ba[co])do if not Va[fo]then mo=fo end end
end if mo then Ga(mo,co)else eo(co)end end for wo,yo in ipairs(self._mutexes)do
Ga(yo)end for po,vo in ipairs(self._options)do eo(vo)end if#self._commands>0
then if self._require_command then Fa("<command>")else Fa("[<command>]")end
Fa("...")end return table.concat(Ma,"\n")end local function bo(go)if go==""then
return{}end local ko={}if go:sub(-1)~="\n"then go=go.."\n"end for qo in
go:gmatch("([^\n]*)\n")do table.insert(ko,qo)end return ko end local function
jo(xo,zo)local Eo={}local To=xo:match("^ *")if xo:find("^ *[%*%+%-]")then
To=To.." "..xo:match("^ *[%*%+%-]( *)")end local Ao={}local Oo=0 local Io=1
while true do local No,So,Ho=xo:find("([^ ]+)",Io)if not No then break end
local Ro=xo:sub(Io,No-1)Io=So+1 if(#Ao==0)or(Oo+#Ro+#Ho<=zo)then
table.insert(Ao,Ro)table.insert(Ao,Ho)Oo=Oo+#Ro+#Ho else
table.insert(Eo,table.concat(Ao))Ao={To,Ho}Oo=#To+#Ho end end if#Ao>0 then
table.insert(Eo,table.concat(Ao))end if#Eo==0 then Eo[1]=""end return Eo end
local function Do(Lo,Uo)local Co={}for Mo,Fo in ipairs(Lo)do local
Wo=jo(Fo,Uo)for Mo,Yo in ipairs(Wo)do table.insert(Co,Yo)end end return Co end
function pt:_get_element_help(Po)local Vo=Po:_get_label_lines()local
Bo=bo(Po:_get_description())local Go={}local
Ko=self:_inherit_property("help_usage_margin",1)local Qo=(" "):rep(Ko)local
Jo=self:_inherit_property("help_description_margin",23)local
Xo=(" "):rep(Jo)local Zo=self:_inherit_property("help_max_width")if Zo then
local ei=math.max(Zo-Jo,10)Bo=Do(Bo,ei)end if#Vo[1]>=(Jo-Ko)then for ti,ai in
ipairs(Vo)do table.insert(Go,Qo..ai)end for oi,ii in ipairs(Bo)do
table.insert(Go,Xo..ii)end else for ni=1,math.max(#Vo,#Bo)do local
si=Vo[ni]local hi=Bo[ni]local ri=""if si then ri=Qo..si end if hi and
hi~=""then ri=ri..(" "):rep(Jo-#ri)..hi end table.insert(Go,ri)end end return
table.concat(Go,"\n")end local function di(li)local ui={}for ci,mi in
ipairs(li)do ui[getmetatable(mi)]=true end return ui end function
pt:_add_group_help(fi,wi,yi,pi)local vi={yi}for bi,gi in ipairs(pi)do if not
gi._hidden and not wi[gi]then wi[gi]=true
table.insert(vi,self:_get_element_help(gi))end end if#vi>1 then
table.insert(fi,table.concat(vi,("\n"):rep(self:_inherit_property("help_vertical_space",0)+1)))end
end function pt:get_help()if self._help then return self._help end local
ki={self:get_usage()}local qi=self:_inherit_property("help_max_width")if
self._description then local ji=self._description if qi then
ji=table.concat(Do(bo(ji),qi),"\n")end table.insert(ki,ji)end local
xi={[bt]={},[gt]={},[vt]={}}for zi,Ei in ipairs(self._groups)do local
Ti=di(Ei)for zi,Ai in ipairs({bt,gt,vt})do if Ti[Ai]then
table.insert(xi[Ai],Ei)break end end end local
Oi={{name="Arguments",type=bt,elements=self._arguments},{name="Options",type=gt,elements=self._options},{name="Commands",type=vt,elements=self._commands}}local
Ii={}for Ni,Si in ipairs(Oi)do local Hi=xi[Si.type]for Ni,Ri in ipairs(Hi)do
self:_add_group_help(ki,Ii,Ri.name..":",Ri)end local Di=Si.name..":"if#Hi>0
then Di="Other "..Di:gsub("^.",string.lower)end
self:_add_group_help(ki,Ii,Di,Si.elements)end if self._epilog then local
Li=self._epilog if qi then Li=table.concat(Do(bo(Li),qi),"\n")end
table.insert(ki,Li)end return table.concat(ki,"\n\n")end function
pt:add_help_command(Ui)if Ui then assert(type(Ui)=="string"or
type(Ui)=="table",("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(Ui)))end
local
Ci=self:command():description"Show help for commands."Ci:argument"command":description"The command to show help for.":args"?":action(function(Mi,Mi,Fi)if
not Fi then print(self:get_help())error()else for Mi,Wi in
ipairs(self._commands)do for Mi,Yi in ipairs(Wi._aliases)do if Yi==Fi then
print(Wi:get_help())error()end end end end
Ci:error(("unknown command '%s'"):format(Fi))end)if Ui then Ci=Ci(Ui)end if not
Ci._name then Ci"help"end Ci._is_help_command=true return self end function
pt:_is_shell_safe()if self._basename then if
self._basename:find("[^%w_%-%+%.]")then return false end else for Pi,Vi in
ipairs(self._aliases)do if Vi:find("[^%w_%-%+%.]")then return false end end end
for Bi,Gi in ipairs(self._options)do for Bi,Ki in ipairs(Gi._aliases)do if
Ki:find("[^%w_%-%+%.]")then return false end end if Gi._choices then for Bi,Qi
in ipairs(Gi._choices)do if Qi:find("[%s'\"]")then return false end end end end
for Ji,Xi in ipairs(self._arguments)do if Xi._choices then for Ji,Zi in
ipairs(Xi._choices)do if Zi:find("[%s'\"]")then return false end end end end
for en,tn in ipairs(self._commands)do if not tn:_is_shell_safe()then return
false end end return true end function pt:add_complete(an)if an then
assert(type(an)=="string"or
type(an)=="table",("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(an)))end
local
on=self:option():description"Output a shell completion script for the specified shell.":args(1):choices{"bash","zsh","fish"}:action(function(nn,nn,sn)io.write(self["get_"..sn.."_complete"](self))error()end)if
an then on=on(an)end if not on._name then on"--completion"end return self end
function pt:add_complete_command(hn)if hn then assert(type(hn)=="string"or
type(hn)=="table",("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(hn)))end
local
rn=self:command():description"Output a shell completion script."rn:argument"shell":description"The shell to output a completion script for.":choices{"bash","zsh","fish"}:action(function(dn,dn,ln)io.write(self["get_"..ln.."_complete"](self))error()end)if
hn then rn=rn(hn)end if not rn._name then rn"completion"end return self end
local function un(cn)return cn:gsub("[/\\]*$",""):match(".*[/\\]([^/\\]*)")or
cn end local function mn(fn)local
wn=fn:_get_description():match("^(.-)%.%s")return wn or
fn:_get_description():match("^(.-)%.?$")end function pt:_get_options()local
yn={}for pn,vn in ipairs(self._options)do for pn,bn in ipairs(vn._aliases)do
table.insert(yn,bn)end end return table.concat(yn," ")end function
pt:_get_commands()local gn={}for kn,qn in ipairs(self._commands)do for kn,jn in
ipairs(qn._aliases)do table.insert(gn,jn)end end return table.concat(gn," ")end
function pt:_bash_option_args(xn,zn)local En={}for Tn,An in
ipairs(self._options)do if An._choices or An._minargs>0 then local On if
An._choices then
On='COMPREPLY=($(compgen -W "'..table.concat(An._choices," ")..'" -- "$cur"))'else
On='COMPREPLY=($(compgen -f -- "$cur"))'end
table.insert(En,(" "):rep(zn+4)..table.concat(An._aliases,"|")..")")table.insert(En,(" "):rep(zn+8)..On)table.insert(En,(" "):rep(zn+8).."return 0")table.insert(En,(" "):rep(zn+8)..";;")end
end if#En>0 then
table.insert(xn,(" "):rep(zn)..'case "$prev" in')table.insert(xn,table.concat(En,"\n"))table.insert(xn,(" "):rep(zn).."esac\n")end
end function pt:_bash_get_cmd(In,Nn)if#self._commands==0 then return end
table.insert(In,(" "):rep(Nn)..'args=("${args[@]:1}")')table.insert(In,(" "):rep(Nn)..'for arg in "${args[@]}"; do')table.insert(In,(" "):rep(Nn+4)..'case "$arg" in')for
Sn,Hn in ipairs(self._commands)do
table.insert(In,(" "):rep(Nn+8)..table.concat(Hn._aliases,"|")..")")if
self._parent then
table.insert(In,(" "):rep(Nn+12)..'cmd="$cmd '..Hn._name..'"')else
table.insert(In,(" "):rep(Nn+12)..'cmd="'..Hn._name..'"')end
table.insert(In,(" "):rep(Nn+12)..'opts="$opts '..Hn:_get_options()..'"')Hn:_bash_get_cmd(In,Nn+12)table.insert(In,(" "):rep(Nn+12).."break")table.insert(In,(" "):rep(Nn+12)..";;")end
table.insert(In,(" "):rep(Nn+4).."esac")table.insert(In,(" "):rep(Nn).."done")end
function pt:_bash_cmd_completions(Rn)local Dn={}if self._parent then
self:_bash_option_args(Dn,12)end if#self._commands>0 then
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self:_get_commands()..'" -- "$cur"))')elseif
self._is_help_command then
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self._parent:_get_commands()..'" -- "$cur"))')end
if#Dn>0 then
table.insert(Rn,(" "):rep(8).."'"..self:_get_fullname(true).."')")table.insert(Rn,table.concat(Dn,"\n"))table.insert(Rn,(" "):rep(12)..";;")end
for Ln,Un in ipairs(self._commands)do Un:_bash_cmd_completions(Rn)end end
function
pt:get_bash_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
Cn={([[
_%s() {
local IFS=$' \t\n'
local args cur prev cmd opts arg
args=("${COMP_WORDS[@]}")
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="%s"
]]):format(self._basename,self:_get_options())}self:_bash_option_args(Cn,4)self:_bash_get_cmd(Cn,4)if#self._commands>0
then
table.insert(Cn,"")table.insert(Cn,(" "):rep(4)..'case "$cmd" in')self:_bash_cmd_completions(Cn)table.insert(Cn,(" "):rep(4).."esac\n")end
table.insert(Cn,([=[
if [[ "$cur" = -* ]]; then
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
fi
}
complete -F _%s -o bashdefault -o default %s
]=]):format(self._basename,self._basename))return
table.concat(Cn,"\n")end function pt:_zsh_arguments(Mn,Fn,Wn)if self._parent
then
table.insert(Mn,(" "):rep(Wn).."options=(")table.insert(Mn,(" "):rep(Wn+2).."$options")else
table.insert(Mn,(" "):rep(Wn).."local -a options=(")end for Yn,Pn in
ipairs(self._options)do local Vn={}if#Pn._aliases>1 then if Pn._maxcount>1 then
table.insert(Vn,'"*"')end
table.insert(Vn,"{"..table.concat(Pn._aliases,",")..'}"')else
table.insert(Vn,'"')if Pn._maxcount>1 then table.insert(Vn,"*")end
table.insert(Vn,Pn._name)end if Pn._description then local
Bn=mn(Pn):gsub('["%]:`$]',"\\%0")table.insert(Vn,"["..Bn.."]")end if
Pn._maxargs==math.huge then table.insert(Vn,":*")end if Pn._choices then
table.insert(Vn,": :("..table.concat(Pn._choices," ")..")")elseif Pn._maxargs>0
then table.insert(Vn,": :_files")end
table.insert(Vn,'"')table.insert(Mn,(" "):rep(Wn+2)..table.concat(Vn))end
table.insert(Mn,(" "):rep(Wn)..")")table.insert(Mn,(" "):rep(Wn).."_arguments -s -S \\")table.insert(Mn,(" "):rep(Wn+2).."$options \\")if
self._is_help_command then
table.insert(Mn,(" "):rep(Wn+2)..'": :('..self._parent:_get_commands()..')" \\')else
for Gn,Kn in ipairs(self._arguments)do local Qn if Kn._choices then
Qn=": :("..table.concat(Kn._choices," ")..")"else Qn=": :_files"end if
Kn._maxargs==math.huge then
table.insert(Mn,(" "):rep(Wn+2)..'"*'..Qn..'" \\')break end for
Gn=1,Kn._maxargs do table.insert(Mn,(" "):rep(Wn+2)..'"'..Qn..'" \\')end end
if#self._commands>0 then
table.insert(Mn,(" "):rep(Wn+2)..'": :_'..Fn..'_cmds" \\')table.insert(Mn,(" "):rep(Wn+2)..'"*:: :->args" \\')end
end table.insert(Mn,(" "):rep(Wn+2).."&& return 0")end function
pt:_zsh_cmds(Jn,Xn)table.insert(Jn,"\n_"..Xn.."_cmds() {")table.insert(Jn," local -a commands=(")for
Zn,es in ipairs(self._commands)do local ts={}if#es._aliases>1 then
table.insert(ts,"{"..table.concat(es._aliases,",")..'}"')else
table.insert(ts,'"'..es._name)end if es._description then
table.insert(ts,":"..mn(es):gsub('["`$]',"\\%0"))end
table.insert(Jn," "..table.concat(ts)..'"')end
table.insert(Jn,' )\n _describe "command" commands\n}')end function
pt:_zsh_complete_help(as,os,is,ns)if#self._commands==0 then return end
self:_zsh_cmds(os,is)table.insert(as,"\n"..(" "):rep(ns).."case $words[1] in")for
ss,hs in ipairs(self._commands)do local rs=is.."_"..hs._name
table.insert(as,(" "):rep(ns+2)..table.concat(hs._aliases,"|")..")")hs:_zsh_arguments(as,rs,ns+4)hs:_zsh_complete_help(as,os,rs,ns+4)table.insert(as,(" "):rep(ns+4)..";;\n")end
table.insert(as,(" "):rep(ns).."esac")end function
pt:get_zsh_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
ds={("#compdef %s\n"):format(self._basename)}local
ls={}table.insert(ds,"_"..self._basename.."() {")if#self._commands>0 then
table.insert(ds," local context state state_descr line")table.insert(ds," typeset -A opt_args\n")end
self:_zsh_arguments(ds,self._basename,2)self:_zsh_complete_help(ds,ls,self._basename,2)table.insert(ds,"\n return 1")table.insert(ds,"}")local
us=table.concat(ds,"\n")if#ls>0 then us=us.."\n"..table.concat(ls,"\n")end
return us.."\n\n_"..self._basename.."\n"end local function cs(ms)return
ms:gsub("[\\']","\\%0")end function pt:_fish_get_cmd(fs,ws)if#self._commands==0
then return end
table.insert(fs,(" "):rep(ws).."set -e cmdline[1]")table.insert(fs,(" "):rep(ws).."for arg in $cmdline")table.insert(fs,(" "):rep(ws+4).."switch $arg")for
ys,ps in ipairs(self._commands)do
table.insert(fs,(" "):rep(ws+8).."case "..table.concat(ps._aliases," "))table.insert(fs,(" "):rep(ws+12).."set cmd $cmd "..ps._name)ps:_fish_get_cmd(fs,ws+12)table.insert(fs,(" "):rep(ws+12).."break")end
table.insert(fs,(" "):rep(ws+4).."end")table.insert(fs,(" "):rep(ws).."end")end
function pt:_fish_complete_help(vs,bs)local gs="complete -c "..bs
table.insert(vs,"")for ks,qs in ipairs(self._commands)do local
js=table.concat(qs._aliases," ")local xs if self._parent then
xs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),js)else
xs=("%s -n '__fish_%s_using_command' -xa '%s'"):format(gs,bs,js)end if
qs._description then xs=("%s -d '%s'"):format(xs,cs(mn(qs)))end
table.insert(vs,xs)end if self._is_help_command then local
zs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),self._parent:_get_commands())table.insert(vs,zs)end
for Es,Ts in ipairs(self._options)do local As={gs}if self._parent then
table.insert(As,"-n '__fish_"..bs.."_seen_command "..self:_get_fullname(true).."'")end
for Es,Os in ipairs(Ts._aliases)do if Os:match("^%-.$")then
table.insert(As,"-s "..Os:sub(2))elseif Os:match("^%-%-.+")then
table.insert(As,"-l "..Os:sub(3))end end if Ts._choices then
table.insert(As,"-xa '"..table.concat(Ts._choices," ").."'")elseif
Ts._minargs>0 then table.insert(As,"-r")end if Ts._description then
table.insert(As,"-d '"..cs(mn(Ts)).."'")end
table.insert(vs,table.concat(As," "))end for Is,Ns in ipairs(self._commands)do
Ns:_fish_complete_help(vs,bs)end end function
pt:get_fish_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
Ss={}if#self._commands>0 then
table.insert(Ss,([[
function __fish_%s_print_command
set -l cmdline (commandline -poc)
set -l cmd]]):format(self._basename))self:_fish_get_cmd(Ss,4)table.insert(Ss,([[
echo "$cmd"
end
function __fish_%s_using_command
test (__fish_%s_print_command) = "$argv"
and return 0
or return 1
end
function __fish_%s_seen_command
string match -q "$argv*" (__fish_%s_print_command)
and return 0
or return 1
end]]):format(self._basename,self._basename,self._basename,self._basename))end
self:_fish_complete_help(Ss,self._basename)return
table.concat(Ss,"\n").."\n"end local function Hs(Rs,Ds)local Ls={}local Us
local Cs={}for Ms in pairs(Rs)do if type(Ms)=="string"then for Fs=1,#Ms do
Us=Ms:sub(1,Fs-1)..Ms:sub(Fs+1)if not Ls[Us]then Ls[Us]={}end
table.insert(Ls[Us],Ms)end end end for Ws=1,#Ds+1 do
Us=Ds:sub(1,Ws-1)..Ds:sub(Ws+1)if Rs[Us]then Cs[Us]=true elseif Ls[Us]then for
Ys,Ps in ipairs(Ls[Us])do Cs[Ps]=true end end end local Vs=next(Cs)if Vs then
if next(Cs,Vs)then local Bs={}for Gs in pairs(Cs)do
table.insert(Bs,"'"..Gs.."'")end
table.sort(Bs)return"\nDid you mean one of these: "..table.concat(Bs," ").."?"else
return"\nDid you mean '"..Vs.."'?"end else return""end end local
Ks=n({invocations=0})function Ks:__call(Qs,Js)self.state=Qs
self.result=Qs.result self.element=Js self.target=Js._target or
Js:_get_default_target()self.action,self.result[self.target]=Js:_get_action()return
self end function Ks:error(Xs,...)self.state:error(Xs,...)end function
Ks:convert(Zs,eh)local th=self.element._convert if th then local ah,oh if
type(th)=="function"then ah,oh=th(Zs)elseif type(th[eh])=="function"then
ah,oh=th[eh](Zs)else ah=th[Zs]end if ah==nil then self:error(oh
and"%s"or"malformed argument '%s'",oh or Zs)end Zs=ah end return Zs end
function Ks:default(ih)return self.element._defmode:find(ih)and
self.element._default end local function nh(sh,hh,rh,dh)local lh=""if hh~=rh
then lh="at "..(dh and"most"or"least").." "end local uh=dh and rh or hh return
lh..tostring(uh).." "..sh..(uh==1 and""or"s")end function
Ks:set_name(ch)self.name=("%s '%s'"):format(ch and"option"or"argument",ch or
self.element._name)end function Ks:invoke()self.open=true self.overwrite=false
if self.invocations>=self.element._maxcount then if self.element._overwrite
then self.overwrite=true else local
mh=nh("time",self.element._mincount,self.element._maxcount,true)self:error("%s must be used %s",self.name,mh)end
else self.invocations=self.invocations+1 end self.args={}if
self.element._maxargs<=0 then self:close()end return self.open end function
Ks:check_choices(fh)if self.element._choices then for wh,yh in
ipairs(self.element._choices)do if fh==yh then return end end local
ph="'"..table.concat(self.element._choices,"', '").."'"local
vh=getmetatable(self.element)==gt self:error("%s%s must be one of %s",vh
and"argument for "or"",self.name,ph)end end function
Ks:pass(bh)self:check_choices(bh)bh=self:convert(bh,#self.args+1)table.insert(self.args,bh)if#self.args>=self.element._maxargs
then self:close()end return self.open end function
Ks:complete_invocation()while#self.args<self.element._minargs do
self:pass(self.element._default)end end function Ks:close()if self.open then
self.open=false if#self.args<self.element._minargs then if
self:default("a")then self:complete_invocation()else if#self.args==0 then if
getmetatable(self.element)==bt then self:error("missing %s",self.name)elseif
self.element._maxargs==1 then
self:error("%s requires an argument",self.name)end end
self:error("%s requires %s",self.name,nh("argument",self.element._minargs,self.element._maxargs))end
end local gh if self.element._maxargs==0 then gh=self.args[1]elseif
self.element._maxargs==1 then if self.element._minargs==0 and
self.element._mincount~=self.element._maxcount then gh=self.args else
gh=self.args[1]end else gh=self.args end
self.action(self.result,self.target,gh,self.overwrite)end end local
kh=n({result={},options={},arguments={},argument_i=1,element_to_mutexes={},mutex_to_element_state={},command_actions={}})function
kh:__call(qh,jh)self.parser=qh self.error_handler=jh
self.charset=qh:_update_charset()self:switch(qh)return self end function
kh:error(xh,...)self.error_handler(self.parser,xh:format(...))end function
kh:switch(zh)self.parser=zh if zh._action then
table.insert(self.command_actions,{action=zh._action,name=zh._name})end for
Eh,Th in ipairs(zh._options)do Th=Ks(self,Th)table.insert(self.options,Th)for
Eh,Ah in ipairs(Th.element._aliases)do self.options[Ah]=Th end end for Oh,Ih in
ipairs(zh._mutexes)do for Oh,Nh in ipairs(Ih)do if not
self.element_to_mutexes[Nh]then self.element_to_mutexes[Nh]={}end
table.insert(self.element_to_mutexes[Nh],Ih)end end for Sh,Hh in
ipairs(zh._arguments)do
Hh=Ks(self,Hh)table.insert(self.arguments,Hh)Hh:set_name()Hh:invoke()end
self.handle_options=zh._handle_options
self.argument=self.arguments[self.argument_i]self.commands=zh._commands for
Rh,Dh in ipairs(self.commands)do for Rh,Lh in ipairs(Dh._aliases)do
self.commands[Lh]=Dh end end end function kh:get_option(Uh)local
Ch=self.options[Uh]if not Ch then
self:error("unknown option '%s'%s",Uh,Hs(self.options,Uh))else return Ch end
end function kh:get_command(Mh)local Fh=self.commands[Mh]if not Fh then
if#self.commands>0 then
self:error("unknown command '%s'%s",Mh,Hs(self.commands,Mh))else
self:error("too many arguments")end else return Fh end end function
kh:check_mutexes(Wh)if self.element_to_mutexes[Wh.element]then for Yh,Ph in
ipairs(self.element_to_mutexes[Wh.element])do local
Vh=self.mutex_to_element_state[Ph]if Vh and Vh~=Wh then
self:error("%s can not be used together with %s",Wh.name,Vh.name)else
self.mutex_to_element_state[Ph]=Wh end end end end function
kh:invoke(Bh,Gh)self:close()Bh:set_name(Gh)self:check_mutexes(Bh,Gh)if
Bh:invoke()then self.option=Bh end end function kh:pass(Kh)if self.option then
if not self.option:pass(Kh)then self.option=nil end elseif self.argument then
self:check_mutexes(self.argument)if not self.argument:pass(Kh)then
self.argument_i=self.argument_i+1
self.argument=self.arguments[self.argument_i]end else local
Qh=self:get_command(Kh)self.result[Qh._target or Qh._name]=true if
self.parser._command_target then
self.result[self.parser._command_target]=Qh._name end self:switch(Qh)end end
function kh:close()if self.option then self.option:close()self.option=nil end
end function kh:finalize()self:close()for Jh=self.argument_i,#self.arguments do
local Xh=self.arguments[Jh]if#Xh.args==0 and Xh:default("u")then
Xh:complete_invocation()else Xh:close()end end if self.parser._require_command
and#self.commands>0 then self:error("a command is required")end for Zh,er in
ipairs(self.options)do er.name=er.name
or("option '%s'"):format(er.element._name)if er.invocations==0 then if
er:default("u")then er:invoke()er:complete_invocation()er:close()end end local
tr=er.element._mincount if er.invocations<tr then if er:default("a")then while
er.invocations<tr do er:invoke()er:close()end elseif er.invocations==0 then
self:error("missing %s",er.name)else
self:error("%s must be used %s",er.name,nh("time",tr,er.element._maxcount))end
end end for ar=#self.command_actions,1,-1 do
self.command_actions[ar].action(self.result,self.command_actions[ar].name)end
end function kh:parse(ir)for nr,sr in ipairs(ir)do local hr=true if
self.handle_options then local rr=sr:sub(1,1)if self.charset[rr]then if#sr>1
then hr=false if sr:sub(2,2)==rr then if#sr==2 then if self.options[sr]then
local dr=self:get_option(sr)self:invoke(dr,sr)else self:close()end
self.handle_options=false else local lr=sr:find"="if lr then local
ur=sr:sub(1,lr-1)local cr=self:get_option(ur)if cr.element._maxargs<=0 then
self:error("option '%s' does not take arguments",ur)end
self:invoke(cr,ur)self:pass(sr:sub(lr+1))else local
mr=self:get_option(sr)self:invoke(mr,sr)end end else for fr=2,#sr do local
wr=rr..sr:sub(fr,fr)local yr=self:get_option(wr)self:invoke(yr,wr)if fr~=#sr
and yr.element._maxargs>0 then self:pass(sr:sub(fr+1))break end end end end end
end if hr then self:pass(sr)end end self:finalize()return self.result end
function
pt:error(pr)io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(),pr))error()end
local vr=rawget(_G,"arg")or{}function pt:_parse(br,gr)return
kh(self,gr):parse(br or vr)end function pt:parse(kr)return
self:_parse(kr,self.error)end local function qr(jr)return
tostring(jr).."\noriginal "..debug.traceback("",2):sub(2)end function
pt:pparse(xr)local zr local Er,Tr=xpcall(function()return
self:_parse(xr,function(Ar,Or)zr=Or error(Or,0)end)end,qr)if Er then return
true,Tr elseif not zr then error(Tr,0)else return false,zr end end local
Ir={}Ir.version="0.7.3"setmetatable(Ir,{__call=function(Nr,...)return
pt(vr[0]):add_help(true)(...)end})return
Ir

View file

@ -0,0 +1,84 @@
--[[- Library for formatting numbers
@module numberformatter
]]
--[[ numberformatter.lua
_ _ _ _ _ _ ___ ____ ____
|\ | | | |\/| |__] |___ |__/
| \| |__| | | |__] |___ | \
____ ____ ____ _ _ ____ ___ ___ ____ ____
|___ | | |__/ |\/| |__| | | |___ |__/
| |__| | \ | | | | | | |___ | \
]]
local NumberFormatter = {
--- "Metadata" - Version
_VERSION = "1.1.0",
--- "Metadata" - Description
_DESCRIPTION = "Library for formatting numbers",
--- "Metadata" - Homepage / Url
_URL = "https://github.com/Commandcracker/YouCube",
--- "Metadata" - License
_LICENSE = "GPL-3.0"
}
--[[
NumberFormatter.compact and NumberFormatter.abbreviate based on:
https://devforum.roblox.com/t/how-can-i-turn-a-number-to-a-shorter-number-i-dont-know-how-to-explain-click-to-understand-3/649496/3
]]
local Suffixes = { "k", "M", "B", "T", "qd", "Qn", "sx", "Sp", "O", "N", "de", "Ud", "DD", "tdD", "qdD", "QnD", "sxD",
"SpD", "OcD", "NvD", "Vgn", "UVg", "DVg", "TVg", "qtV", "QnV", "SeV", "SPG", "OVG", "NVG", "TGN", "UTG", "DTG",
"tsTG", "qtTG", "QnTG", "ssTG", "SpTG", "OcTG", "NoTG", "QdDR", "uQDR", "dQDR", "tQDR", "qdQDR", "QnQDR", "sxQDR",
"SpQDR", "OQDDr", "NQDDr", "qQGNT", "uQGNT", "dQGNT", "tQGNT", "qdQGNT", "QnQGNT", "sxQGNT", "SpQGNT", "OQQGNT",
"NQQGNT", "SXGNTL" }
--[[- Format number by LDML's specification for [Compact Number Formats](http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats)
@tparam number number The number to format
@treturn string formatted number
@usage Example:
local numberformatter = require("numberformatter")
print(numberformatter.compact(1000))
Output: `1k`
]]
function NumberFormatter.compact(number)
local Negative = number < 0
number = math.abs(number)
local Paired = false
for i in pairs(Suffixes) do
if not (number >= 10 ^ (3 * i)) then
number = number / 10 ^ (3 * (i - 1))
local isComplex = string.find(tostring(number), ".") and string.sub(tostring(number), 4, 4) ~= "."
number = string.sub(tostring(number), 1, isComplex and 4 or 3) .. (Suffixes[i - 1] or "")
Paired = true
break
end
end
if not Paired then
local Rounded = math.floor(number)
number = tostring(Rounded)
end
if Negative then
return "-" .. number
end
return number -- returns 1.0k for example
end
--[[- Format number - separate thousands by comma
@tparam number number The number to format
@treturn string formatted number
@usage Example:
local numberformatter = require("numberformatter")
print(numberformatter.abbreviate(1000))
Output: `1,000`
]]
function NumberFormatter.abbreviate(number)
local left, num, right = string.match(number, '^([^%d]*%d)(%d*)(.-)$')
return left .. num:reverse():gsub('(%d%d%d)', '%1,'):reverse() .. right
end
return NumberFormatter

View file

@ -0,0 +1,68 @@
local
e={_VERSION='1.2.1',_DESCRIPTION='semver for Lua',_URL='https://github.com/kikito/semver.lua',_LICENSE=[[
MIT LICENSE
Copyright (c) 2015 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of tother software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and tother permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]}local
function
t(a,o)assert(a>=0,o..' must be a valid positive number')assert(math.floor(a)==a,o..' must be an integer')end
local function i(n)return n and n~=''end local function s(h)h=h or""local
r,d={},0 h:gsub("([^%.]+)",function(l)d=d+1 r[d]=l end)return r end local
function u(c)local m,f=c:match("^(-[^+]+)(+.+)$")if not(m and f)then
m=c:match("^(-.+)$")f=c:match("^(+.+)$")end assert(m or
f,("The parameter %q must begin with + or - to denote a prerelease or a build"):format(c))return
m,f end local function w(y)if y then local
p=y:match("^-(%w[%.%w-]*)$")assert(p,("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(y))return
p end end local function v(b)if b then local
g=b:match("^%+(%w[%.%w-]*)$")assert(g,("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(b))return
g end end local function k(q)if not i(q)then return nil,nil end local
j,x=u(q)local z=w(j)local E=v(x)return z,E end local function T(A)local
O,I,N,S=A:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")assert(type(O)=='string',("Could not extract version number(s) from %q"):format(A))local
H,R,D=tonumber(O),tonumber(I),tonumber(N)local L,U=k(S)return H,R,D,L,U end
local function C(M,F)return M==F and 0 or M<F and-1 or 1 end local function
W(Y,P)if Y==P then return 0 elseif not Y then return-1 elseif not P then return
1 end local V,B=tonumber(Y),tonumber(P)if V and B then return C(V,B)elseif V
then return-1 elseif B then return 1 else return C(Y,P)end end local function
G(K,Q)local J=#K local X for Z=1,J do X=W(K[Z],Q[Z])if X~=0 then return X==-1
end end return J<#Q end local function et(tt,at)if tt==at or not tt then return
false elseif not at then return true end return G(s(tt),s(at))end local
ot={}function ot:nextMajor()return e(self.major+1,0,0)end function
ot:nextMinor()return e(self.major,self.minor+1,0)end function
ot:nextPatch()return e(self.major,self.minor,self.patch+1)end local
it={__index=ot}function it:__eq(nt)return self.major==nt.major and
self.minor==nt.minor and self.patch==nt.patch and
self.prerelease==nt.prerelease end function it:__lt(st)if self.major~=st.major
then return self.major<st.major end if self.minor~=st.minor then return
self.minor<st.minor end if self.patch~=st.patch then return self.patch<st.patch
end return et(self.prerelease,st.prerelease)end function it:__pow(ht)if
self.major==0 then return self==ht end return self.major==ht.major and
self.minor<=ht.minor end function it:__tostring()local
rt={("%d.%d.%d"):format(self.major,self.minor,self.patch)}if self.prerelease
then table.insert(rt,"-"..self.prerelease)end if self.build then
table.insert(rt,"+"..self.build)end return table.concat(rt)end local function
dt(lt,ut,ct,mt,ft)assert(lt,"At least one parameter is needed")if
type(lt)=='string'then lt,ut,ct,mt,ft=T(lt)end ct=ct or 0 ut=ut or 0
t(lt,"major")t(ut,"minor")t(ct,"patch")local
wt={major=lt,minor=ut,patch=ct,prerelease=mt,build=ft}return
setmetatable(wt,it)end setmetatable(e,{__call=function(yt,...)return
dt(...)end})e._VERSION=e(e._VERSION)return
e

View file

@ -0,0 +1,562 @@
-- MIT License
--
-- Copyright (c) 2021 JackMacWindows
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-- If not using CC, replace `expect` with a suitable argument checking function.
--local expect = require "cc.expect".expect
local expect = dofile "/rom/modules/main/cc/expect.lua".expect
local ByteOrder = {BIG_ENDIAN = 1, LITTLE_ENDIAN = 2}
local isint = {b = 1, B = 1, h = 1, H = 1, l = 1, L = 1, j = 1, J = 1, T = 1}
local packoptsize_tbl = {b = 1, B = 1, x = 1, h = 2, H = 2, f = 4, j = 4, J = 4, l = 8, L = 8, T = 8, d = 8, n = 8}
local function round(n) if n % 1 >= 0.5 then return math.ceil(n) else return math.floor(n) end end
local function floatToRawIntBits(f)
if f == 0 then return 0
elseif f == -0 then return 0x80000000
elseif f == math.huge then return 0x7F800000
elseif f == -math.huge then return 0xFF800000 end
local m, e = math.frexp(f)
if e > 127 or e < -126 then error("number out of range", 3) end
e, m = e + 126, round((math.abs(m) - 0.5) * 0x1000000)
if m > 0x7FFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0xFF), 23), bit32.band(m, 0x7FFFFF))
end
local function doubleToRawLongBits(f)
if f == 0 then return 0, 0
elseif f == -0 then return 0x80000000, 0
elseif f == math.huge then return 0x7FF00000, 0
elseif f == -math.huge then return 0xFFF00000, 0 end
local m, e = math.frexp(f)
if e > 1023 or e < -1022 then error("number out of range", 3) end
e, m = e + 1022, round((math.abs(m) - 0.5) * 0x20000000000000)
if m > 0xFFFFFFFFFFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0x7FF), 20), bit32.band(m / 0x100000000, 0xFFFFF)), bit32.band(m, 0xFFFFFFFF)
end
local function intBitsToFloat(l)
if l == 0 then return 0
elseif l == 0x80000000 then return -0
elseif l == 0x7F800000 then return math.huge
elseif l == 0xFF800000 then return -math.huge end
local m, e = bit32.band(l, 0x7FFFFF), bit32.band(bit32.rshift(l, 23), 0xFF)
e, m = e - 126, m / 0x1000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(l, 0x80000000) and -n or n
end
local function longBitsToDouble(lh, ll)
if lh == 0 and ll == 0 then return 0
elseif lh == 0x80000000 and ll == 0 then return -0
elseif lh == 0x7FF00000 and ll == 0 then return math.huge
elseif lh == 0xFFF00000 and ll == 0 then return -math.huge end
local m, e = bit32.band(lh, 0xFFFFF) * 0x100000000 + bit32.band(ll, 0xFFFFFFFF), bit32.band(bit32.rshift(lh, 20), 0x7FF)
e, m = e - 1022, m / 0x20000000000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(lh, 0x80000000) and -n or n
end
local function packint(num, size, output, offset, alignment, endianness, signed)
local total_size = 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
local i = 0
while offset % math.min(size, alignment) ~= 0 and i < alignment do
output[offset] = 0
offset = offset + 1
total_size = total_size + 1
i = i + 1
end
end
if endianness == ByteOrder.BIG_ENDIAN then
local added_padding = 0
if size > 8 then for i = 0, size - 9 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
added_padding = added_padding + 1
total_size = total_size + 1
end end
for i = added_padding, size - 1 do
output[offset + i] = bit32.band(bit32.rshift(num, ((size - i - 1) * 8)), 0xFF)
total_size = total_size + 1
end
else
for i = 0, math.min(size, 8) - 1 do
output[offset + i] = num / 2^(i * 8) % 256
total_size = total_size + 1
end
for i = 8, size - 1 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
total_size = total_size + 1
end
end
return total_size
end
local function unpackint(str, offset, size, endianness, alignment, signed)
local result, rsize = 0, 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
for i = 0, alignment - 1 do
if offset % math.min(size, alignment) == 0 then break end
offset = offset + 1
rsize = rsize + 1
end
end
for i = 0, size - 1 do
result = result + str:byte(offset + i) * 2^((endianness == ByteOrder.BIG_ENDIAN and size - i - 1 or i) * 8)
rsize = rsize + 1
end
if (signed and result >= 2^(size * 8 - 1)) then result = result - 2^(size * 8) end
return result, rsize
end
local function packoptsize(opt, alignment)
local retval = packoptsize_tbl[opt] or 0
if (alignment > 1 and retval % alignment ~= 0) then retval = retval + (alignment - (retval % alignment)) end
return retval
end
--[[
* string.pack (fmt, v1, v2, ...)
*
* Returns a binary string containing the values v1, v2, etc.
* serialized in binary form (packed) according to the format string fmt.
]]
local function pack(...)
local fmt = expect(1, ..., "string")
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local pos = 1
local argnum = 2
local output = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '=' or c == '<' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = -1
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then alignment = 4
else alignment = size end
elseif isint[c] then
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (packoptsize(c, 0) * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (packoptsize(c, 0) * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, packoptsize(c, 0), output, pos, alignment, endianness, false)
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = -1
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == -1) then size = 4 end
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (size * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (size * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, size, output, pos, alignment, endianness, signed)
elseif c == 'f' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local l = floatToRawIntBits(f)
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(4, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 3 - j or j)] = bit32.band(bit32.rshift(l, (j * 8)), 0xFF) end
pos = pos + 4
elseif c == 'd' or c == 'n' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local lh, ll = doubleToRawLongBits(f)
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(8, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(ll, (j * 8)), 0xFF) end
for j = 4, 7 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(lh, ((j - 4) * 8)), 0xFF) end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0xFFFFFFFF) then error("bad argument #1 to 'pack' (format result too large)", 2) end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str > size) then error(string.format("bad argument #%d to 'pack' (string longer than given size)", argnum - 1), 2) end
if size > 0 then
for j = 0, size - 1 do output[pos+j] = str:byte(j + 1) or 0 end
pos = pos + size
end
elseif c == 'z' then
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
for b in str:gmatch "." do if (b == '\0') then error(string.format("bad argument #%d to 'pack' (string contains zeros)", argnum - 1), 2) end end
for j = 0, #str - 1 do output[pos+j] = str:byte(j + 1) end
output[pos + #str] = 0
pos = pos + #str + 1
elseif c == 's' then
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str >= math.pow(2, (size * 8))) then
error(string.format("bad argument #%d to 'pack' (string length does not fit in given size)", argnum - 1), 2)
end
packint(#str, size, output, pos, 1, endianness, false)
for j = size, #str + size - 1 do output[pos+j] = str:byte(j - size + 1) or 0 end
pos = pos + #str + size
elseif c == 'x' then
output[pos] = 0
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
return string.char(table.unpack(output))
end
--[[
* string.packsize (fmt)
*
* Returns the size of a string resulting from string.pack with the given format.
* The format string cannot have the variable-length options 's' or 'z'.
]]
local function packsize(fmt)
local pos = 0
local alignment = 1
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
local size = packoptsize(c, 0)
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c:lower() == 'i' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == 0) then size = 4 end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0x7FFFFFFF) then error("bad argument #1 to 'packsize' (format result too large)", 2) end
pos = pos + size
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c == 's' or c == 'z' then error("bad argument #1 to 'packsize' (variable-length format)", 2)
elseif c ~= ' ' and c ~= '<' and c ~= '>' and c ~= '=' then error(string.format("invalid format option '%s'", c), 2) end
end
return pos
end
--[[
* string.unpack (fmt, s [, pos])
*
* Returns the values packed in string s (see string.pack) according to the format string fmt.
* An optional pos marks where to start reading in s (default is 1).
* After the read values, this function also returns the index of the first unread byte in s.
]]
local function unpack(fmt, str, pos)
expect(1, fmt, "string")
expect(2, str, "string")
expect(3, pos, "number", "nil")
if pos then
if (pos < 0) then pos = #str + pos
elseif (pos == 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
if (pos > #str or pos < 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
else pos = 1 end
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local retval = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '<' or c == '=' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
if (pos + packoptsize(c, 0) > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, packoptsize(c, 0), endianness, alignment, c:match("%l") ~= nil)
retval[#retval+1] = res
pos = pos + ressz
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size > 8) then
error(string.format("%d-byte integer does not fit into Lua Integer", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, size, endianness, alignment, signed)
retval[#retval+1] = res
pos = pos + ressz
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 4 > #str + 1) then error("data string too short", 2) end
local res = unpackint(str, pos, 4, endianness, alignment, false)
retval[#retval+1] = intBitsToFloat(res)
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 8 > #str + 1) then error("data string too short", 2) end
local lh, ll = 0, 0
for j = 0, 3 do lh = bit32.bor(lh, bit32.lshift((str:byte(pos + j)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
for j = 0, 3 do ll = bit32.bor(ll, bit32.lshift((str:byte(pos + j + 4)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
if endianness == ByteOrder.LITTLE_ENDIAN then lh, ll = ll, lh end
retval[#retval+1] = longBitsToDouble(lh, ll)
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)") end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size
elseif c == 'z' then
local size = 0
while (str:byte(pos + size) ~= 0) do
size = size + 1
if (pos + size > #str) then error("unfinished string for format 'z'", 2) end
end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size + 1
elseif c == 's' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local num, numsz = unpackint(str, pos, size, endianness, alignment, false)
pos = pos + numsz
if (pos + num > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + num - 1)
pos = pos + num
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then size = 4 end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
retval[#retval+1] = pos
return table.unpack(retval)
end
return {
pack = pack,
packsize = packsize,
unpack = unpack
}

View file

@ -0,0 +1,637 @@
--[[- Lua library for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
@module youcubeapi
]]
--[[ youcubeapi.lua
_ _ ____ _ _ ____ _ _ ___ ____ ____ ___ _
\_/ | | | | | | | |__] |___ |__| |__] |
| |__| |__| |___ |__| |__] |___ | | | |
]]
--[[- "wrapper" for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
@type API
@usage Example:
local youcubeapi = require("youcubeapi")
local api = youcubeapi.API.new()
api:detect_bestest_server()
api:request_media(url)
local data = api.websocket.receive()
]]
local API = {}
--- Create's a new API instance.
-- @param websocket [Websocket](https://tweaked.cc/module/http.html#ty:Websocket) The websocket.
-- @treturn API instance
function API.new(websocket)
return setmetatable({
websocket = websocket,
}, { __index = API })
end
-- Look at the [Documentation](https://commandcracker.github.io/YouCube/) for moor information
-- Contact the server owner on Discord, when the server is down
local servers = {
"ws://127.0.0.1:5000", -- Your server!
"wss://us-ky.youcube.knijn.one", -- By EmmaKnijn
"wss://youcube.knijn.one", -- By EmmaKnijn
"wss://youcube.onrender.com", -- By Commandcracker#8528
}
if settings then
local server = settings.get("youcube.server")
if server then
table.insert(servers, 1, server)
end
end
local function websocket_with_timeout(_url, _headers, _timeout)
if http.websocketAsync then
local websocket, websocket_error = http.websocketAsync(_url, _headers)
if not websocket then
return false, websocket_error
end
local timerID = os.startTimer(_timeout)
while true do
local event, param1, param2 = os.pullEvent()
-- TODO: Close web-socket when the connection succeeds after the timeout
if event == "websocket_success" and param1 == _url then
return param2
elseif event == "websocket_failure" and param1 == _url then
return false, param2
elseif event == "timer" and param1 == timerID then
return false, "Timeout"
end
end
end
-- use websocket without timeout
-- when the CC version dos not support websocketAsync
return http.websocket(_url, _headers)
end
--- Connects to a YouCub Server
function API:detect_bestest_server(_server, _verbose)
if _server then
table.insert(servers, 1, _server)
end
for i = 1, #servers do
local server = servers[i]
local ok, err = http.checkURL(server:gsub("^ws://", "http://"):gsub("^wss://", "https://"))
if ok then
if _verbose then
print("Trying to connect to:", server)
end
local websocket, websocket_error = websocket_with_timeout(server, nil, 5)
if websocket ~= false then
term.write("Using the YouCube server: ")
term.setTextColor(colors.blue)
print(server)
term.setTextColor(colors.white)
self.websocket = websocket
break
elseif i == #servers then
error(websocket_error)
elseif _verbose then
print(websocket_error)
end
elseif i == #servers then
error(err)
elseif _verbose then
print(err)
end
end
end
--- Receive data from The YouCub Server
-- @tparam string filter action filter
-- @treturn table retval data
function API:receive(filter)
local status, retval = pcall(self.websocket.receive)
if not status then
error("Lost connection to server\n" .. retval)
end
if retval == nil then
error("Received empty message or max message size exceeded")
end
local data, err = textutils.unserialiseJSON(retval)
if data == nil then
error("Failed to parse message\n" .. err)
end
if filter then
--if type(filter) == "table" then
-- if not filter[data.action] then
-- return self:receive(filter)
-- end
--else
if data.action ~= filter then
return self:receive(filter)
end
end
return data
end
--- Send data to The YouCub Server
-- @tparam table data data to send
function API:send(data)
local status, retval = pcall(self.websocket.send, textutils.serialiseJSON(data))
if not status then
error("Lost connection to server\n" .. retval)
end
end
--[[- [Base64](https://wikipedia.org/wiki/Base64) functions
@type Base64
]]
local Base64 = {}
local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-- based on https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua
--- Decode base64 string
-- @tparam string str base64 string
-- @treturn string string decoded string
function Base64.decode(str)
local retval = ""
for s in str:gmatch("....") do
if s:sub(3, 4) == "==" then
retval = retval
.. string.char(
bit32.bor(
bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2),
bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4)
)
)
elseif s:sub(4, 4) == "=" then
local n = (b64str:find(s:sub(1, 1)) - 1) * 4096
+ (b64str:find(s:sub(2, 2)) - 1) * 64
+ (b64str:find(s:sub(3, 3)) - 1)
retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8))
else
local n = (b64str:find(s:sub(1, 1)) - 1) * 262144
+ (b64str:find(s:sub(2, 2)) - 1) * 4096
+ (b64str:find(s:sub(3, 3)) - 1) * 64
+ (b64str:find(s:sub(4, 4)) - 1)
retval = retval
.. string.char(bit32.extract(n, 16, 8))
.. string.char(bit32.extract(n, 8, 8))
.. string.char(bit32.extract(n, 0, 8))
end
end
return retval
end
--- Request a `16 * 1024` bit chunk
-- @tparam number chunkindex The chunkindex
-- @tparam string id Media id
-- @treturn bytes chunk `16 * 1024` bit chunk
function API:get_chunk(chunkindex, id)
self:send({
["action"] = "get_chunk",
["chunkindex"] = chunkindex,
["id"] = id,
})
return Base64.decode(self:receive("chunk").chunk)
end
--- Get 32vid
-- @tparam number line The line to return
-- @tparam string id Media id
-- @tparam number width Video width
-- @tparam number height Video height
-- @treturn string line one line of the given 32vid
function API:get_vid(tracker, id, width, height)
self:send({
["action"] = "get_vid",
["tracker"] = tracker,
["id"] = id,
["width"] = width * 2,
["height"] = height * 3,
})
return self:receive("vid")
end
--- Request media
-- @tparam string url Url or Search Term
--@treturn table json response
function API:request_media(url, width, height)
local request = {
["action"] = "request_media",
["url"] = url,
}
if width and height then
request.width = width * 2
request.height = height * 3
end
self:send(request)
--return self:receive({ ["media"] = true, ["status"] = true })
end
--- Handshake - get Server capabilities and version
--@treturn table json response
function API:handshake()
self:send({
["action"] = "handshake",
})
return self:receive("handshake")
end
--[[- Abstraction for Audio Devices
@type AudioDevice
]]
local AudioDevice = {}
--- Create's a new AudioDevice instance.
-- @tparam table object Base values
-- @treturn AudioDevice instance
function AudioDevice.new(object)
-- @type AudioDevice
local self = object or {}
function self:validate() end
function self:setLabel(lable) end
function self:write(chunk) end
function self:play() end
function self:reset() end
function self:setVolume(volume) end
return self
end
--[[- @{AudioDevice} from a Speaker
@type Speaker
@usage Example:
local youcubeapi = require("youcubeapi")
local speaker = peripheral.find("speaker")
local audiodevice = youcubeapi.Speaker.new(speaker)
]]
local Speaker = {}
local decoder
local status, dfpwm = pcall(require, "cc.audio.dfpwm")
if status then
decoder = dfpwm.make_decoder()
end
--- Create's a new Tape instance.
-- @tparam speaker speaker The speaker
-- @treturn AudioDevice|Speaker instance
function Speaker.new(speaker)
local self = AudioDevice.new({ speaker = speaker })
function self:validate()
if not decoder then
return "This ComputerCraft version dos not support DFPWM"
end
end
function self:setVolume(volume)
self.volume = volume
end
function self:write(chunk)
local buffer = decoder(chunk)
while not self.speaker.playAudio(buffer, self.volume) do
os.pullEvent("speaker_audio_empty")
end
end
return self
end
--[[- @{AudioDevice} from a [Computronics tape_drive](https://wiki.vexatos.com/wiki:computronics:tape)
@type Tape
@usage Example:
local youcubeapi = require("youcubeapi")
local tape_drive = peripheral.find("tape_drive")
local audiodevice = youcubeapi.Tape.new(tape_drive)
]]
local Tape = {}
--- Create's a new Tape instance.
-- @tparam tape tape The tape_drive
-- @treturn AudioDevice|Tape instance
function Tape.new(tape)
local self = AudioDevice.new({ tape = tape })
function self:validate()
if not self.tape.isReady() then
return "You need to insert a tape"
end
end
function self:setVolume(volume)
if volume then
self.tape.setVolume(volume)
end
end
function self:play(chunk)
self.tape.seek(-self.tape.getSize())
self.tape.play()
end
function self:write(chunk)
self.tape.write(chunk)
end
function self:setLabel(lable)
self.tape.setLabel(lable)
end
function self:reset()
-- based on https://github.com/Vexatos/Computronics/blob/b0ade53cab10529dbe91ebabfa882d1b4b21fa90/src/main/resources/assets/computronics/lua/peripheral/tape_drive/programs/tape_drive/tape#L109-L123
local size = self.tape.getSize()
self.tape.stop()
self.tape.seek(-size)
self.tape.stop()
self.tape.seek(-90000)
local s = string.rep(string.char(170), 8192)
for i = 1, size + 8191, 8192 do
self.tape.write(s)
end
self.tape.seek(-size)
self.tape.seek(-90000)
end
return self
end
--[[- Abstract object for filling a @{Buffer}
@type Filler
]]
local Filler = {}
--- Create's a new Filler instance.
-- @treturn Filler instance
function Filler.new()
local self = {}
function self:next() end
return self
end
--[[- @{Filler} for Audio
@type AudioFiller
]]
local AudioFiller = {}
--- Create's a new AudioFiller instance.
-- @tparam API youcubeapi API object
-- @tparam string id Media id
-- @treturn AudioFiller|Filler instance
function AudioFiller.new(youcubeapi, id)
local self = {
id = id,
chunkindex = 0,
youcubeapi = youcubeapi,
}
function self:next()
local response = self.youcubeapi:get_chunk(self.chunkindex, self.id)
self.chunkindex = self.chunkindex + 1
return response
end
return self
end
--[[- @{Filler} for Video
@type VideoFiller
]]
local VideoFiller = {}
--- Create's a new VideoFiller instance.
-- @tparam API youcubeapi API object
-- @tparam string id Media id
-- @tparam number width Video width
-- @tparam number height Video height
-- @treturn VideoFiller|Filler instance
function VideoFiller.new(youcubeapi, id, width, height)
local self = {
id = id,
width = width,
height = height,
tracker = 0,
youcubeapi = youcubeapi,
}
function self:next()
local response = self.youcubeapi:get_vid(self.tracker, self.id, self.width, self.height)
for i = 1, #response.lines do
self.tracker = self.tracker + #response.lines[i] + 1
end
return response.lines
end
return self
end
--[[- Buffers Data
@type Buffer
]]
local Buffer = {}
--- Create's a new Buffer instance.
-- @tparam Filler filler filler instance
-- @tparam number size buffer limit
-- @treturn Buffer instance
function Buffer.new(filler, size)
local self = {
filler = filler,
size = size,
}
self.buffer = {}
function self:next()
while #self.buffer == 0 do
os.pullEvent()
end -- Wait until next is available
local next = self.buffer[1]
table.remove(self.buffer, 1)
return next
end
function self:fill()
if #self.buffer < self.size then
local next = filler:next()
if type(next) == "table" then
for i = 1, #next do
self.buffer[#self.buffer + 1] = next[i]
end
else
self.buffer[#self.buffer + 1] = next
end
return true
end
return false
end
return self
end
local currnt_palette = {}
for i = 0, 15 do
local r, g, b = term.getPaletteColour(2 ^ i)
currnt_palette[i] = { r, g, b }
end
local function reset_term()
for i = 0, 15 do
term.setPaletteColor(2 ^ i, currnt_palette[i][1], currnt_palette[i][2], currnt_palette[i][3])
end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)
end
--[[- Create's a new Buffer instance.
Based on [sanjuuni/raw-player.lua](https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua)
and [sanjuuni/websocket-player.lua](https://github.com/MCJack123/sanjuuni/blob/30dcabb4b56f1eb32c88e1bce384b0898367ebda/websocket-player.lua)
@tparam Buffer buffer filled with frames
]]
local function play_vid(buffer, force_fps, string_unpack)
if not string_unpack then
string_unpack = string.unpack
end
local Fwidth, Fheight = term.getSize()
local tracker = 0
if buffer:next() ~= "32Vid 1.1" then
error("Unsupported file")
end
local fps = tonumber(buffer:next())
if force_fps then
fps = force_fps
end
-- Adjust buffer size
buffer.size = math.ceil(fps) * 2
local first, second = buffer:next(), buffer:next()
if second == "" or second == nil then
fps = 0
end
term.clear()
local start = os.epoch("utc")
local frame_count = 0
while true do
frame_count = frame_count + 1
local frame
if first then
frame, first = first, nil
elseif second then
frame, second = second, nil
else
frame = buffer:next()
end
if frame == "" or frame == nil then
break
end
local mode = frame:match("^!CP([CD])")
if not mode then
error("Invalid file")
end
local b64data
if mode == "C" then
local len = tonumber(frame:sub(5, 8), 16)
b64data = frame:sub(9, len + 8)
else
local len = tonumber(frame:sub(5, 16), 16)
b64data = frame:sub(17, len + 16)
end
local data = Base64.decode(b64data)
-- TODO: maybe verify checksums?
assert(data:sub(1, 4) == "\0\0\0\0" and data:sub(9, 16) == "\0\0\0\0\0\0\0\0", "Invalid file")
local width, height = string_unpack("HH", data, 5)
local c, n, pos = string_unpack("c1B", data, 17)
local text = {}
for y = 1, height do
text[y] = ""
for x = 1, width do
text[y] = text[y] .. c
n = n - 1
if n == 0 then
c, n, pos = string_unpack("c1B", data, pos)
end
end
end
c = c:byte()
for y = 1, height do
local fg, bg = "", ""
for x = 1, width do
fg, bg = fg .. ("%x"):format(bit32.band(c, 0x0F)), bg .. ("%x"):format(bit32.rshift(c, 4))
n = n - 1
if n == 0 then
c, n, pos = string_unpack("BB", data, pos)
end
end
term.setCursorPos(1, y)
term.blit(text[y], fg, bg)
end
pos = pos - 2
local r, g, b
for i = 0, 15 do
r, g, b, pos = string_unpack("BBB", data, pos)
term.setPaletteColor(2 ^ i, r / 255, g / 255, b / 255)
end
if fps == 0 then
read()
break
else
while os.epoch("utc") < start + (frame_count + 1) / fps * 1000 do
sleep(1 / fps)
end
end
end
reset_term()
end
return {
--- "Metadata" - [YouCube API](https://commandcracker.github.io/YouCube/) Version
_API_VERSION = "0.0.0-poc.1.0.0",
--- "Metadata" - Library Version
_VERSION = "0.0.0-poc.1.4.2",
--- "Metadata" - Description
_DESCRIPTION = "Library for accessing YouCub's API",
--- "Metadata" - Homepage / Url
_URL = "https://github.com/Commandcracker/YouCube",
--- "Metadata" - License
_LICENSE = "GPL-3.0",
API = API,
AudioDevice = AudioDevice,
Speaker = Speaker,
Tape = Tape,
Base64 = Base64,
Filler = Filler,
AudioFiller = AudioFiller,
VideoFiller = VideoFiller,
Buffer = Buffer,
play_vid = play_vid,
reset_term = reset_term,
}

19
computer/28/startup.lua Normal file
View file

@ -0,0 +1,19 @@
rednet.open("right")
while true do
sender,message = rednet.receive("elevator")
if sender == 27 then
rednet.send(
sender,
{
pcall(
peripheral.call,
unpack(message)
)
},
"elevator"
)
print(unpack(message),"called")
end
end

604
computer/28/youcube.lua Normal file
View file

@ -0,0 +1,604 @@
--[[
_ _ ____ _ _ ____ _ _ ___ ____
\_/ | | | | | | | |__] |___
| |__| |__| |___ |__| |__] |___
Github Repository: https://github.com/Commandcracker/YouCube
License: GPL-3.0
]]
local _VERSION = "0.0.0-poc.1.1.2"
-- Libraries - OpenLibrarieLoader v1.0.1 --
--TODO: Optional libs:
-- For something like a JSON lib that is only needed for older CC Versions or
-- optional logging.lua support
local function is_lib(libs, lib)
for i = 1, #libs do
local value = libs[i]
if value == lib or value .. ".lua" == lib then
return true, value
end
end
return false
end
local libs = { "youcubeapi", "numberformatter", "semver", "argparse", "string_pack" }
local lib_paths = { ".", "./lib", "./apis", "./modules", "/", "/lib", "/apis", "/modules" }
-- LevelOS Support
if _G.lOS then
lib_paths[#lib_paths + 1] = "/Program_Files/YouCube/lib"
end
local function load_lib(lib)
if require then
return require(lib:gsub(".lua", ""))
end
return dofile(lib)
end
for i_path = 1, #lib_paths do
local path = lib_paths[i_path]
if fs.exists(path) then
local files = fs.list(path)
for i_file = 1, #files do
local found, lib = is_lib(libs, files[i_file])
if found and lib ~= nil and libs[lib] == nil then
libs[lib] = load_lib(path .. "/" .. files[i_file])
end
end
end
end
for i = 1, #libs do
local lib = libs[i]
if libs[lib] == nil then
error(('Library "%s" not found.'):format(lib))
end
end
-- args --
local function get_program_name()
if arg then
return arg[0]
end
return fs.getName(shell.getRunningProgram()):gsub("[\\.].*$", "")
end
-- stylua: ignore start
local parser = libs.argparse {
help_max_width = ({ term.getSize() })[1],
name = get_program_name()
}
:description "Official YouCube client for accessing media from services like YouTube"
parser:argument "URL"
:args "*"
:description "URL or search term."
parser:flag "-v" "--verbose"
:description "Enables verbose output."
:target "verbose"
:action "store_true"
parser:option "-V" "--volume"
:description "Sets the volume of the audio. A value from 0-100"
:target "volume"
parser:option "-s" "--server"
:description "The server that YC should use."
:target "server"
:args(1)
parser:flag "--nv" "--no-video"
:description "Disables video."
:target "no_video"
:action "store_true"
parser:flag "--na" "--no-audio"
:description "Disables audio."
:target "no_audio"
:action "store_true"
parser:flag "--sh" "--shuffle"
:description "Shuffles audio before playing"
:target "shuffle"
:action "store_true"
parser:flag "-l" "--loop"
:description "Loops the media."
:target "loop"
:action "store_true"
parser:flag "--lp" "--loop-playlist"
:description "Loops the playlist."
:target "loop_playlist"
:action "store_true"
parser:option "--fps"
:description "Force sanjuuni to use a specified frame rate"
:target "force_fps"
-- stylua: ignore end
local args = parser:parse({ ... })
if args.force_fps then
args.force_fps = tonumber(args.force_fps)
end
if args.volume then
args.volume = tonumber(args.volume)
if args.volume == nil then
parser:error("Volume must be a number")
end
if args.volume > 100 then
parser:error("Volume cant be over 100")
end
if args.volume < 0 then
parser:error("Volume cant be below 0")
end
args.volume = args.volume / 100
end
if #args.URL > 0 then
args.URL = table.concat(args.URL, " ")
else
args.URL = nil
end
if args.no_video and args.no_audio then
parser:error("Nothing will happen, when audio and video is disabled!")
end
-- CraftOS-PC support --
if periphemu then
periphemu.create("top", "speaker")
-- Fuck the max websocket message police
config.set("http_max_websocket_message", 2 ^ 30)
end
local function get_audiodevices()
local audiodevices = {}
local speakers = { peripheral.find("speaker") }
for i = 1, #speakers do
audiodevices[#audiodevices + 1] = libs.youcubeapi.Speaker.new(speakers[i])
end
local tapes = { peripheral.find("tape_drive") }
for i = 1, #tapes do
audiodevices[#audiodevices + 1] = libs.youcubeapi.Tape.new(tapes[i])
end
if #audiodevices == 0 then
-- Disable audio when no audiodevice is found
args.no_audio = true
return audiodevices
end
-- Validate audiodevices
local last_error
local valid_audiodevices = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
local _error = audiodevice:validate()
if _error == nil then
valid_audiodevices[#valid_audiodevices + 1] = audiodevice
else
last_error = _error
end
end
if #valid_audiodevices == 0 then
error(last_error)
end
return valid_audiodevices
end
-- main --
local youcubeapi = libs.youcubeapi.API.new()
local audiodevices = get_audiodevices()
-- update check --
local function get_versions()
local url = "https://raw.githubusercontent.com/CC-YouCube/installer/main/versions.json"
-- Check if the URL is valid
local ok, err = http.checkURL(url)
if not ok then
printError("Invalid Update URL.", '"' .. url .. '" ', err)
return
end
local response, http_err = http.get(url, nil, true)
if not response then
printError('Failed to retreat data from update URL. "' .. url .. '" (' .. http_err .. ")")
return
end
local sResponse = response.readAll()
response.close()
return textutils.unserialiseJSON(sResponse)
end
local function write_colored(text, color)
term.setTextColor(color)
term.write(text)
end
local function new_line()
local w, h = term.getSize()
local x, y = term.getCursorPos()
if y + 1 <= h then
term.setCursorPos(1, y + 1)
else
term.setCursorPos(1, h)
term.scroll(1)
end
end
local function write_outdated(current, latest)
if libs.semver(current) ^ libs.semver(latest) then
term.setTextColor(colors.yellow)
else
term.setTextColor(colors.red)
end
term.write(current)
write_colored(" -> ", colors.lightGray)
write_colored(latest, colors.lime)
term.setTextColor(colors.white)
new_line()
end
local function can_update(name, current, latest)
if libs.semver(current) < libs.semver(latest) then
term.write(name .. " ")
write_outdated(current, latest)
end
end
local function update_checker()
local versions = get_versions()
if versions == nil then
return
end
can_update("youcube", _VERSION, versions.client.version)
can_update("youcubeapi", libs.youcubeapi._VERSION, versions.client.libraries.youcubeapi.version)
can_update("numberformatter", libs.numberformatter._VERSION, versions.client.libraries.numberformatter.version)
can_update("semver", tostring(libs.semver._VERSION), versions.client.libraries.semver.version)
can_update("argparse", libs.argparse.version, versions.client.libraries.argparse.version)
local handshake = youcubeapi:handshake()
if libs.semver(handshake.server.version) < libs.semver(versions.server.version) then
print("Tell the server owner to update their server!")
write_outdated(handshake.server.version, versions.server.version)
end
if not libs.semver(libs.youcubeapi._API_VERSION) ^ libs.semver(handshake.api.version) then
print("Client is not compatible with server")
write_colored(libs.youcubeapi._API_VERSION, colors.red)
write_colored(" ^ ", colors.lightGray)
write_colored(handshake.api.version, colors.red)
term.setTextColor(colors.white)
new_line()
end
if libs.semver(libs.youcubeapi._API_VERSION) < libs.semver(versions.api.version) then
print("Your client is using an outdated API version")
write_outdated(libs.youcubeapi._API_VERSION, versions.api.version)
end
if libs.semver(handshake.api.version) < libs.semver(versions.api.version) then
print("The server is using an outdated API version")
write_outdated(libs.youcubeapi._API_VERSION, versions.api.version)
end
end
local function play_audio(buffer, title)
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
audiodevice:reset()
audiodevice:setLabel(title)
audiodevice:setVolume(args.volume)
end
while true do
local chunk = buffer:next()
-- Adjust buffer size on first chunk
if buffer.filler.chunkindex == 1 then
buffer.size = math.ceil(1024 / (#chunk / 16))
end
if chunk == "" then
local play_functions = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
play_functions[#play_functions + 1] = function()
audiodevice:play()
end
end
parallel.waitForAll(table.unpack(play_functions))
return
end
local write_functions = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
table.insert(write_functions, function()
audiodevice:write(chunk)
end)
end
parallel.waitForAll(table.unpack(write_functions))
end
end
-- #region playback controll vars
local back_buffer = {}
local max_back = settings.get("youcube.max_back") or 32
local queue = {}
local restart = false
-- #endregion
-- keys
local skip_key = settings.get("youcube.keys.skip") or keys.d
local restart_key = settings.get("youcube.keys.restart") or keys.r
local back_key = settings.get("youcube.keys.back") or keys.a
local function play(url)
restart = false
print("Requesting media ...")
if not args.no_video then
youcubeapi:request_media(url, term.getSize())
else
youcubeapi:request_media(url)
end
local data
local x, y = term.getCursorPos()
repeat
data = youcubeapi:receive()
if data.action == "status" then
os.queueEvent("youcube:status", data)
term.setCursorPos(x, y)
term.clearLine()
term.write("Status: ")
write_colored(data.message, colors.green)
term.setTextColor(colors.white)
else
new_line()
end
until data.action == "media"
if data.action == "error" then
error(data.message)
end
term.write("Playing: ")
term.setTextColor(colors.lime)
print(data.title)
term.setTextColor(colors.white)
if data.like_count then
print("Likes: " .. libs.numberformatter.compact(data.like_count))
end
if data.view_count then
print("Views: " .. libs.numberformatter.compact(data.view_count))
end
if not args.no_video then
-- wait, that the user can see the video info
sleep(2)
end
local video_buffer = libs.youcubeapi.Buffer.new(
libs.youcubeapi.VideoFiller.new(youcubeapi, data.id, term.getSize()),
60 -- Most videos run on 30 fps, so we store 2s of video.
)
local audio_buffer = libs.youcubeapi.Buffer.new(
libs.youcubeapi.AudioFiller.new(youcubeapi, data.id),
--[[
We want to buffer 1024 chunks.
One chunks is 16 bits.
The server (with default settings) sends 32 chunks at once.
]]
32
)
if args.verbose then
term.clear()
term.setCursorPos(1, 1)
term.write("[DEBUG MODE]")
end
local function fill_buffers()
while true do
os.queueEvent("youcube:fill_buffers")
local event = os.pullEventRaw()
if event == "terminate" then
libs.youcubeapi.reset_term()
end
if not args.no_audio then
audio_buffer:fill()
end
if args.verbose then
term.setCursorPos(1, ({ term.getSize() })[2])
term.clearLine()
term.write("Audio_Buffer: " .. #audio_buffer.buffer)
end
if not args.no_video then
video_buffer:fill()
end
end
end
local function _play_video()
if not args.no_video then
local string_unpack
if not string.unpack then
string_unpack = libs.string_pack.unpack
end
os.queueEvent("youcube:vid_playing", data)
libs.youcubeapi.play_vid(video_buffer, args.force_fps, string_unpack)
os.queueEvent("youcube:vid_eof", data)
end
end
local function _play_audio()
if not args.no_audio then
os.queueEvent("youcube:audio_playing", data)
play_audio(audio_buffer, data.title)
os.queueEvent("youcube:audio_eof", data)
end
end
local function _play_media()
os.queueEvent("youcube:playing")
parallel.waitForAll(_play_video, _play_audio)
end
local function _hotkey_handler()
while true do
local _, key = os.pullEvent("key")
if key == skip_key then
back_buffer[#back_buffer + 1] = url --finished playing, push the value to the back buffer
if #back_buffer > max_back then
back_buffer[1] = nil --remove it from the front of the buffer
end
if not args.no_video then
libs.youcubeapi.reset_term()
end
break
end
if key == restart_key then
queue[#queue + 1] = url --add the current song to upcoming
if not args.no_video then
libs.youcubeapi.reset_term()
end
restart = true
break
end
end
end
parallel.waitForAny(fill_buffers, _play_media, _hotkey_handler)
if data.playlist_videos then
return data.playlist_videos
end
end
local function shuffle_playlist(playlist)
local shuffled = {}
for i = 1, #queue do
local pos = math.random(1, #shuffled + 1)
shuffled[pos] = queue[i]
end
return shuffled
end
local function play_playlist(playlist)
queue = playlist
if args.shuffle then
queue = shuffle_playlist(queue)
end
while #queue ~= 0 do
local pl = table.remove(queue)
local function handle_back_hotkey()
while true do
local _, key = os.pullEvent("key")
if key == back_key then
queue[#queue + 1] = pl --add the current song to upcoming
local prev = table.remove(back_buffer)
if prev then --nil/false check
queue[#queue + 1] = prev --add previous song to upcoming
end
if not args.no_video then
libs.youcubeapi.reset_term()
end
break
end
end
end
parallel.waitForAny(handle_back_hotkey, function()
play(pl) --play the url
end)
end
end
local function main()
youcubeapi:detect_bestest_server(args.server, args.verbose)
pcall(update_checker)
if not args.URL then
print("Enter Url or Search Term:")
term.setTextColor(colors.lightGray)
args.URL = read()
term.setTextColor(colors.white)
end
local playlist_videos = play(args.URL)
if args.loop == true then
while true do
play(args.URL)
end
end
if playlist_videos then
if args.loop_playlist == true then
while true do
if playlist_videos then
play_playlist(playlist_videos)
end
end
end
play_playlist(playlist_videos)
end
while restart do
play(args.URL)
end
youcubeapi.websocket.close()
if not args.no_video then
libs.youcubeapi.reset_term()
end
os.queueEvent("youcube:playback_ended")
end
main()

View file

@ -0,0 +1,41 @@
controller = peripheral.wrap("right")
print(controller)
write("which row? ")
--row = read()
write("which column? ")
--col = read()
sleep(1)
print(row, col)
function rotatetomove(blocks)
sleep(0.05)
controller.move(
math.abs(blocks),
math.abs(blocks)/blocks
)
while controller.isRunning() do
sleep(0.05)
end
end
function travelY(blocks)
redstone.setAnalogOutput("top",0)
rotatetomove(blocks)
end
function travelX(blocks)
redstone.setAnalogOutput("top",1)
rotatetomove(blocks)
end
function travelZ(blocks)
redstone.setAnalogOutput("top",2)
rotatetomove(blocks)
end
travelY(4)
travelX(5)
travelZ(6)
sleep(10)
travelZ(-9)
travelX(-20)
travelY(-20)

View file

@ -10,7 +10,7 @@ sf = require("structure")
function removechunks() function removechunks()
file = fs.open("tobuild","r") file = fs.open("tobuild","r")
lines = {} counts = {}
line = file.readLine() line = file.readLine()
while line do while line do
@ -18,7 +18,7 @@ function removechunks()
if i == 0 then sleep(0.05) end if i == 0 then sleep(0.05) end
xyz = stringtovec(line) xyz = stringtovec(line)
if not chunkisempty(xyz) then if not chunkisempty(xyz) then
table.insert(lines,line) table.insert(counts,line)
print(line) print(line)
end end
line = file.readLine() line = file.readLine()
@ -26,7 +26,7 @@ function removechunks()
file.close() file.close()
fs.delete("tobuildcopy") fs.delete("tobuildcopy")
file = fs.open("tobuildcopy","w") file = fs.open("tobuildcopy","w")
for i,line in ipairs(lines) do for i,line in ipairs(counts) do
file.writeLine(line) file.writeLine(line)
print(line) print(line)
if math.mod(i,100) == 0 then sleep(0.05) end if math.mod(i,100) == 0 then sleep(0.05) end

View file

@ -21,7 +21,14 @@ im = require("inventorymanager")
function fell(index) function fell(index)
im.select("techreborn:rubber_sapling") im.select("techreborn:rubber_sapling")
pf.lookat(trees[index or math.random(#trees)]) pf.lookat(trees[index],
function()
has, data = turtle.inspect()
if has and data and data.name == "techreborn:rubber_leaves" then
turtle.dig()
end
end
)
has, data = turtle.inspect() has, data = turtle.inspect()
--print(has,data) --print(has,data)
if if
@ -43,9 +50,17 @@ function fell(index)
turtle.place() turtle.place()
end end
repeat
for i = 1,#trees do for i = 1,#trees do
fell(i) fell(i)
end end
sleep(300)
until (
(turtle.getFuelLevel() < 500)
or
(im.count("techeborn:rubber_sapling") < 2*#trees)
)
pf.to(pf.home+vector.new(0,10,0)) pf.to(pf.home+vector.new(0,10,0))
pf.returnHome() pf.returnHome()

View file

@ -67,9 +67,11 @@ function greedystep(target)
return true return true
end end
greedystep = require("pathfinding2").greedystep greedystep = require("pathfinding2").greedystep
function to(target) function to(target, func)
func = func or function() end
print(tostring(target)) print(tostring(target))
while greedystep(target) do while greedystep(target) do
func()
end end
end end
function returnhome() function returnhome()
@ -89,11 +91,12 @@ function returnhome()
_G.facing = facing _G.facing = facing
_G.position = position _G.position = position
end end
function lookat(target) function lookat(target, func)
print("lookat"..target:tostring()) print("lookat"..target:tostring())
while (position+facing).x ~= target.x while (position+facing).x ~= target.x
or (position+facing).z ~= target.z do or (position+facing).z ~= target.z do
greedystep(target) greedystep(target)
if func then func() end
end end
print("temp") print("temp")
temp = target.y-position.y temp = target.y-position.y

11
disk/3/bg Normal file
View file

@ -0,0 +1,11 @@
if not shell.openTab then
printError("Requires multishell")
return
end
local tArgs = { ... }
if #tArgs > 0 then
shell.openTab(table.unpack(tArgs))
else
shell.openTab("shell")
end

426
disk/3/multishell.lua Normal file
View file

@ -0,0 +1,426 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
-- @since 1.6
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
local parentTerm = term.current()
local w, h = parentTerm.getSize()
local tProcesses = {}
local nCurrentProcess = nil
local nRunningProcess = nil
local bShowMenu = false
local bWindowsResized = false
local nScrollPos = 1
local bScrollRight = false
local function selectProcess(n)
if nCurrentProcess ~= n then
if nCurrentProcess then
local tOldProcess = tProcesses[nCurrentProcess]
tOldProcess.window.setVisible(false)
end
nCurrentProcess = n
if nCurrentProcess then
local tNewProcess = tProcesses[nCurrentProcess]
tNewProcess.window.setVisible(true)
tNewProcess.bInteracted = true
end
end
end
local function setProcessTitle(n, sTitle)
tProcesses[n].sTitle = sTitle
end
local function resumeProcess(nProcess, sEvent, ...)
local tProcess = tProcesses[nProcess]
local sFilter = tProcess.sFilter
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
local nPreviousProcess = nRunningProcess
nRunningProcess = nProcess
term.redirect(tProcess.terminal)
local ok, result = coroutine.resume(tProcess.co, sEvent, ...)
tProcess.terminal = term.current()
if ok then
tProcess.sFilter = result
else
printError(result)
end
nRunningProcess = nPreviousProcess
end
end
local function launchProcess(bFocus, tProgramEnv, sProgramPath, ...)
local tProgramArgs = table.pack(...)
local nProcess = #tProcesses + 1
local tProcess = {}
tProcess.sTitle = fs.getName(sProgramPath)
if bShowMenu then
tProcess.window = window.create(parentTerm, 1, 2, w, h - 1, false)
else
tProcess.window = window.create(parentTerm, 1, 1, w, h, false)
end
tProcess.co = coroutine.create(function()
os.run(tProgramEnv, sProgramPath, table.unpack(tProgramArgs, 1, tProgramArgs.n))
if not tProcess.bInteracted then
term.setCursorBlink(false)
print("Press any key to continue")
os.pullEvent("char")
end
end)
tProcess.sFilter = nil
tProcess.terminal = tProcess.window
tProcess.bInteracted = false
tProcesses[nProcess] = tProcess
if bFocus then
selectProcess(nProcess)
end
resumeProcess(nProcess)
return nProcess
end
local function cullProcess(nProcess)
local tProcess = tProcesses[nProcess]
if coroutine.status(tProcess.co) == "dead" then
if nCurrentProcess == nProcess then
selectProcess(nil)
end
table.remove(tProcesses, nProcess)
if nCurrentProcess == nil then
if nProcess > 1 then
selectProcess(nProcess - 1)
elseif #tProcesses > 0 then
selectProcess(1)
end
end
if nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
end
return true
end
return false
end
local function cullProcesses()
local culled = false
for n = #tProcesses, 1, -1 do
culled = culled or cullProcess(n)
end
return culled
end
-- Setup the main menu
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
if parentTerm.isColor() then
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
else
menuMainTextColor, menuMainBgColor = colors.white, colors.black
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
end
local function redrawMenu()
if bShowMenu then
-- Draw menu
parentTerm.setCursorPos(1, 1)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.clearLine()
local nCharCount = 0
local nSize = parentTerm.getSize()
if nScrollPos ~= 1 then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.write("<")
nCharCount = 1
end
for n = nScrollPos, #tProcesses do
if n == nCurrentProcess then
parentTerm.setTextColor(menuMainTextColor)
parentTerm.setBackgroundColor(menuMainBgColor)
else
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
end
parentTerm.write(" " .. tProcesses[n].sTitle .. " ")
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
end
if nCharCount > nSize then
parentTerm.setTextColor(menuOtherTextColor)
parentTerm.setBackgroundColor(menuOtherBgColor)
parentTerm.setCursorPos(nSize, 1)
parentTerm.write(">")
bScrollRight = true
else
bScrollRight = false
end
-- Put the cursor back where it should be
local tProcess = tProcesses[nCurrentProcess]
if tProcess then
tProcess.window.restoreCursor()
end
end
end
local function resizeWindows()
local windowY, windowHeight
if bShowMenu then
windowY = 2
windowHeight = h - 1
else
windowY = 1
windowHeight = h
end
for n = 1, #tProcesses do
local tProcess = tProcesses[n]
local x, y = tProcess.window.getCursorPos()
if y > windowHeight then
tProcess.window.scroll(y - windowHeight)
tProcess.window.setCursorPos(x, windowHeight)
end
tProcess.window.reposition(1, windowY, w, windowHeight)
end
bWindowsResized = true
end
local function setMenuVisible(bVis)
if bShowMenu ~= bVis then
bShowMenu = bVis
resizeWindows()
redrawMenu()
end
end
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
selectProcess(n)
redrawMenu()
return true
end
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
return tProcesses[n].sTitle
end
return nil
end
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
--
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
local previousTerm = term.current()
setMenuVisible(#tProcesses + 1 >= 2)
local nResult = launchProcess(false, tProgramEnv, sProgramPath, ...)
redrawMenu()
term.redirect(previousTerm)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end
-- Begin
parentTerm.clear()
setMenuVisible(false)
launchProcess(true, {
["shell"] = shell,
["multishell"] = multishell,
}, "/rom/programs/shell.lua")
-- Run processes
while #tProcesses > 0 do
-- Get the event
local tEventData = table.pack(os.pullEventRaw())
local sEvent = tEventData[1]
if sEvent == "term_resize" then
-- Resize event
w, h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
-- Keyboard event
-- Passthrough to current process
resumeProcess(nCurrentProcess, table.unpack(tEventData, 1, tEventData.n))
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
elseif sEvent == "mouse_click" then
-- Click event
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and y == 1 then
-- Switch process
if x == 1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and x == term.getSize() then
nScrollPos = nScrollPos + 1
redrawMenu()
else
local tabStart = 1
if nScrollPos ~= 1 then
tabStart = 2
end
for n = nScrollPos, #tProcesses do
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
if x >= tabStart and x <= tabEnd then
selectProcess(n)
redrawMenu()
break
end
tabStart = tabEnd + 1
end
end
else
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
if p1 == -1 and nScrollPos ~= 1 then
nScrollPos = nScrollPos - 1
redrawMenu()
elseif bScrollRight and p1 == 1 then
nScrollPos = nScrollPos + 1
redrawMenu()
end
elseif not (bShowMenu and y == 1) then
-- Passthrough to current process
resumeProcess(nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y)
if cullProcess(nCurrentProcess) then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
else
-- Other event
-- Passthrough to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, table.unpack(tEventData, 1, tEventData.n))
end
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
if bWindowsResized then
-- Pass term_resize to all processes
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
for n = 1, nLimit do
resumeProcess(n, "term_resize")
end
bWindowsResized = false
if cullProcesses() then
setMenuVisible(#tProcesses >= 2)
redrawMenu()
end
end
end
-- Shutdown
term.redirect(parentTerm)

112
disk/3/pf Normal file
View file

@ -0,0 +1,112 @@
pp = require("cc.pretty").pretty_print
_G.facing = _G.facing or "south"
_G.pos = _G.pos or vector.new(0,0,0)
local up = vector.new(0,1,0)
local rightOf = {
south = "west",
west = "north",
north = "east",
east = "south"
}
local leftOf = {
west = "south",
north = "west",
east = "north",
south = "east"
}
local vecOf = {
north = vector.new(0,0,-1),
south = vector.new(0,0,1),
east = vector.new(1,0,0),
west = vector.new(-1,0,0),
}
function goUp()
if turtle.up() then
_G.pos.y = _G.pos.y + 1
else
printError("failed to go up")
printError(pos)
end
end
function goDown()
if turtle.down() then
_G.pos.y = _G.pos.y - 1
else
printError("failed to go down")
printError(pos)
end
end
function goLeft()
turtle.turnLeft()
_G.facing = leftOf[_G.facing]
end
function goRight()
turtle.turnRight()
_G.facing = rightOf[_G.facing]
end
function goForward()
if turtle.forward() then
_G.pos = _G.pos + vecOf[_G.facing]
if math.random(10) > 9 then
meow()
end
else
printError("failed to go forward")
printError(pos)
end
end
function goBack()
if turtle.back() then
_G.pos = _G.pos - vecOf[_G.facing]
else
printError("failed to go backward")
printError(pos)
end
end
function stepTo(target)
local delta = target - _G.pos
-- print(delta)
if delta.y > 0 then
goUp()
elseif delta.y < 0 then
goDown()
elseif delta:dot(vecOf[_G.facing]) > 0 then
goForward()
elseif delta:dot(vecOf[_G.facing]:cross(up)) > 0 then
goRight()
else
goLeft()
end
end
function goTo(target, face)
while target ~= _G.pos do
stepTo(target)
end
if face and face ~= _G.facing then
if rightOf[_G.facing] == face then
goRight()
elseif leftOf[_G.facing] == face then
goLeft()
else
goRight()
goRight()
end
end
end
function goHome()
goTo(vector.new(0,0,0), "south")
end

55
disk/3/sounds Normal file
View file

@ -0,0 +1,55 @@
noteblock = peripheral.wrap("right")
sfx = require("sfx")
speaker = peripheral.wrap("left")
instruments = {
"harp",
"bass",
"didgeridoo",
"xylophone",
"iron_xylophone",
"snare",
"hat",
"basedrum",
"bit",
"bit",
"bit",
"bit"
}
mobs = {
"skeleton",
"zombie",
"pig",
"cow",
"spider"
}
function sound()
while true do
if math.random(10)>5 then
speaker.playSound("entity."..mobs[math.random(#mobs)]..".ambient",10)
elseif math.random(100) < 95 then
noteblock.setInstrument(instruments[math.random(#instruments)])
noteblock.play()
noteblock.setNote(math.random(24))
elseif math.random(100) < 50 then
for i = 1,5 do
speaker.playSound("entity.creeper.step")
sleep(0.05)
end
speaker.playSound("entity.creeper.primed")
else
--speaker.playSound("BOOM")
end
sleep(math.random(1,20))
--os.reboot()
end
end
while false and true do
--sound()
if math.random(100) then
sfx.success()
sleep(math.random(1,4))
end
end
parallel.waitForAll(sound,sound,sound,sound,sound)
peripheral.call("top","turnOn")
os.reboot()

1
disk/3/startup.lua Normal file
View file

@ -0,0 +1 @@
print("heehee")

View file

@ -1,5 +1,5 @@
{ {
"computer": 26, "computer": 29,
"disk": 4, "disk": 4,
"peripheral.create:fluid_tank": 2, "peripheral.create:fluid_tank": 2,
"peripheral.create:item_vault": 0, "peripheral.create:item_vault": 0,