:3
This commit is contained in:
parent
491112768c
commit
68ec37f994
66 changed files with 6591 additions and 10096 deletions
107
computer/0/apis/bitreader.lua
Normal file
107
computer/0/apis/bitreader.lua
Normal 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
|
144
computer/0/apis/hexscreen.lua
Normal file
144
computer/0/apis/hexscreen.lua
Normal 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
427
computer/0/apis/wave.lua
Normal 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
BIN
computer/0/badapple.nbs
Normal file
Binary file not shown.
BIN
computer/0/badapple.qtv
Normal file
BIN
computer/0/badapple.qtv
Normal file
Binary file not shown.
55
computer/0/sounds.lua
Normal file
55
computer/0/sounds.lua
Normal 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,55 +1,22 @@
|
|||
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()
|
||||
term.clear()
|
||||
term.setCursorPos(1,1)
|
||||
|
||||
speaker = peripheral.wrap("right")
|
||||
while true do
|
||||
if math.random(10)>5 then
|
||||
speaker.playSound("entity."..mobs[math.random(#mobs)]..".ambient")
|
||||
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")
|
||||
for i = 1,100 do
|
||||
pitch = math.random()*0.4+0.8
|
||||
volume = math.random()*.5+1.5
|
||||
--print("pitch: "..pitch,"volume: "..volume)
|
||||
speaker.playSound(
|
||||
"entity.wandering_trader.ambient",
|
||||
volume,
|
||||
pitch
|
||||
)
|
||||
--sleep(math.random()*3+1)
|
||||
end
|
||||
sleep(math.random(1,20))
|
||||
--os.reboot()
|
||||
--sleep(math.random(300,600))
|
||||
shell.run("pastebin", "run", "KMRmKTc1")
|
||||
sleep(math.random(300,600))
|
||||
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
283
computer/0/videoplayer.lua
Normal 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
|
|
@ -18,7 +18,7 @@ while true do
|
|||
filename,
|
||||
"r"
|
||||
)
|
||||
lines = {}
|
||||
counts = {}
|
||||
vec = stringtovec(message)
|
||||
print(vector.new().tostring(vec))
|
||||
record = nil
|
||||
|
@ -51,14 +51,14 @@ while true do
|
|||
end
|
||||
end
|
||||
if adding then
|
||||
table.insert(lines,adding:tostring())
|
||||
table.insert(counts,adding:tostring())
|
||||
end
|
||||
until not line
|
||||
rednet.send(id,record:tostring(),"nexttobuild")
|
||||
print(record)
|
||||
file.close()
|
||||
file = fs.open(filename,"w")
|
||||
for i,v in pairs(lines) do
|
||||
for i,v in pairs(counts) do
|
||||
file.writeLine(v)
|
||||
end
|
||||
file.close()
|
||||
|
|
9942
computer/1/tobuild
9942
computer/1/tobuild
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
return {
|
||||
{
|
||||
name = "water",
|
||||
amount = 1409400,
|
||||
amount = 0,
|
||||
},
|
||||
{
|
||||
name = "blood",
|
||||
|
@ -9,7 +9,7 @@ return {
|
|||
},
|
||||
{
|
||||
name = "molten_brass",
|
||||
amount = 2991,
|
||||
amount = 0,
|
||||
},
|
||||
{
|
||||
name = "lava",
|
||||
|
@ -29,7 +29,7 @@ return {
|
|||
},
|
||||
{
|
||||
name = "molten_rose_gold",
|
||||
amount = 9000,
|
||||
amount = 0,
|
||||
},
|
||||
{
|
||||
amount = 0,
|
||||
|
|
|
@ -78,6 +78,15 @@ function getFluidAmountInTanks(type, tanks)
|
|||
return 0
|
||||
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)
|
||||
goTo(vector.new(4, 0, -1))
|
||||
-- clutches invert the signal
|
||||
|
|
|
@ -116,11 +116,23 @@ end
|
|||
|
||||
function emptyInventory()
|
||||
for i = 1, 16 do
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
local item = turtle.getItemDetail(i);
|
||||
if item then
|
||||
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
|
|
@ -209,9 +209,9 @@ function melt(_, product, yield)
|
|||
connectTankOrAssign(product)
|
||||
pumpToTanks(true)
|
||||
goTo(melter_pos, "north")
|
||||
while #pFront("items") > 0 do
|
||||
sleep(1)
|
||||
end
|
||||
repeat
|
||||
sleep(0.5)
|
||||
until tanksAreEmpty(pFront("tanks"))
|
||||
pumpToTanks(false)
|
||||
fluidInvAdd(product, yield)
|
||||
end
|
||||
|
|
|
@ -522,6 +522,11 @@ base bucket
|
|||
steps:
|
||||
spout water:1000
|
||||
|
||||
copper_ingot
|
||||
yield 9
|
||||
steps:
|
||||
craft copper_block
|
||||
|
||||
brass_tunnel
|
||||
yield 2
|
||||
steps:
|
||||
|
|
35
computer/13/secretvirus
Normal file
35
computer/13/secretvirus
Normal 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
|
|
@ -8,14 +8,16 @@ return {
|
|||
speaker.playNote("pling",volume,8)
|
||||
sleep(0.1)
|
||||
speaker.playNote("pling",volume,16)
|
||||
speaker.playSound("entity.cat.beg_for_food")
|
||||
end,
|
||||
fail = function ()
|
||||
speaker.playSound("entity.cat.death")
|
||||
speaker.playNote("didgeridoo", volume, 6)
|
||||
sleep(0.2)
|
||||
speaker.playNote("didgeridoo", volume, 3)
|
||||
end,
|
||||
eat = function ()
|
||||
speaker.playSound("entity.generic.eat")
|
||||
speaker.playSound("entity.cat.eat")
|
||||
sleep(0.1)
|
||||
speaker.playSound("entity.generic.eat")
|
||||
end
|
||||
|
|
|
@ -16,5 +16,7 @@ parallel.waitForAny(
|
|||
end
|
||||
end
|
||||
end
|
||||
--,
|
||||
--require("/secretvirus")
|
||||
)
|
||||
os.shutdown()
|
|
@ -3,7 +3,7 @@
|
|||
stock keeping
|
||||
keep spout filled during repeated operations
|
||||
multi-item crafting
|
||||
push items into existing stacks in chest
|
||||
-push items into existing stacks in chest
|
||||
pull items from multiple stacks if necessary
|
||||
-refuel self
|
||||
refuel furnace
|
||||
|
|
|
@ -59,12 +59,7 @@ function doRecipe(recipe)
|
|||
end
|
||||
end
|
||||
goHome()
|
||||
for i = 1, 16 do
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
turtle.select(i)
|
||||
turtle.drop()
|
||||
end
|
||||
end
|
||||
emptyInventory()
|
||||
if turtle.getFuelLevel() < 1000 then
|
||||
print("refueling")
|
||||
goTo(vector.new(4, 0, -2), "east")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
[ "shell.allow_disk_startup" ] = 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
11
computer/15/bg.lua
Normal 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
80
computer/15/dirt.lua
Normal 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
36
computer/15/lock.lua
Normal 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()
|
|
@ -1 +0,0 @@
|
|||
require("startup")
|
429
computer/15/multishell.lua
Normal file
429
computer/15/multishell.lua
Normal 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)
|
|
@ -1,48 +1,3 @@
|
|||
term.clear()
|
||||
term.setCursorPos(1,1)
|
||||
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
|
||||
--require("multishell")
|
||||
shell.execute("dirt")
|
||||
|
||||
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
11
computer/18/bg.lua
Normal 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
431
computer/18/multishell.lua
Normal 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
1
computer/18/startup.lua
Normal file
|
@ -0,0 +1 @@
|
|||
require("multishell")
|
13
computer/18/steal.lua
Normal file
13
computer/18/steal.lua
Normal 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()
|
||||
|
||||
|
|
@ -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
11
computer/22/bg.lua
Normal 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
431
computer/22/multishell.lua
Normal 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
1
computer/22/startup.lua
Normal file
|
@ -0,0 +1 @@
|
|||
require("multishell")
|
16
computer/22/steal.lua
Normal file
16
computer/22/steal.lua
Normal 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
11
computer/26/bg.lua
Normal 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
106
computer/26/expand.lua
Normal 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()
|
85
computer/26/garbidge/dig.lua
Normal file
85
computer/26/garbidge/dig.lua
Normal 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
|
34
computer/26/garbidge/roof.lua
Normal file
34
computer/26/garbidge/roof.lua
Normal 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
|
32
computer/26/garbidge/tile.lua
Normal file
32
computer/26/garbidge/tile.lua
Normal 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
|
4
computer/26/garbidge/tilemany.lua
Normal file
4
computer/26/garbidge/tilemany.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
t = require("tile")
|
||||
for i = 1,2 do
|
||||
t()
|
||||
end
|
429
computer/26/multishell.lua
Normal file
429
computer/26/multishell.lua
Normal 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
124
computer/26/path.lua
Normal 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
2
computer/26/progress.lua
Normal file
|
@ -0,0 +1,2 @@
|
|||
progress = 1
|
||||
layer_start = 1
|
1
computer/26/startup.lua
Normal file
1
computer/26/startup.lua
Normal file
|
@ -0,0 +1 @@
|
|||
require("multishell")
|
31
computer/27/dismemberer.lua
Normal file
31
computer/27/dismemberer.lua
Normal 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
2
computer/27/startup.lua
Normal file
|
@ -0,0 +1,2 @@
|
|||
rednet.open("top")
|
||||
rednet.host("elevator")
|
1
computer/28/char
Normal file
1
computer/28/char
Normal file
|
@ -0,0 +1 @@
|
|||
|
477
computer/28/lib/argparse.lua
Normal file
477
computer/28/lib/argparse.lua
Normal 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
|
84
computer/28/lib/numberformatter.lua
Normal file
84
computer/28/lib/numberformatter.lua
Normal 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
|
68
computer/28/lib/semver.lua
Normal file
68
computer/28/lib/semver.lua
Normal 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
|
562
computer/28/lib/string_pack.lua
Normal file
562
computer/28/lib/string_pack.lua
Normal 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
|
||||
}
|
637
computer/28/lib/youcubeapi.lua
Normal file
637
computer/28/lib/youcubeapi.lua
Normal 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
19
computer/28/startup.lua
Normal 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
604
computer/28/youcube.lua
Normal 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()
|
41
computer/29/vaultmanager.lua
Normal file
41
computer/29/vaultmanager.lua
Normal 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)
|
|
@ -10,7 +10,7 @@ sf = require("structure")
|
|||
|
||||
function removechunks()
|
||||
file = fs.open("tobuild","r")
|
||||
lines = {}
|
||||
counts = {}
|
||||
line = file.readLine()
|
||||
|
||||
while line do
|
||||
|
@ -18,7 +18,7 @@ function removechunks()
|
|||
if i == 0 then sleep(0.05) end
|
||||
xyz = stringtovec(line)
|
||||
if not chunkisempty(xyz) then
|
||||
table.insert(lines,line)
|
||||
table.insert(counts,line)
|
||||
print(line)
|
||||
end
|
||||
line = file.readLine()
|
||||
|
@ -26,7 +26,7 @@ function removechunks()
|
|||
file.close()
|
||||
fs.delete("tobuildcopy")
|
||||
file = fs.open("tobuildcopy","w")
|
||||
for i,line in ipairs(lines) do
|
||||
for i,line in ipairs(counts) do
|
||||
file.writeLine(line)
|
||||
print(line)
|
||||
if math.mod(i,100) == 0 then sleep(0.05) end
|
||||
|
|
|
@ -21,7 +21,14 @@ im = require("inventorymanager")
|
|||
function fell(index)
|
||||
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()
|
||||
--print(has,data)
|
||||
if
|
||||
|
@ -43,9 +50,17 @@ function fell(index)
|
|||
turtle.place()
|
||||
end
|
||||
|
||||
repeat
|
||||
for i = 1,#trees do
|
||||
fell(i)
|
||||
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.returnHome()
|
||||
|
||||
|
|
|
@ -67,9 +67,11 @@ function greedystep(target)
|
|||
return true
|
||||
end
|
||||
greedystep = require("pathfinding2").greedystep
|
||||
function to(target)
|
||||
function to(target, func)
|
||||
func = func or function() end
|
||||
print(tostring(target))
|
||||
while greedystep(target) do
|
||||
func()
|
||||
end
|
||||
end
|
||||
function returnhome()
|
||||
|
@ -89,11 +91,12 @@ function returnhome()
|
|||
_G.facing = facing
|
||||
_G.position = position
|
||||
end
|
||||
function lookat(target)
|
||||
function lookat(target, func)
|
||||
print("lookat"..target:tostring())
|
||||
while (position+facing).x ~= target.x
|
||||
or (position+facing).z ~= target.z do
|
||||
greedystep(target)
|
||||
if func then func() end
|
||||
end
|
||||
print("temp")
|
||||
temp = target.y-position.y
|
||||
|
|
11
disk/3/bg
Normal file
11
disk/3/bg
Normal 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
426
disk/3/multishell.lua
Normal 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
112
disk/3/pf
Normal 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
55
disk/3/sounds
Normal 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
1
disk/3/startup.lua
Normal file
|
@ -0,0 +1 @@
|
|||
print("heehee")
|
2
ids.json
2
ids.json
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"computer": 26,
|
||||
"computer": 29,
|
||||
"disk": 4,
|
||||
"peripheral.create:fluid_tank": 2,
|
||||
"peripheral.create:item_vault": 0,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue