diff --git a/sys/apps/PackageManager.lua b/sys/apps/PackageManager.lua index 1329ec7..3a3dd8b 100644 --- a/sys/apps/PackageManager.lua +++ b/sys/apps/PackageManager.lua @@ -58,6 +58,9 @@ local page = UI.Page { }, }, statusBar = UI.StatusBar { }, + accelerators = { + [ 'control-q' ] = 'quit', + }, } function page:loadPackages() diff --git a/sys/apps/autorun.lua b/sys/apps/autorun.lua index e8b378f..1274666 100644 --- a/sys/apps/autorun.lua +++ b/sys/apps/autorun.lua @@ -47,8 +47,8 @@ local function runDir(directory) end runDir('sys/autorun') -for name in pairs(Packages:installed()) do - local packageDir = 'packages/' .. name .. '/autorun' +for _, package in pairs(Packages:installedSorted()) do + local packageDir = 'packages/' .. package.name .. '/autorun' runDir(packageDir) end runDir('usr/autorun') diff --git a/sys/init/5.network.lua b/sys/init/5.network.lua index 6526c5f..b02937a 100644 --- a/sys/init/5.network.lua +++ b/sys/init/5.network.lua @@ -39,7 +39,7 @@ local function setModem(dev) end end --- create a psuedo-device named 'wireleess_modem' +-- create a psuedo-device named 'wireless_modem' kernel.hook('device_attach', function(_, eventData) local dev = device[eventData[1]] if dev and dev.type == 'modem' then diff --git a/sys/init/6.tl3.lua b/sys/init/6.tl3.lua deleted file mode 100644 index e1e9ecf..0000000 --- a/sys/init/6.tl3.lua +++ /dev/null @@ -1,1273 +0,0 @@ -if not _G.turtle then - return -end - -local Pathing = require('opus.pathfind') -local Point = require('opus.point') -local synchronized = require('opus.sync').sync -local Util = require('opus.util') - -local os = _G.os -local peripheral = _G.peripheral -local turtle = _G.turtle - -local function noop() end -local headings = Point.headings -local state = { } - -turtle.pathfind = Pathing.pathfind -turtle.point = { x = 0, y = 0, z = 0, heading = 0 } - -function turtle.getState() return state end -function turtle.isAborted() return state.abort end -function turtle.getStatus() return state.status end -function turtle.setStatus(s) state.status = s end - -local function _defaultMove(action) - while not action.move() do - if not state.digPolicy(action) and not state.attackPolicy(action) then - return false - end - end - return true -end - -function turtle.getPoint() return turtle.point end -function turtle.setPoint(pt, isGPS) - turtle.point.x = pt.x - turtle.point.y = pt.y - turtle.point.z = pt.z - if pt.heading then - turtle.point.heading = pt.heading - end - turtle.point.gps = isGPS - return true -end - -function turtle.resetState() - state.abort = false - state.status = 'idle' - state.attackPolicy = noop - state.digPolicy = noop - state.movePolicy = _defaultMove - state.moveCallback = noop - state.blacklist = nil - state.reference = nil -- gps reference when converting to relative coords - Pathing.reset() - return true -end - -function turtle.reset() - turtle.point.x = 0 - turtle.point.y = 0 - turtle.point.z = 0 - turtle.point.heading = 0 -- should be facing - turtle.point.gps = false - - turtle.resetState() - return true -end - -local function _dig(name, inspect, dig) - if name then - local s, b = inspect() - if not s or b.name ~= name then - return false - end - end - return dig() -end - -function turtle.dig(s) - return _dig(s, turtle.inspect, turtle.native.dig) -end - -function turtle.digUp(s) - return _dig(s, turtle.inspectUp, turtle.native.digUp) -end - -function turtle.digDown(s) - return _dig(s, turtle.inspectDown, turtle.native.digDown) -end - -local actions = { - up = { - detect = turtle.native.detectUp, - dig = turtle.digUp, - move = turtle.native.up, - attack = turtle.native.attackUp, - place = turtle.native.placeUp, - drop = turtle.native.dropUp, - suck = turtle.native.suckUp, - compare = turtle.native.compareUp, - inspect = turtle.native.inspectUp, - side = 'top' - }, - down = { - detect = turtle.native.detectDown, - dig = turtle.digDown, - move = turtle.native.down, - attack = turtle.native.attackDown, - place = turtle.native.placeDown, - drop = turtle.native.dropDown, - suck = turtle.native.suckDown, - compare = turtle.native.compareDown, - inspect = turtle.native.inspectDown, - side = 'bottom' - }, - forward = { - detect = turtle.native.detect, - dig = turtle.dig, - move = turtle.native.forward, - attack = turtle.native.attack, - place = turtle.native.place, - drop = turtle.native.drop, - suck = turtle.native.suck, - compare = turtle.native.compare, - inspect = turtle.native.inspect, - side = 'front' - }, - back = { - detect = noop, - dig = noop, - move = turtle.native.back, - attack = noop, - place = noop, - suck = noop, - compare = noop, - side = 'back' - }, -} - -function turtle.getAction(direction) - return actions[direction] -end - -function turtle.getHeadingInfo(heading) - heading = heading or turtle.point.heading - return headings[heading] -end - -function turtle.isTurtleAtSide(side) - local sideType = peripheral.getType(side) - return sideType and sideType == 'turtle' -end - --- [[ Policies ]] -- -turtle.policies = { } - -function turtle.addPolicy(name, policy) - turtle.policies[name] = policy -end - -function turtle.getPolicy(policy) - if type(policy) == 'function' then - return policy - end - local p = turtle.policies[policy] - if not p then - error('Invalid policy: ' .. tostring(policy)) - end - return p -end - --- [[ Basic turtle actions ]] -- -local function inventoryAction(fn, name, qty) - local slots = turtle.getFilledSlots() - local s - for _,slot in pairs(slots) do - if slot.key == name or slot.name == name then - turtle.native.select(slot.index) - if not qty then - s = fn() - else - s = fn(math.min(qty, slot.count)) - qty = qty - slot.count - if qty < 0 then - break - end - end - end - end - if not s then - return false, 'No items found' - end - return s -end - --- [[ Attack ]] -- -local function _attack(action) - if action.attack() then - repeat until not action.attack() - return true - end - return false -end - -function turtle.attack() return _attack(actions.forward) end -function turtle.attackUp() return _attack(actions.up) end -function turtle.attackDown() return _attack(actions.down) end - -turtle.addPolicy('attackNone', noop) -turtle.addPolicy('attack', function(action) - return _attack(action) -end) - -function turtle.setAttackPolicy(policy) state.attackPolicy = policy end - --- [[ Place ]] -- -local function _place(action, indexOrId) - local slot - - if indexOrId then - slot = turtle.getSlot(indexOrId) - if not slot then - return false, 'No items to place' - end - end - - if slot and slot.count == 0 then - return false, 'No items to place' - end - - return Util.tryTimes(3, function() - if slot then - turtle.select(slot.index) - end - local result = { action.place() } - if result[1] then - return true - end - if not state.digPolicy(action) then - state.attackPolicy(action) - end - return table.unpack(result) - end) -end - -function turtle.place(slot) return _place(actions.forward, slot) end -function turtle.placeUp(slot) return _place(actions.up, slot) end -function turtle.placeDown(slot) return _place(actions.down, slot) end - --- [[ Drop ]] -- -local function _drop(action, qtyOrName, qty) - if not qtyOrName or type(qtyOrName) == 'number' then - return action.drop(qtyOrName or 64) - end - return inventoryAction(action.drop, qtyOrName, qty) -end - -function turtle.drop(count, slot) return _drop(actions.forward, count, slot) end -function turtle.dropUp(count, slot) return _drop(actions.up, count, slot) end -function turtle.dropDown(count, slot) return _drop(actions.down, count, slot) end - --- [[ Dig ]] -- -turtle.addPolicy('digNone', noop) - -turtle.addPolicy('dig', function(action) - return action.dig() -end) - -turtle.addPolicy('turtleSafe', function(action) - if action.side == 'back' then - return false - end - if not turtle.isTurtleAtSide(action.side) then - return action.dig() - end - return Util.tryTimes(6, function() - os.sleep(.25) - if not action.detect() then - return true - end - end) -end) - -local function isBlacklisted(b) - if b and state.blacklist then - for _, v in pairs(state.blacklist) do - if b.name:find(v) then - return true - end - end - end -end - -turtle.addPolicy('blacklist', function(action) - if action.side == 'back' then - return false - end - local s, m = action.inspect() - if not isBlacklisted(s and m) then - return action.dig() - end - if s and m and m.name:find('turtle') then - return Util.tryTimes(math.random(3, 6), function() - os.sleep(.25) - if not action.detect() then - return true - end - end) - end -end) - -turtle.addPolicy('digAndDrop', function(action) - if action.detect() then - local slots = turtle.getInventory() - if action.dig() then - turtle.reconcileInventory(slots) - return true - end - end - return false -end) - -function turtle.setDigPolicy(policy) state.digPolicy = policy end - --- [[ Move ]] -- -turtle.addPolicy('moveNone', noop) -turtle.addPolicy('moveDefault', _defaultMove) -turtle.addPolicy('moveAssured', function(action) - if not _defaultMove(action) then - if action.side == 'back' then - return false - end - local oldStatus = state.status - print('assured move: stuck') - state.status = 'stuck' - repeat - os.sleep(1) - until _defaultMove(action) - state.status = oldStatus - end - return true -end) - -function turtle.setMoveCallback(cb) state.moveCallback = cb end -function turtle.clearMoveCallback() state.moveCallback = noop end -function turtle.getMoveCallback() return state.moveCallback end - --- convenience method for setting multiple values -function turtle.set(args) - for k,v in pairs(args) do - - if k == 'attackPolicy' then - turtle.setAttackPolicy(turtle.getPolicy(v)) - - elseif k == 'digPolicy' then - turtle.setDigPolicy(turtle.getPolicy(v)) - - elseif k == 'movePolicy' then - state.movePolicy = turtle.getPolicy(v) - - elseif k == 'movementStrategy' then - turtle.setMovementStrategy(v) - - elseif k == 'pathingBox' then - turtle.setPathingBox(v) - - elseif k == 'point' then - turtle.setPoint(v) - - elseif k == 'moveCallback' then - turtle.setMoveCallback(v) - - elseif k == 'status' then - turtle.setStatus(v) - - elseif k == 'blacklist' then - state.blacklist = v - - elseif k == 'reference' then - state.reference = v - - else - error('Invalid turle.set: ' .. tostring(k)) - end - end -end - --- [[ Fuel ]] -- -if type(turtle.getFuelLevel()) ~= 'number' then - -- Support unlimited fuel - function turtle.getFuelLevel() - return 100000 - end -end - --- override to optionally specify a fuel -function turtle.refuel(qtyOrName, qty) - if not qtyOrName or type(qtyOrName) == 'number' then - return turtle.native.refuel(qtyOrName or 64) - end - return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64) -end - --- [[ Heading ]] -- -function turtle.getHeading() - return turtle.point.heading -end - -function turtle.turnRight() - turtle.setHeading((turtle.point.heading + 1) % 4) - return turtle.point -end - -function turtle.turnLeft() - turtle.setHeading((turtle.point.heading - 1) % 4) - return turtle.point -end - -function turtle.turnAround() - turtle.setHeading((turtle.point.heading + 2) % 4) - return turtle.point -end - -function turtle.setHeading(heading) - if not heading then - return false, 'Invalid heading' - end - - if heading == turtle.point.heading then - return turtle.point - end - - local fi = Point.facings[heading] - if not fi then - return false, 'Invalid heading' - end - - heading = fi.heading % 4 - if heading ~= turtle.point.heading then - while heading < turtle.point.heading do - heading = heading + 4 - end - if heading - turtle.point.heading == 3 then - turtle.native.turnLeft() - turtle.point.heading = (turtle.point.heading - 1) % 4 - state.moveCallback('turn', turtle.point) - else - local turns = heading - turtle.point.heading - while turns > 0 do - turns = turns - 1 - turtle.native.turnRight() - turtle.point.heading = (turtle.point.heading + 1) % 4 - state.moveCallback('turn', turtle.point) - end - end - end - - return turtle.point -end - -function turtle.headTowardsX(dx) - if turtle.point.x ~= dx then - if turtle.point.x > dx then - turtle.setHeading(2) - else - turtle.setHeading(0) - end - end -end - -function turtle.headTowardsZ(dz) - if turtle.point.z ~= dz then - if turtle.point.z > dz then - turtle.setHeading(3) - else - turtle.setHeading(1) - end - end -end - -function turtle.headTowards(pt) - local xd = math.abs(turtle.point.x - pt.x) - local zd = math.abs(turtle.point.z - pt.z) - if xd > zd then - turtle.headTowardsX(pt.x) - else - turtle.headTowardsZ(pt.z) - end -end - --- [[ move ]] -- -function turtle.up() - if state.movePolicy(actions.up) then - turtle.point.y = turtle.point.y + 1 - state.moveCallback('up', turtle.point) - return true, turtle.point - end -end - -function turtle.down() - if state.movePolicy(actions.down) then - turtle.point.y = turtle.point.y - 1 - state.moveCallback('down', turtle.point) - return true, turtle.point - end -end - -function turtle.forward() - if state.movePolicy(actions.forward) then - turtle.point.x = turtle.point.x + headings[turtle.point.heading].xd - turtle.point.z = turtle.point.z + headings[turtle.point.heading].zd - state.moveCallback('forward', turtle.point) - return true, turtle.point - end -end - -function turtle.back() - if state.movePolicy(actions.back) then - turtle.point.x = turtle.point.x - headings[turtle.point.heading].xd - turtle.point.z = turtle.point.z - headings[turtle.point.heading].zd - state.moveCallback('back', turtle.point) - return true, turtle.point - end -end - -local function moveTowardsX(dx) - if not tonumber(dx) then error('moveTowardsX: Invalid arguments') end - local direction = dx - turtle.point.x - local move - - if direction == 0 then - return true - end - - if direction > 0 and turtle.point.heading == 0 or - direction < 0 and turtle.point.heading == 2 then - move = turtle.forward - else - move = turtle.back - end - - repeat - if not move() then - return false - end - until turtle.point.x == dx - return true -end - -local function moveTowardsZ(dz) - local direction = dz - turtle.point.z - local move - - if direction == 0 then - return true - end - - if direction > 0 and turtle.point.heading == 1 or - direction < 0 and turtle.point.heading == 3 then - move = turtle.forward - else - move = turtle.back - end - - repeat - if not move() then - return false - end - until turtle.point.z == dz - return true -end - --- [[ go ]] -- --- 1 turn goto (going backwards if possible) -function turtle.gotoSingleTurn(dx, dy, dz, dh) - dx = dx or turtle.point.x - dy = dy or turtle.point.y - dz = dz or turtle.point.z - - local function gx() - if turtle.point.x ~= dx then - moveTowardsX(dx) - end - if turtle.point.z ~= dz then - if dh and dh % 2 == 1 then - turtle.setHeading(dh) - else - turtle.headTowardsZ(dz) - end - end - end - - local function gz() - if turtle.point.z ~= dz then - moveTowardsZ(dz) - end - if turtle.point.x ~= dx then - if dh and dh % 2 == 0 then - turtle.setHeading(dh) - else - turtle.headTowardsX(dx) - end - end - end - - repeat - local x, z - local y = turtle.point.y - - repeat - x, z = turtle.point.x, turtle.point.z - - if turtle.point.heading % 2 == 0 then - gx() - gz() - else - gz() - gx() - end - until x == turtle.point.x and z == turtle.point.z - - if turtle.point.y ~= dy then - turtle.gotoY(dy) - end - - if turtle.point.x == dx and turtle.point.z == dz and turtle.point.y == dy then - return true - end - - until x == turtle.point.x and z == turtle.point.z and y == turtle.point.y - - return false -end - -local function gotoEx(dx, dy, dz) - -- determine the heading to ensure the least amount of turns - -- first check is 1 turn needed - remaining require 2 turns - if turtle.point.heading == 0 and turtle.point.x <= dx or - turtle.point.heading == 2 and turtle.point.x >= dx or - turtle.point.heading == 1 and turtle.point.z <= dz or - turtle.point.heading == 3 and turtle.point.z >= dz then - -- maintain current heading - -- nop - elseif dz > turtle.point.z and turtle.point.heading == 0 or - dz < turtle.point.z and turtle.point.heading == 2 or - dx < turtle.point.x and turtle.point.heading == 1 or - dx > turtle.point.x and turtle.point.heading == 3 then - turtle.turnRight() - else - turtle.turnLeft() - end - - if (turtle.point.heading % 2) == 1 then - if not turtle.gotoZ(dz) then return false end - if not turtle.gotoX(dx) then return false end - else - if not turtle.gotoX(dx) then return false end - if not turtle.gotoZ(dz) then return false end - end - - if dy then - if not turtle.gotoY(dy) then return false end - end - - return true -end - --- fallback goto - will turn around if was previously moving backwards -local function gotoMultiTurn(dx, dy, dz) - if gotoEx(dx, dy, dz) then - return true - end - - local moved - repeat - local x, y, z = turtle.point.x, turtle.point.y, turtle.point.z - - -- try going the other way - if (turtle.point.heading % 2) == 1 then - turtle.headTowardsX(dx) - else - turtle.headTowardsZ(dz) - end - - if gotoEx(dx, dy, dz) then - return true - end - - if dy then - turtle.gotoY(dy) - end - - moved = x ~= turtle.point.x or y ~= turtle.point.y or z ~= turtle.point.z - until not moved - - return false -end - --- go backwards - turning around if necessary to fight mobs / break blocks -function turtle.goback() - local hi = headings[turtle.point.heading] - return turtle.go({ - x = turtle.point.x - hi.xd, - y = turtle.point.y, - z = turtle.point.z - hi.zd, - heading = turtle.point.heading, - }) -end - -function turtle.gotoYfirst(pt) - if turtle.gotoY(pt.y) then - if turtle.go(pt) then - turtle.setHeading(pt.heading) - return true - end - end -end - -function turtle.go(pt) - if not pt.x and not pt.z and pt.y then - if turtle.gotoY(pt.y) then - turtle.setHeading(pt.heading) - return true - end - return false, 'Failed to reach location' - end - - local dx = pt.x or turtle.point.x - local dz = pt.z or turtle.point.z - local dy, dh = pt.y, pt.heading - if not turtle.gotoSingleTurn(dx, dy, dz, dh) then - if not gotoMultiTurn(dx, dy, dz) then - return false, 'Failed to reach location' - end - end - turtle.setHeading(dh) - return pt -end - --- avoid lint errors --- deprecated -turtle['goto'] = turtle.go -turtle['_goto'] = turtle.go - --- TODO: localize these goto functions -function turtle.gotoX(dx) - turtle.headTowardsX(dx) - - while turtle.point.x ~= dx do - if not turtle.forward() then - return false - end - end - return true -end - -function turtle.gotoZ(dz) - turtle.headTowardsZ(dz) - - while turtle.point.z ~= dz do - if not turtle.forward() then - return false - end - end - return true -end - -function turtle.gotoY(dy) - while turtle.point.y > dy do - if not turtle.down() then - return false - end - end - - while turtle.point.y < dy do - if not turtle.up() then - return false - end - end - return true -end - --- [[ Inventory ]] -- -function turtle.getSlot(indexOrId, slots) - if type(indexOrId) == 'string' then - slots = slots or turtle.getInventory() - local _,c = string.gsub(indexOrId, ':', '') - if c == 2 then -- combined id and dmg .. ie. minecraft:coal:0 - return Util.find(slots, 'key', indexOrId) - end - return Util.find(slots, 'name', indexOrId) - end - - local detail = turtle.getItemDetail(indexOrId) - if detail then - return { - name = detail.name, - damage = detail.damage, - count = detail.count, - key = detail.name .. ':' .. detail.damage, - - index = indexOrId, - - -- deprecate - qty = detail.count, - dmg = detail.damage, - id = detail.name, - } - end - - -- inconsistent return value - -- null is returned if indexOrId is a string and no item is present - return { - qty = 0, -- deprecate - count = 0, - index = indexOrId, - } -end - -function turtle.select(indexOrId) - if type(indexOrId) == 'number' then - return turtle.native.select(indexOrId) - end - - local s = turtle.getSlot(indexOrId) - if s then - turtle.native.select(s.index) - return s - end - - return false, 'Inventory does not contain item' -end - -function turtle.getInventory(slots) - slots = slots or { } - for i = 1, 16 do - slots[i] = turtle.getSlot(i) - end - return slots -end - -function turtle.getSummedInventory() - local slots = turtle.getFilledSlots() - local t = { } - for _,slot in pairs(slots) do - local entry = t[slot.key] - if not entry then - entry = { - count = 0, - damage = slot.damage, - name = slot.name, - key = slot.key, - - -- deprecate - qty = 0, - dmg = slot.dmg, - id = slot.id, - } - t[slot.key] = entry - end - entry.qty = entry.qty + slot.qty - entry.count = entry.qty - end - return t -end - -function turtle.has(item, count) - if item:match('.*:%d') then - local slot = turtle.getSummedInventory()[item] - return slot and slot.count >= (count or 1) - end - local slot = turtle.getSlot(item) - return slot and slot.count >= (count or 1) -end - -function turtle.getFilledSlots(startSlot) - startSlot = startSlot or 1 - - local slots = { } - for i = startSlot, 16 do - local count = turtle.getItemCount(i) - if count > 0 then - slots[i] = turtle.getSlot(i) - end - end - return slots -end - -function turtle.eachFilledSlot(fn) - local slots = turtle.getFilledSlots() - for _,slot in pairs(slots) do - fn(slot) - end -end - -function turtle.emptyInventory(dropAction) - dropAction = dropAction or turtle.native.drop - turtle.eachFilledSlot(function(slot) - turtle.select(slot.index) - dropAction() - end) - turtle.select(1) -end - -function turtle.reconcileInventory(slots, dropAction) - dropAction = dropAction or turtle.native.drop - for _,s in pairs(slots) do - local qty = turtle.getItemCount(s.index) - if qty > s.qty then - turtle.select(s.index) - dropAction(qty-s.qty, s) - end - end -end - -function turtle.selectSlotWithItems(startSlot) - startSlot = startSlot or 1 - for i = startSlot, 16 do - if turtle.getItemCount(i) > 0 then - turtle.select(i) - return i - end - end -end - -function turtle.selectSlotWithQuantity(qty, startSlot) - startSlot = startSlot or 1 - - for i = startSlot, 16 do - if turtle.getItemCount(i) == qty then - turtle.select(i) - return i - end - end -end - -function turtle.selectOpenSlot(startSlot) - return turtle.selectSlotWithQuantity(0, startSlot) -end - -function turtle.condense() - local slots = turtle.getInventory() - - for i = 1, 16 do - if slots[i].count < 64 then - for j = 16, i + 1, -1 do - if slots[j].count > 0 and (slots[i].count == 0 or slots[i].key == slots[j].key) then - turtle.select(j) - if turtle.transferTo(i) then - local transferred = turtle.getItemCount(i) - slots[i].count - slots[j].count = slots[j].count - transferred - slots[i].count = slots[i].count + transferred - slots[i].key = slots[j].key - if slots[j].count == 0 then - slots[j].key = nil - end - if slots[i].count == 64 then - break - end - else - break - end - end - end - end - end - turtle.select(1) - return true -end - -function turtle.getItemCount(idOrName) - if type(idOrName) == 'number' then - return turtle.native.getItemCount(idOrName) - end - local slots = turtle.getFilledSlots() - local count = 0 - for _,slot in pairs(slots) do - if slot.key == idOrName or slot.name == idOrName then - count = count + slot.count - end - end - return count -end - --- [[ Equipment ]] -- -function turtle.equip(side, item) - if item then - if not turtle.select(item) then - return false, 'Unable to equip ' .. item - end - end - - if side == 'left' then - return turtle.equipLeft() - end - return turtle.equipRight() -end - -function turtle.isEquipped(item) - if peripheral.getType('left') == item then - return 'left' - elseif peripheral.getType('right') == item then - return 'right' - end -end - -function turtle.unequip(side) - if not turtle.selectSlotWithQuantity(0) then - return false, 'No slots available' - end - return turtle.equip(side) -end - --- deprecate -function turtle.run(fn, ...) - local args = { ... } - local s, m - - if type(fn) == 'string' then - fn = turtle[fn] - end - - synchronized(turtle, function() - turtle.resetState() - s, m = pcall(function() fn(table.unpack(args)) end) - turtle.resetState() - if not s and m then - _G.printError(m) - end - end) - - return s, m -end - -function turtle.abort(abort) - state.abort = abort - if abort then - os.queueEvent('turtle_abort') - end -end - --- [[ Pathing ]] -- -function turtle.setPersistent(isPersistent) - if isPersistent then - Pathing.setBlocks({ }) - else - Pathing.setBlocks() - end -end - -function turtle.setPathingBox(box) - Pathing.setBox(box) -end - -function turtle.addWorldBlock(pt) - Pathing.addBlock(pt) -end - -function turtle.addWorldBlocks(pts) - Util.each(pts, function(pt) - Pathing.addBlock(pt) - end) -end - -local movementStrategy = turtle.pathfind - -function turtle.setMovementStrategy(strategy) - if strategy == 'pathing' then - movementStrategy = turtle.pathfind - elseif strategy == 'goto' then - movementStrategy = turtle.go - else - error('Invalid movement strategy') - end -end - -function turtle.faceAgainst(pt, options) -- 4 sided - options = options or { } - options.dest = { } - - for i = 0, 3 do - local hi = Point.facings[i] - table.insert(options.dest, { - x = pt.x + hi.xd, - z = pt.z + hi.zd, - y = pt.y + hi.yd, - heading = (hi.heading + 2) % 4, - }) - end - - return movementStrategy(Point.closest(turtle.point, options.dest), options) -end - --- move against this point --- if the point does not contain a heading, then the turtle --- will face the block (if on same plane) --- if above or below, the heading is undetermined unless specified -function turtle.moveAgainst(pt, options) -- 6 sided - options = options or { } - options.dest = { } - - for i = 0, 5 do - local hi = turtle.getHeadingInfo(i) - local heading, direction - if i < 4 then - heading = (hi.heading + 2) % 4 - direction = 'forward' - elseif i == 4 then - direction = 'down' - elseif i == 5 then - direction = 'up' - end - - table.insert(options.dest, { - x = pt.x + hi.xd, - z = pt.z + hi.zd, - y = pt.y + hi.yd, - direction = direction, - heading = pt.heading or heading, - }) - end - - return movementStrategy(Point.closest(turtle.point, options.dest), options) -end - -local actionsAt = { - detect = { - up = turtle.detectUp, - down = turtle.detectDown, - forward = turtle.detect, - }, - dig = { - up = turtle.digUp, - down = turtle.digDown, - forward = turtle.dig, - }, - move = { - up = turtle.moveUp, - down = turtle.moveDown, - forward = turtle.move, - }, - attack = { - up = turtle.attackUp, - down = turtle.attackDown, - forward = turtle.attack, - }, - place = { - up = turtle.placeUp, - down = turtle.placeDown, - forward = turtle.place, - }, - drop = { - up = turtle.dropUp, - down = turtle.dropDown, - forward = turtle.drop, - }, - suck = { - up = turtle.suckUp, - down = turtle.suckDown, - forward = turtle.suck, - }, - compare = { - up = turtle.compareUp, - down = turtle.compareDown, - forward = turtle.compare, - }, - inspect = { - up = turtle.inspectUp, - down = turtle.inspectDown, - forward = turtle.inspect, - }, -} - --- pt = { x,y,z,heading,direction } --- direction should only be up or down if provided --- heading can be provided to tell which way to face during action --- ex: place a block at the point from above facing east -local function _actionAt(action, pt, ...) - if not pt.heading and not pt.direction then - local msg - pt, msg = turtle.moveAgainst(pt) - if pt then - return action[pt.direction](...) - end - return pt, msg - end - - local reversed = - { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, } - local dir = reversed[headings[pt.direction or pt.heading].heading] - local apt = { x = pt.x + headings[dir].xd, - y = pt.y + headings[dir].yd, - z = pt.z + headings[dir].zd, } - local direction - - -- ex: place a block at this point, from above, facing east - if dir < 4 then - apt.heading = (dir + 2) % 4 - direction = 'forward' - elseif dir == 4 then - apt.heading = pt.heading - direction = 'down' - elseif dir == 5 then - apt.heading = pt.heading - direction = 'up' - end - - if movementStrategy(apt) then - return action[direction](...) - end -end - -local function _actionDownAt(action, pt, ...) - pt = Util.shallowCopy(pt) - pt.direction = Point.DOWN - return _actionAt(action, pt, ...) -end - -local function _actionUpAt(action, pt, ...) - pt = Util.shallowCopy(pt) - pt.direction = Point.UP - return _actionAt(action, pt, ...) -end - -local function _actionForwardAt(action, pt, ...) - if turtle.faceAgainst(pt) then - return action.forward(...) - end -end - -function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end -function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end -function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end -function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end - -function turtle.digAt(pt, ...) return _actionAt(actionsAt.dig, pt, ...) end -function turtle.digDownAt(pt, ...) return _actionDownAt(actionsAt.dig, pt, ...) end -function turtle.digForwardAt(pt, ...) return _actionForwardAt(actionsAt.dig, pt, ...) end -function turtle.digUpAt(pt, ...) return _actionUpAt(actionsAt.dig, pt, ...) end - -function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end -function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end -function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end -function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end - -function turtle.placeAt(pt, arg, dir) return _actionAt(actionsAt.place, pt, arg, dir) end -function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end -function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end -function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end - -function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end -function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end -function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end -function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end - -function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end - -function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end -function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end -function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end -function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end - -function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end -function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end -function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end -function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end - -turtle.reset() diff --git a/sys/lzwfs.lua b/sys/lzwfs.lua new file mode 100644 index 0000000..ac2ddb8 --- /dev/null +++ b/sys/lzwfs.lua @@ -0,0 +1,247 @@ +-- see: https://github.com/Rochet2/lualzw +-- MIT License - Copyright (c) 2016 Rochet2 + +-- Transparent file system compression for non-binary files using lzw + +-- Files that are compressed will have the first bytes in file set to 'LZWC'. +-- If a file does not benefit from compression, the contents will not be altered. + +-- Allow exclusions for files that shouldn't be compressed +-- Also allow for future types of exclusions using bit operations +-- 1 is reserved for compression exclusion +-- fs.addException('startup.lua', 1) + +-- To renable compression for a file +-- fs.removeException('startup.lua', 1) + +-- Restores file system +-- fs.restore() + +local char = string.char +local type = type +local sub = string.sub +local tconcat = table.concat +local tinsert = table.insert + +local SIGC = 'LZWC' +local IGNORE_COMPRESSION = 1 -- support other bits as well + +local basedictcompress = {} +local basedictdecompress = {} +for i = 0, 255 do + local ic, iic = char(i), char(i, 0) + basedictcompress[ic] = iic + basedictdecompress[iic] = ic +end + +local native = { open = fs.open } +fs.exceptions = fs.exceptions or { } + +local function dictAddA(str, dict, a, b) + if a >= 256 then + a, b = 0, b+1 + if b >= 256 then + dict = {} + b = 1 + end + end + dict[str] = char(a,b) + a = a+1 + return dict, a, b +end + +local function compress(input) + if type(input) ~= "string" then + error ("string expected, got "..type(input)) + end + local len = #input + if len <= 1 then + return input + end + + local dict = {} + local a, b = 0, 1 + + local result = { SIGC } + local resultlen = 1 + local n = 2 + local word = "" + for i = 1, len do + local c = sub(input, i, i) + local wc = word..c + if not (basedictcompress[wc] or dict[wc]) then + local write = basedictcompress[word] or dict[word] + if not write then + error "algorithm error, could not fetch word" + end + result[n] = write + resultlen = resultlen + #write + n = n+1 + if len <= resultlen then + return input + end + dict, a, b = dictAddA(wc, dict, a, b) + word = c + else + word = wc + end + end + result[n] = basedictcompress[word] or dict[word] + resultlen = resultlen+#result[n] + if len <= resultlen then + return input + end + return tconcat(result) +end + +local function dictAddB(str, dict, a, b) + if a >= 256 then + a, b = 0, b+1 + if b >= 256 then + dict = {} + b = 1 + end + end + dict[char(a,b)] = str + a = a+1 + return dict, a, b +end + +local function decompress(input) + if type(input) ~= "string" then + error( "string expected, got "..type(input)) + end + + if #input <= 1 then + return input + end + + local control = sub(input, 1, 4) + if control ~= SIGC then + return input + end + input = sub(input, 5) + local len = #input + + if len < 2 then + error("invalid input - not a compressed string") + end + + local dict = {} + local a, b = 0, 1 + + local result = {} + local n = 1 + local last = sub(input, 1, 2) + result[n] = basedictdecompress[last] or dict[last] + n = n+1 + for i = 3, len, 2 do + local code = sub(input, i, i+1) + local lastStr = basedictdecompress[last] or dict[last] + if not lastStr then + error( "could not find last from dict. Invalid input?") + end + local toAdd = basedictdecompress[code] or dict[code] + if toAdd then + result[n] = toAdd + n = n+1 + dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b) + else + local tmp = lastStr..sub(lastStr, 1, 1) + result[n] = tmp + n = n+1 + dict, a, b = dictAddB(tmp, dict, a, b) + end + last = code + end + return tconcat(result) +end + +function split(str, pattern) + pattern = pattern or "(.-)\n" + local t = {} + local function helper(line) tinsert(t, line) return "" end + helper((str:gsub(pattern, helper))) + return t +end + +function fs.open(fname, flags) + if flags == 'r' then + local f, err = native.open(fname, 'rb') + if not f then + return f, err + end + + local ctr = 0 + local lines + return { + readLine = function() + if not lines then + lines = split(decompress(f.readAll())) + end + ctr = ctr + 1 + return lines[ctr] + end, + readAll = function() + return decompress(f.readAll()) + end, + close = function() + f.close() + end, + } + elseif flags == 'w' or flags == 'a' then + if bit.band(fs.exceptions[fs.combine(fname, '')] or 0, IGNORE_COMPRESSION) == IGNORE_COMPRESSION then + return native.open(fname, flags) + end + + local c = { } + + if flags == 'a' then + local f = fs.open(fname, 'r') + if f then + tinsert(c, f.readAll()) + f.close() + end + end + + local f, err = native.open(fname, 'wb') + if not f then + return f, err + end + + return { + write = function(str) + tinsert(c, str) + end, + writeLine = function(str) + tinsert(c, str) + tinsert(c, '\n') + end, + flush = function() + -- this isn't gonna work... + // f.write(compress(tconcat(c))) + f.flush(); + end, + close = function() + f.write(compress(tconcat(c))) + f.close() + end, + } + end + + return native.open(fname, flags) +end + +function fs.addException(fname, mode) + fname = fs.combine(fname, '') + fs.exceptions[fname] = bit.bor(fs.exceptions[fname] or 0, mode) +end + +function fs.removeException(fname, mode) + fname = fs.combine(fname, '') + fs.exceptions[fname] = bit.bxor(fs.exceptions[fname] or 0, mode) +end + +function fs.restore() + fs.open = native.open +end diff --git a/sys/modules/opus/jumper/core/bheap.lua b/sys/modules/opus/jumper/core/bheap.lua deleted file mode 100644 index c58ce2f..0000000 --- a/sys/modules/opus/jumper/core/bheap.lua +++ /dev/null @@ -1,175 +0,0 @@ ---- A light implementation of Binary heaps data structure. --- While running a search, some search algorithms (Astar, Dijkstra, Jump Point Search) have to maintains --- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow, --- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real --- problem especially when dozens of nodes are being processed (on large maps). --- --- The current module implements a binary heap --- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being --- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end --- up quickly. --- --- This module is internally used by the library on purpose. --- It should normally not be used explicitely, yet it remains fully accessible. --- - ---[[ - Notes: - This lighter implementation of binary heaps, based on : - https://github.com/Yonaba/Binary-Heaps ---]] - -if (...) then - - -- Dependency - local Utils = require((...):gsub('%.bheap$','.utils')) - - -- Local reference - local floor = math.floor - - -- Default comparison function - local function f_min(a,b) return a < b end - - -- Percolates up - local function percolate_up(heap, index) - if index == 1 then return end - local pIndex - if index <= 1 then return end - if index%2 == 0 then - pIndex = index/2 - else pIndex = (index-1)/2 - end - if not heap._sort(heap._heap[pIndex], heap._heap[index]) then - heap._heap[pIndex], heap._heap[index] = - heap._heap[index], heap._heap[pIndex] - percolate_up(heap, pIndex) - end - end - - -- Percolates down - local function percolate_down(heap,index) - local lfIndex,rtIndex,minIndex - lfIndex = 2*index - rtIndex = lfIndex + 1 - if rtIndex > heap._size then - if lfIndex > heap._size then return - else minIndex = lfIndex end - else - if heap._sort(heap._heap[lfIndex],heap._heap[rtIndex]) then - minIndex = lfIndex - else - minIndex = rtIndex - end - end - if not heap._sort(heap._heap[index],heap._heap[minIndex]) then - heap._heap[index],heap._heap[minIndex] = heap._heap[minIndex],heap._heap[index] - percolate_down(heap,minIndex) - end - end - - -- Produces a new heap - local function newHeap(template,comp) - return setmetatable({_heap = {}, - _sort = comp or f_min, _size = 0}, - template) - end - - - --- The `heap` class.
- -- This class is callable. - -- _Therefore,_ heap(...) _is used to instantiate new heaps_. - -- @type heap - local heap = setmetatable({}, - {__call = function(self,...) - return newHeap(self,...) - end}) - heap.__index = heap - - --- Checks if a `heap` is empty - -- @class function - -- @treturn bool __true__ of no item is queued in the heap, __false__ otherwise - -- @usage - -- if myHeap:empty() then - -- print('Heap is empty!') - -- end - function heap:empty() - return (self._size==0) - end - - --- Clears the `heap` (removes all items queued in the heap) - -- @class function - -- @treturn heap self (the calling `heap` itself, can be chained) - -- @usage myHeap:clear() - function heap:clear() - self._heap = {} - self._size = 0 - self._sort = self._sort or f_min - return self - end - - --- Adds a new item in the `heap` - -- @class function - -- @tparam value item a new value to be queued in the heap - -- @treturn heap self (the calling `heap` itself, can be chained) - -- @usage - -- myHeap:push(1) - -- -- or, with chaining - -- myHeap:push(1):push(2):push(4) - function heap:push(item) - if item then - self._size = self._size + 1 - self._heap[self._size] = item - percolate_up(self, self._size) - end - return self - end - - --- Pops from the `heap`. - -- Removes and returns the lowest cost item (with respect to the comparison function being used) from the `heap`. - -- @class function - -- @treturn value a value previously pushed into the heap - -- @usage - -- while not myHeap:empty() do - -- local lowestValue = myHeap:pop() - -- ... - -- end - function heap:pop() - local root - if self._size > 0 then - root = self._heap[1] - self._heap[1] = self._heap[self._size] - self._heap[self._size] = nil - self._size = self._size-1 - if self._size>1 then - percolate_down(self, 1) - end - end - return root - end - - --- Restores the `heap` property. - -- Reorders the `heap` with respect to the comparison function being used. - -- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`. - -- Otherwise, the whole `heap` will be cheacked. - -- @class function - -- @tparam[opt] value item the modified value - -- @treturn heap self (the calling `heap` itself, can be chained) - -- @usage myHeap:heapify() - function heap:heapify(item) - if self._size == 0 then return end - if item then - local i = Utils.indexOf(self._heap,item) - if i then - percolate_down(self, i) - percolate_up(self, i) - end - return - end - for i = floor(self._size/2),1,-1 do - percolate_down(self,i) - end - return self - end - - return heap -end \ No newline at end of file diff --git a/sys/modules/opus/jumper/core/node.lua b/sys/modules/opus/jumper/core/node.lua deleted file mode 100644 index 09d1db4..0000000 --- a/sys/modules/opus/jumper/core/node.lua +++ /dev/null @@ -1,41 +0,0 @@ ---- The Node class. --- The `node` represents a cell (or a tile) on a collision map. Basically, for each single cell (tile) --- in the collision map passed-in upon initialization, a `node` object will be generated --- and then cached within the `grid`. --- --- In the following implementation, nodes can be compared using the `<` operator. The comparison is --- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search --- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details. --- -if (...) then - - local Node = {} - Node.__index = Node - - function Node:new(x,y,z) - return setmetatable({x = x, y = y, z = z }, Node) - end - - -- Enables the use of operator '<' to compare nodes. - -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost - function Node.__lt(A,B) return (A._f < B._f) end - - function Node:getX() return self.x end - function Node:getY() return self.y end - function Node:getZ() return self.z end - - --- Clears temporary cached attributes of a `node`. - -- Deletes the attributes cached within a given node after a pathfinding call. - -- This function is internally used by the search algorithms, so you should not use it explicitely. - function Node:reset() - self._g, self._h, self._f = nil, nil, nil - self._opened, self._closed, self._parent = nil, nil, nil - return self - end - - return setmetatable(Node, - {__call = function(_,...) - return Node:new(...) - end} - ) -end \ No newline at end of file diff --git a/sys/modules/opus/jumper/core/path.lua b/sys/modules/opus/jumper/core/path.lua deleted file mode 100644 index f02c41b..0000000 --- a/sys/modules/opus/jumper/core/path.lua +++ /dev/null @@ -1,67 +0,0 @@ ---- The Path class. --- The `path` class is a structure which represents a path (ordered set of nodes) from a start location to a goal. --- An instance from this class would be a result of a request addressed to `Pathfinder:getPath`. --- --- This module is internally used by the library on purpose. --- It should normally not be used explicitely, yet it remains fully accessible. --- - -if (...) then - - local t_remove = table.remove - - local Path = {} - Path.__index = Path - - function Path:new() - return setmetatable({_nodes = {}}, Path) - end - - --- Iterates on each single `node` along a `path`. At each step of iteration, - -- returns the `node` plus a count value. Aliased as @{Path:nodes} - -- @usage - -- for node, count in p:iter() do - -- ... - -- end - function Path:nodes() - local i = 1 - return function() - if self._nodes[i] then - i = i+1 - return self._nodes[i-1],i-1 - end - end - end - - --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path` - -- consisting of straight moves. Does the opposite of @{Path:fill} - -- @class function - -- @treturn path self (the calling `path` itself, can be chained) - -- @see Path:fill - -- @usage p:filter() - function Path:filter() - local i = 2 - local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz - xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z - dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z - while true do - olddx, olddy, olddz = dx, dy, dz - if self._nodes[i+1] then - i = i+1 - xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z - dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z - if olddx == dx and olddy == dy and olddz == dz then - t_remove(self._nodes, i-1) - i = i - 1 - end - else break end - end - return self - end - - return setmetatable(Path, - {__call = function(_,...) - return Path:new(...) - end - }) -end \ No newline at end of file diff --git a/sys/modules/opus/jumper/core/utils.lua b/sys/modules/opus/jumper/core/utils.lua deleted file mode 100644 index ab5f764..0000000 --- a/sys/modules/opus/jumper/core/utils.lua +++ /dev/null @@ -1,57 +0,0 @@ --- Various utilities for Jumper top-level modules - -if (...) then - - -- Dependencies - local _PATH = (...):gsub('%.utils$','') - local Path = require (_PATH .. '.path') - - -- Local references - local pairs = pairs - local t_insert = table.insert - - -- Raw array items count - local function arraySize(t) - local count = 0 - for _ in pairs(t) do - count = count+1 - end - return count - end - - -- Extract a path from a given start/end position - local function traceBackPath(finder, node, startNode) - local path = Path:new() - path._grid = finder._grid - while true do - if node._parent then - t_insert(path._nodes,1,node) - node = node._parent - else - t_insert(path._nodes,1,startNode) - return path - end - end - end - - -- Lookup for value in a table - local indexOf = function(t,v) - for i = 1,#t do - if t[i] == v then return i end - end - return nil - end - - -- Is i out of range - local function outOfRange(i,low,up) - return (i< low or i > up) - end - - return { - arraySize = arraySize, - indexOf = indexOf, - outOfRange = outOfRange, - traceBackPath = traceBackPath - } - -end diff --git a/sys/modules/opus/jumper/grid.lua b/sys/modules/opus/jumper/grid.lua deleted file mode 100644 index 6cfc53a..0000000 --- a/sys/modules/opus/jumper/grid.lua +++ /dev/null @@ -1,101 +0,0 @@ ---- The Grid class. --- Implementation of the `grid` class. --- The `grid` is a implicit graph which represents the 2D --- world map layout on which the `pathfinder` object will run. --- During a search, the `pathfinder` object needs to save some critical values. --- These values are cached within each `node` --- object, and the whole set of nodes are tight inside the `grid` object itself. - -if (...) then - - -- Dependencies - local _PATH = (...):gsub('%.grid$','') - - -- Local references - local Utils = require (_PATH .. '.core.utils') - local Node = require (_PATH .. '.core.node') - - -- Local references - local setmetatable = setmetatable - - -- Offsets for straights moves - local straightOffsets = { - {x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]] - {x = 0, y = 1, z = 0} --[[S]], {x = 0, y = -1, z = 0}, --[[N]] - {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]] - } - - local Grid = {} - Grid.__index = Grid - - function Grid:new(dim) - local newGrid = { } - newGrid._min_x, newGrid._max_x = dim.x, dim.ex - newGrid._min_y, newGrid._max_y = dim.y, dim.ey - newGrid._min_z, newGrid._max_z = dim.z, dim.ez - newGrid._nodes = { } - newGrid._width = (newGrid._max_x-newGrid._min_x)+1 - newGrid._height = (newGrid._max_y-newGrid._min_y)+1 - newGrid._length = (newGrid._max_z-newGrid._min_z)+1 - return setmetatable(newGrid,Grid) - end - - function Grid:isWalkableAt(x, y, z) - local node = self:getNodeAt(x,y,z) - return node and node.walkable ~= 1 - end - - function Grid:getWidth() - return self._width - end - - function Grid:getHeight() - return self._height - end - - function Grid:getNodes() - return self._nodes - end - - function Grid:getBounds() - return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z - end - - --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`. - -- @treturn {node,...} an array of nodes neighbouring a given node - function Grid:getNeighbours(node) - local neighbours = {} - for i = 1,#straightOffsets do - local n = self:getNodeAt( - node.x + straightOffsets[i].x, - node.y + straightOffsets[i].y, - node.z + straightOffsets[i].z - ) - if n and self:isWalkableAt(n.x, n.y, n.z) then - neighbours[#neighbours+1] = n - end - end - - return neighbours - end - - function Grid:getNodeAt(x,y,z) - if not x or not y or not z then return end - if Utils.outOfRange(x,self._min_x,self._max_x) then return end - if Utils.outOfRange(y,self._min_y,self._max_y) then return end - if Utils.outOfRange(z,self._min_z,self._max_z) then return end - - -- inefficient - if not self._nodes[y] then self._nodes[y] = {} end - if not self._nodes[y][x] then self._nodes[y][x] = {} end - if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end - return self._nodes[y][x][z] - end - - return setmetatable(Grid,{ - __call = function(self,...) - return self:new(...) - end - }) - -end diff --git a/sys/modules/opus/jumper/pathfinder.lua b/sys/modules/opus/jumper/pathfinder.lua deleted file mode 100644 index 0ff2844..0000000 --- a/sys/modules/opus/jumper/pathfinder.lua +++ /dev/null @@ -1,104 +0,0 @@ ---[[ - The following License applies to all files within the jumper directory. - - Note that this is only a partial copy of the full jumper code base. Also, - the code was modified to support 3D maps. ---]] - ---[[ -This work is under MIT-LICENSE -Copyright (c) 2012-2013 Roland Yonaba. - --- https://opensource.org/licenses/MIT - ---]] - -local _VERSION = "" -local _RELEASEDATE = "" - -if (...) then - - -- Dependencies - local _PATH = (...):gsub('%.pathfinder$','') - local Utils = require (_PATH .. '.core.utils') - - -- Internalization - local pairs = pairs - local assert = assert - local setmetatable = setmetatable - - --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper. - --
  • [A*](http://en.wikipedia.org/wiki/A*_search_algorithm)
  • - local Finders = { - ['ASTAR'] = require (_PATH .. '.search.astar'), - } - - -- Will keep track of all nodes expanded during the search - -- to easily reset their properties for the next pathfinding call - local toClear = {} - - -- Performs a traceback from the goal node to the start node - -- Only happens when the path was found - - local Pathfinder = {} - Pathfinder.__index = Pathfinder - - function Pathfinder:new(heuristic) - local newPathfinder = {} - setmetatable(newPathfinder, Pathfinder) - self._finder = Finders.ASTAR - self._heuristic = heuristic - return newPathfinder - end - - function Pathfinder:setGrid(grid) - self._grid = grid - return self - end - - --- Calculates a `path`. Returns the `path` from start to end location - -- Both locations must exist on the collision map. The starting location can be unwalkable. - -- @treturn path a path (array of nodes) when found, otherwise nil - -- @usage local path = myFinder:getPath(1,1,5,5) - function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh) - self:reset() - local startNode = self._grid:getNodeAt(startX, startY, startZ) - local endNode = self._grid:getNodeAt(endX, endY, endZ) - if not startNode or not endNode then - return nil - end - - startNode.heading = ih - endNode.heading = oh - - assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ)) - assert(endNode and self._grid:isWalkableAt(endX, endY, endZ), - ('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ)) - local _endNode = self._finder(self, startNode, endNode, toClear) - if _endNode then - return Utils.traceBackPath(self, _endNode, startNode) - end - return nil - end - - --- Resets the `pathfinder`. This function is called internally between - -- successive pathfinding calls, so you should not - -- use it explicitely, unless under specific circumstances. - -- @class function - -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained) - -- @usage local path, len = myFinder:getPath(1,1,5,5) - function Pathfinder:reset() - for node in pairs(toClear) do node:reset() end - toClear = {} - return self - end - - -- Returns Pathfinder class - Pathfinder._VERSION = _VERSION - Pathfinder._RELEASEDATE = _RELEASEDATE - return setmetatable(Pathfinder,{ - __call = function(self,...) - return self:new(...) - end - }) -end diff --git a/sys/modules/opus/jumper/search/astar.lua b/sys/modules/opus/jumper/search/astar.lua deleted file mode 100644 index c92dabc..0000000 --- a/sys/modules/opus/jumper/search/astar.lua +++ /dev/null @@ -1,77 +0,0 @@ --- Astar algorithm --- This actual implementation of A-star is based on --- [Nash A. & al. pseudocode](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/) - -if (...) then - - -- Internalization - local huge = math.huge - - -- Dependancies - local _PATH = (...):match('(.+)%.search.astar$') - local Heap = require (_PATH.. '.core.bheap') - - -- Updates G-cost - local function computeCost(node, neighbour, heuristic) - local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node) - - if node._g + mCost < neighbour._g then - neighbour._parent = node - neighbour._g = node._g + mCost - neighbour.heading = heading - end - end - - -- Updates vertex node-neighbour - local function updateVertex(openList, node, neighbour, endNode, heuristic) - local oldG = neighbour._g - computeCost(node, neighbour, heuristic) - if neighbour._g < oldG then - if neighbour._opened then neighbour._opened = false end - neighbour._h = heuristic(endNode, neighbour) - neighbour._f = neighbour._g + neighbour._h - openList:push(neighbour) - neighbour._opened = true - end - end - - -- Calculates a path. - -- Returns the path from location `` to location ``. - return function (finder, startNode, endNode, toClear) - local openList = Heap() - startNode._g = 0 - startNode._h = finder._heuristic(endNode, startNode) - startNode._f = startNode._g + startNode._h - openList:push(startNode) - toClear[startNode] = true - startNode._opened = true - - while not openList:empty() do - local node = openList:pop() - node._closed = true - if node == endNode then return node end - local neighbours = finder._grid:getNeighbours(node) - for i = 1,#neighbours do - local neighbour = neighbours[i] - if not neighbour._closed then - toClear[neighbour] = true - if not neighbour._opened then - neighbour._g = huge - neighbour._parent = nil - end - updateVertex(openList, node, neighbour, endNode, finder._heuristic) - end - end - - --[[ - printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g) - for i = 1,#neighbours do - local n = neighbours[i] - printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1) - end - --]] - - end - return nil - end -end diff --git a/sys/modules/opus/packages.lua b/sys/modules/opus/packages.lua index 0c5187a..8b0f5e1 100644 --- a/sys/modules/opus/packages.lua +++ b/sys/modules/opus/packages.lua @@ -20,6 +20,29 @@ function Packages:installed() return list end +function Packages:installedSorted() + local list = { } + + for k, v in pairs(self.installed()) do + v.name = k + v.deps = { } + table.insert(list, v) + for _, v2 in pairs(v.required or { }) do + v.deps[v2] = true + end + end + + table.sort(list, function(a, b) + return not not (b.deps and b.deps[a.name]) + end) + + table.sort(list, function(a, b) + return not (a.deps and a.deps[b.name]) + end) + + return list +end + function Packages:list() if not fs.exists('usr/config/packages') then self:downloadList() diff --git a/sys/modules/opus/pathfind.lua b/sys/modules/opus/pathfind.lua deleted file mode 100644 index 06c12f1..0000000 --- a/sys/modules/opus/pathfind.lua +++ /dev/null @@ -1,256 +0,0 @@ -local Grid = require('opus.jumper.grid') -local Pathfinder = require('opus.jumper.pathfinder') -local Point = require('opus.point') -local Util = require('opus.util') - -local turtle = _G.turtle - -local function addBlock(grid, b, dim) - if Point.inBox(b, dim) then - local node = grid:getNodeAt(b.x, b.y, b.z) - if node then - node.walkable = 1 - end - end -end - --- map shrinks/grows depending upon blocks encountered --- the map will encompass any blocks encountered, the turtle position, and the destination -local function mapDimensions(dest, blocks, boundingBox, dests) - local box = Point.makeBox(turtle.point, turtle.point) - - Point.expandBox(box, dest) - - for _,d in pairs(dests) do - Point.expandBox(box, d) - end - - for _,b in pairs(blocks) do - Point.expandBox(box, b) - end - - -- expand one block out in all directions - if boundingBox then - box.x = math.max(box.x - 1, boundingBox.x) - box.z = math.max(box.z - 1, boundingBox.z) - box.y = math.max(box.y - 1, boundingBox.y) - box.ex = math.min(box.ex + 1, boundingBox.ex) - box.ez = math.min(box.ez + 1, boundingBox.ez) - box.ey = math.min(box.ey + 1, boundingBox.ey) - else - box.x = box.x - 1 - box.z = box.z - 1 - box.y = box.y - 1 - box.ex = box.ex + 1 - box.ez = box.ez + 1 - box.ey = box.ey + 1 - end - - return box -end - -local function nodeToPoint(node) - return { x = node.x, y = node.y, z = node.z, heading = node.heading } -end - -local function heuristic(n, node) - return Point.calculateMoves(node, n) --- { x = node.x, y = node.y, z = node.z, heading = node.heading }, --- { x = n.x, y = n.y, z = n.z, heading = n.heading }) -end - -local function dimsAreEqual(d1, d2) - return d1.ex == d2.ex and - d1.ey == d2.ey and - d1.ez == d2.ez and - d1.x == d2.x and - d1.y == d2.y and - d1.z == d2.z -end - --- turtle sensor returns blocks in relation to the world - not turtle orientation --- so cannot figure out block location unless we know our orientation in the world --- really kinda dumb since it returns the coordinates as offsets of our location --- instead of true coordinates -local function addSensorBlocks(blocks, sblocks) - for _,b in pairs(sblocks) do - if b.type ~= 'AIR' then - local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z } - pt.x = pt.x - b.x - pt.z = pt.z - b.z -- this will only work if we were originally facing west - local found = false - for _,ob in pairs(blocks) do - if pt.x == ob.x and pt.y == ob.y and pt.z == ob.z then - found = true - break - end - end - if not found then - table.insert(blocks, pt) - end - end - end -end - -local function selectDestination(pts, box, grid) - while #pts > 0 do - local pt = Point.closest(turtle.point, pts) - if box and not Point.inBox(pt, box) then - Util.removeByValue(pts, pt) - else - if grid:isWalkableAt(pt.x, pt.y, pt.z) then - return pt - end - Util.removeByValue(pts, pt) - end - end -end - -local function updateCanvas(path) - local t = { } - for node in path:nodes() do - table.insert(t, { x = node.x, y = node.y, z = node.z }) - end - os.queueEvent('canvas', { - type = 'canvas_path', - data = t, - }) -end - -local function pathTo(dest, options) - local blocks = options.blocks or turtle.getState().blocks or { } - local dests = options.dest or { dest } -- support alternative destinations - local box = options.box or turtle.getState().box - local lastDim - local grid - - if box then - box = Point.normalizeBox(box) - end - - -- Creates a pathfinder object - local finder = Pathfinder(heuristic) - - while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do - - -- map expands as we encounter obstacles - local dim = mapDimensions(dest, blocks, box, dests) - - -- reuse map if possible - if not lastDim or not dimsAreEqual(dim, lastDim) then - -- Creates a grid object - grid = Grid(dim) - finder:setGrid(grid) - - lastDim = dim - end - for _,b in pairs(blocks) do - addBlock(grid, b, dim) - end - - dest = selectDestination(dests, box, grid) - if not dest then - return false, 'failed to reach destination' - end - if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then - break - end - - -- Define start and goal locations coordinates - local startPt = turtle.point - - -- Calculates the path, and its length - local path = finder:getPath( - startPt.x, startPt.y, startPt.z, turtle.point.heading, - dest.x, dest.y, dest.z, dest.heading) - - if not path then - Util.removeByValue(dests, dest) - else - updateCanvas(path) - - path:filter() - - for node in path:nodes() do - local pt = nodeToPoint(node) - - if turtle.isAborted() then - return false, 'aborted' - end - ---if this is the next to last node ---and we are traveling up or down, then the ---heading for this node should be the heading of the last node ---or, maybe.. ---if last node is up or down (or either?) - - -- use single turn method so the turtle doesn't turn around - -- when encountering obstacles - --if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then - pt.heading = nil - if not turtle.go(pt) then - local bpt = Point.nearestTo(turtle.point, pt) - - if turtle.getFuelLevel() == 0 then - return false, 'Out of fuel' - end - table.insert(blocks, bpt) - os.queueEvent('canvas', { - type = 'canvas_barrier', - data = { bpt }, - }) - -- really need to check if the block we ran into was a turtle. - -- if so, this block should be temporary (1-2 secs) - - --local side = turtle.getSide(turtle.point, pt) - --if turtle.isTurtleAtSide(side) then - -- pt.timestamp = os.clock() + ? - --end - -- if dim has not changed, then need to update grid with - -- walkable = nil (after time has elapsed) - - --if device.turtlesensorenvironment then - -- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan()) - --end - break - end - end - end - end - - if dest.heading then - turtle.setHeading(dest.heading) - end - return dest -end - -return { - pathfind = function(dest, options) - options = options or { } - --if not options.blocks and turtle.gotoPoint(dest) then - -- return dest - --end - return pathTo(dest, options) - end, - - -- set a global bounding box - -- box can be overridden by passing box in pathfind options - setBox = function(box) - turtle.getState().box = box - end, - - setBlocks = function(blocks) - turtle.getState().blocks = blocks - end, - - addBlock = function(block) - if turtle.getState().blocks then - table.insert(turtle.getState().blocks, block) - end - end, - - reset = function() - turtle.getState().box = nil - turtle.getState().blocks = nil - end, -}