Add more
This commit is contained in:
parent
89a15d277f
commit
f7bb6c8137
5 changed files with 1055 additions and 0 deletions
66
autofeed.lua
Normal file
66
autofeed.lua
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
-- AutoFeed module for Minit
|
||||||
|
-- Copyright (C) 2023 AlexDevs
|
||||||
|
-- This software is licensed under the MIT license.
|
||||||
|
|
||||||
|
local module = {
|
||||||
|
name = "autofeed",
|
||||||
|
}
|
||||||
|
|
||||||
|
local saturation = 7
|
||||||
|
local foods = {
|
||||||
|
"golden_carrot",
|
||||||
|
"popcorn",
|
||||||
|
"cooked_beef",
|
||||||
|
"cooked_pork",
|
||||||
|
"cooked_chicken",
|
||||||
|
"baked_potato",
|
||||||
|
"cooked_cod",
|
||||||
|
"cooked_salmon",
|
||||||
|
"bread",
|
||||||
|
"melon_slice",
|
||||||
|
"cooked_rabbit",
|
||||||
|
"cooked_mutton",
|
||||||
|
}
|
||||||
|
|
||||||
|
local neural
|
||||||
|
|
||||||
|
local function has(arr, val)
|
||||||
|
for k, v in ipairs(arr) do
|
||||||
|
if val:find(v) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findSlot(inventory)
|
||||||
|
local list = inventory.list()
|
||||||
|
for k, v in pairs(list) do
|
||||||
|
if has(foods, v.name) then
|
||||||
|
return k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function feed()
|
||||||
|
local inventory = neural.getInventory()
|
||||||
|
local foodSlot = findSlot(inventory)
|
||||||
|
if foodSlot == -1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
inventory.consume(foodSlot)
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.setup(ni)
|
||||||
|
neural = ni
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.update(meta)
|
||||||
|
if meta.food.hungry or meta.food.saturation < saturation then
|
||||||
|
feed()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return module
|
||||||
68
client.lua
Normal file
68
client.lua
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
local modules = {}
|
||||||
|
|
||||||
|
local function getIndex(tab, val)
|
||||||
|
local index = nil
|
||||||
|
for i, v in ipairs (tab) do
|
||||||
|
if (v == val) then
|
||||||
|
index = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local kinetic = peripheral.wrap("back")
|
||||||
|
local canvas = peripheral.wrap("back").canvas()
|
||||||
|
|
||||||
|
timer = os.startTimer(0.5)
|
||||||
|
canvas.clear()
|
||||||
|
|
||||||
|
local group = canvas.addGroup({ 0, 0 })
|
||||||
|
local text = group.addText({ 5, 5 }, "")
|
||||||
|
|
||||||
|
text.setText("this is NOT a client")
|
||||||
|
|
||||||
|
local moduleText = group.addText({ 5, 15 }, table.concat(modules, "\n"))
|
||||||
|
moduleText.setScale(0.5)
|
||||||
|
local function enableModule(module)
|
||||||
|
table.insert(modules, module)
|
||||||
|
moduleText.setText(table.concat(modules, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function disableModule(module)
|
||||||
|
local idx = getIndex(modules, module)
|
||||||
|
table.remove(modules, idx)
|
||||||
|
moduleText.setText(table.concat(modules, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isModuleEnabled(module)
|
||||||
|
local idx = getIndex(modules, module)
|
||||||
|
return idx ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function checkKey()
|
||||||
|
while true do
|
||||||
|
local event, key, is_held = os.pullEvent("key")
|
||||||
|
if keys.getName(key) == "g" then
|
||||||
|
if isModuleEnabled("flight") then
|
||||||
|
disableModule("flight")
|
||||||
|
else
|
||||||
|
enableModule("flight")
|
||||||
|
timer = os.startTimer(0.5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function flyEvent()
|
||||||
|
while true do
|
||||||
|
local _, tid = os.pullEvent("timer")
|
||||||
|
if tid == timer and isModuleEnabled("flight") then
|
||||||
|
timer = os.startTimer(0.5)
|
||||||
|
kinetic.launch(0, -90, 4);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
parallel.waitForAll(checkKey, flyEvent)
|
||||||
250
flight.lua
Normal file
250
flight.lua
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
-- Elytra Flight module for Minit
|
||||||
|
-- Copyright (C) 2023 AlexDevs
|
||||||
|
-- This software is licensed under the MIT license.
|
||||||
|
|
||||||
|
local module = {
|
||||||
|
name = "flight",
|
||||||
|
}
|
||||||
|
|
||||||
|
local locale = {
|
||||||
|
direction = "%s : %d", -- windrose : pitch
|
||||||
|
altitude = "Y: %d",
|
||||||
|
yMotion = "Y: %.2f",
|
||||||
|
speed = "%.2f m/s",
|
||||||
|
}
|
||||||
|
|
||||||
|
local icons = {
|
||||||
|
empty = "air",
|
||||||
|
fly = "elytra",
|
||||||
|
launch = "firework_rocket",
|
||||||
|
slow = "feather",
|
||||||
|
disabled = "barrier",
|
||||||
|
propelling = "blaze_powder"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Default variables. Use settings file to change values.
|
||||||
|
settings.define("elytra.power", {
|
||||||
|
description = "Propeller power",
|
||||||
|
type = "number",
|
||||||
|
default = 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.pitch", {
|
||||||
|
description = "Propeller pitch treshold for automatic mode",
|
||||||
|
type = "number",
|
||||||
|
default = 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.scale", {
|
||||||
|
description = "Scale of UI",
|
||||||
|
type = "number",
|
||||||
|
default = 0.6,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.sounds", {
|
||||||
|
description = "Enable sound effects",
|
||||||
|
type = "boolean",
|
||||||
|
default = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.manual", {
|
||||||
|
description = "Press SHIFT while flying to propel",
|
||||||
|
type = "boolean",
|
||||||
|
default = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.softfall.enable", {
|
||||||
|
description = "Enable softfall",
|
||||||
|
type = "boolean",
|
||||||
|
default = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.softfall.basepower", {
|
||||||
|
description = "Base power for soft fall",
|
||||||
|
type = "number",
|
||||||
|
default = 0.75,
|
||||||
|
})
|
||||||
|
|
||||||
|
settings.define("elytra.softfall.trigger", {
|
||||||
|
description = "Minimum negative Y motion required to trigger soft fall. USE NEGATIVE NUMBERS",
|
||||||
|
type = "number",
|
||||||
|
default = -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
local neural, speaker
|
||||||
|
local canvas, container
|
||||||
|
local screen = {}
|
||||||
|
local currentY = 0
|
||||||
|
|
||||||
|
local function getRoseWind(degrees)
|
||||||
|
degrees = degrees + 180
|
||||||
|
local directions = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }
|
||||||
|
local index = math.floor((degrees / 45) + 0.5) % 8
|
||||||
|
return directions[index + 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createScreen()
|
||||||
|
canvas = neural.canvas()
|
||||||
|
screen = {}
|
||||||
|
|
||||||
|
if container then
|
||||||
|
pcall(container.clear)
|
||||||
|
end
|
||||||
|
|
||||||
|
container = canvas.addGroup({ 1, 1 })
|
||||||
|
|
||||||
|
local scale = settings.get("elytra.scale")
|
||||||
|
local x, y = 25, math.ceil(9 * scale)
|
||||||
|
|
||||||
|
screen.icon = container.addItem({ 0, 1 }, "elytra")
|
||||||
|
screen.speed = container.addText({ x, y * 1 }, "")
|
||||||
|
screen.speed.setScale(scale)
|
||||||
|
|
||||||
|
screen.altitude = container.addText({ x, y * 2 }, "")
|
||||||
|
screen.altitude.setScale(scale)
|
||||||
|
|
||||||
|
screen.direction = container.addText({ x, y * 3 }, "")
|
||||||
|
screen.direction.setScale(scale)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function updateScreen(meta)
|
||||||
|
if not canvas or not container then
|
||||||
|
createScreen()
|
||||||
|
end
|
||||||
|
|
||||||
|
local mVector = vector.new(meta.deltaPosX, meta.deltaPosY, meta.deltaPosZ)
|
||||||
|
local speed = mVector:length() * 20
|
||||||
|
screen.speed.setText(string.format(locale.speed, speed))
|
||||||
|
screen.direction.setText(string.format(locale.direction, getRoseWind(meta.yaw), meta.pitch))
|
||||||
|
screen.altitude.setText(string.format(locale.altitude, currentY))
|
||||||
|
end
|
||||||
|
|
||||||
|
local icon = icons.empty
|
||||||
|
local function toggleScreen(show)
|
||||||
|
local alpha = show and 0xff or 0
|
||||||
|
screen.icon.setItem(show and icon or icons.empty)
|
||||||
|
screen.speed.setAlpha(alpha)
|
||||||
|
screen.altitude.setAlpha(alpha)
|
||||||
|
screen.direction.setAlpha(alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setIcon(newIcon)
|
||||||
|
icon = newIcon
|
||||||
|
screen.icon.setItem(newIcon)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function playSound(sound, volume, pitch)
|
||||||
|
if speaker and settings.get("elytra.sounds") then
|
||||||
|
speaker.playSound(sound, volume, pitch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function launch(yaw, pitch, power)
|
||||||
|
power = power or settings.get("elytra.power")
|
||||||
|
neural.launch(yaw, pitch, math.min(power, 4))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function launchUp(power)
|
||||||
|
launch(0, -90, power)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function softFall(motionY)
|
||||||
|
motionY = motionY or 0
|
||||||
|
launchUp(-motionY + settings.get("elytra.softfall.basepower"))
|
||||||
|
playSound("minecraft:entity.phantom.flap", 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function canPropel(meta)
|
||||||
|
return settings.get("elytra.manual") and meta.isSneaking or not meta.isSneaking
|
||||||
|
end
|
||||||
|
|
||||||
|
local function propel(meta, icon)
|
||||||
|
neural.launch(meta.yaw, meta.pitch, settings.get("elytra.power"))
|
||||||
|
playSound("minecraft:entity.fishing_bobber.throw", 0.4, 1)
|
||||||
|
|
||||||
|
if icon then
|
||||||
|
setIcon(icon)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gpsLocate()
|
||||||
|
while true do
|
||||||
|
local x, y, z = gps.locate()
|
||||||
|
if x then
|
||||||
|
currentY = y
|
||||||
|
end
|
||||||
|
sleep(0.5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.init(init)
|
||||||
|
init.addTask(gpsLocate)
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.setup(ni)
|
||||||
|
neural = ni
|
||||||
|
speaker = peripheral.find("speaker")
|
||||||
|
|
||||||
|
createScreen()
|
||||||
|
toggleScreen(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.update(meta)
|
||||||
|
if not meta.isElytraFlying then
|
||||||
|
if meta.isSneaking then
|
||||||
|
if meta.pitch == -90 then
|
||||||
|
setIcon(icons.launch)
|
||||||
|
launchUp(2)
|
||||||
|
else
|
||||||
|
setIcon(icons.disabled)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if meta.deltaPosY < settings.get("elytra.softfall.trigger") and settings.get("elytra.softfall.enable") then
|
||||||
|
softFall(meta.motionY)
|
||||||
|
setIcon(icons.slow)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
toggleScreen(false)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
toggleScreen(true)
|
||||||
|
updateScreen(meta)
|
||||||
|
|
||||||
|
local pitch = meta.pitch
|
||||||
|
local yaw = meta.yaw
|
||||||
|
|
||||||
|
if not canPropel(meta) then
|
||||||
|
setIcon(icons.disabled)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if settings.get("elytra.manual") then
|
||||||
|
if meta.isSneaking then
|
||||||
|
propel(meta, icons.propelling)
|
||||||
|
else
|
||||||
|
if meta.deltaPosY < settings.get("elytra.softfall.trigger") and settings.get("elytra.softfall.enable") then
|
||||||
|
softFall(meta.motionY)
|
||||||
|
setIcon(icons.slow)
|
||||||
|
else
|
||||||
|
setIcon(icons.fly)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if pitch > settings.get("elytra.pitch") then
|
||||||
|
if meta.deltaPosY < settings.get("elytra.softfall.trigger") and settings.get("elytra.softfall.enable") then
|
||||||
|
softFall(meta.motionY)
|
||||||
|
setIcon(icons.slow)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
setIcon(icons.fly)
|
||||||
|
propel(meta, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return module
|
||||||
521
hopper.lua
Normal file
521
hopper.lua
Normal file
|
|
@ -0,0 +1,521 @@
|
||||||
|
-- Copyright umnikos (Alex Stefanov) 2023
|
||||||
|
-- Licensed under MIT license
|
||||||
|
-- Version 1.2BETA
|
||||||
|
|
||||||
|
-- TODO: take into account NBT data when stacking
|
||||||
|
|
||||||
|
local help_message = [[
|
||||||
|
hopper script v1.2, made by umnikos
|
||||||
|
|
||||||
|
usage: hopper {from} {to} [{item name}/{flag}]*
|
||||||
|
example: hopper *chest* *barrel* *:pink_wool
|
||||||
|
|
||||||
|
flags:
|
||||||
|
-once : run the script only once instead of in a loop (undo with -forever)
|
||||||
|
-quiet: print less things to the terminal (undo with -verbose)
|
||||||
|
-from_slot [slot]: restrict pulling to a single slot
|
||||||
|
-to_slot [slot]: restrict pushing to a single slot
|
||||||
|
-from_limit [num]: keep at least this many matching items in every source chest
|
||||||
|
-to_limit [num]: fill every destination chest with at most this many matching items
|
||||||
|
-sleep [num]: set the delay in seconds between each iteration (default is 1)]]
|
||||||
|
|
||||||
|
-- further things of note:
|
||||||
|
-- - `self` is a valid peripheral name if you're running the script from a turtle connected to a wired modem
|
||||||
|
-- - you can import this file as a library with `require "hopper"`
|
||||||
|
-- - the script will prioritize taking from almost empty stacks and filling into almost full stacks
|
||||||
|
|
||||||
|
local function noop()
|
||||||
|
end
|
||||||
|
|
||||||
|
local print = print
|
||||||
|
|
||||||
|
-- for debugging purposes
|
||||||
|
local function dump(o)
|
||||||
|
if type(o) == 'table' then
|
||||||
|
local s = '{ '
|
||||||
|
for k,v in pairs(o) do
|
||||||
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||||
|
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||||
|
end
|
||||||
|
return s .. '} '
|
||||||
|
else
|
||||||
|
return tostring(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_empty(t)
|
||||||
|
return next(t) == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function glob(p, s)
|
||||||
|
local p = "^"..string.gsub(p,"*",".*").."$"
|
||||||
|
local res = string.find(s,p)
|
||||||
|
return res ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function default_options(options)
|
||||||
|
if not options then
|
||||||
|
options = {}
|
||||||
|
end
|
||||||
|
if options.quiet == nil then
|
||||||
|
options.quiet = true
|
||||||
|
end
|
||||||
|
if options.once == nil then
|
||||||
|
options.once = true
|
||||||
|
end
|
||||||
|
if options.sleep == nil then
|
||||||
|
options.sleep = 1
|
||||||
|
end
|
||||||
|
--IDEA: to/from slot ranges instead of singular slots
|
||||||
|
--if type(options.from_slot) == "number" then
|
||||||
|
-- options.from_slot = {options.from_slot, options.from_slot}
|
||||||
|
--end
|
||||||
|
--if type(options.to_slot) == "number" then
|
||||||
|
-- options.to_slot = {options.to_slot, options.to_slot}
|
||||||
|
--end
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
|
||||||
|
local function default_filters(filters)
|
||||||
|
if not filters then
|
||||||
|
filters = {}
|
||||||
|
end
|
||||||
|
if type(filters) == "string" then
|
||||||
|
filters = {filters}
|
||||||
|
end
|
||||||
|
return filters
|
||||||
|
end
|
||||||
|
|
||||||
|
local function display_info(from, to, sources, destinations, filters, options)
|
||||||
|
if options.quiet then print = noop end
|
||||||
|
|
||||||
|
print("hoppering from "..from)
|
||||||
|
if options.from_slot then
|
||||||
|
print("and only from slot "..tostring(options.from_slot))
|
||||||
|
end
|
||||||
|
if options.from_limit then
|
||||||
|
print("keeping at least "..tostring(options.from_limit).." items in reserve per container")
|
||||||
|
end
|
||||||
|
if #sources == 0 then
|
||||||
|
print("except there's nothing matching that description!")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
print("to "..to)
|
||||||
|
if #destinations == 0 then
|
||||||
|
print("except there's nothing matching that description!")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if options.to_slot then
|
||||||
|
print("and only to slot "..tostring(options.to_slot))
|
||||||
|
end
|
||||||
|
if options.to_limit then
|
||||||
|
print("filling up to "..tostring(options.to_limit).." items per container")
|
||||||
|
end
|
||||||
|
|
||||||
|
if #filters == 1 and filters[1] ~= "*" then
|
||||||
|
print("only the items matching the filter "..filters[1])
|
||||||
|
elseif #filters > 1 then
|
||||||
|
print("only the items matching any of the "..tostring(#filters).." filters")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function matches_filters(filters,s)
|
||||||
|
if #filters == 0 then return true end
|
||||||
|
for _,filter in pairs(filters) do
|
||||||
|
--print(filter)
|
||||||
|
if glob(filter,s) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if the computer has storage (aka. is a turtle)
|
||||||
|
-- we'd like to be able to transfer to it
|
||||||
|
local self = nil
|
||||||
|
local function determine_self()
|
||||||
|
if not turtle then return end
|
||||||
|
for _,dir in ipairs({"top","front","bottom","back"}) do
|
||||||
|
local p = peripheral.wrap(dir)
|
||||||
|
--print(p)
|
||||||
|
if p and p.getNameLocal then
|
||||||
|
--print("FOUND SELF")
|
||||||
|
self = p.getNameLocal()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function transfer(from,to,from_slot,to_slot,count)
|
||||||
|
if count <= 0 then
|
||||||
|
--print("WARNING: transfering 0 or less items?!?")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
--print("TRANSFER! from "..from.." to "..to)
|
||||||
|
if from ~= "self" then
|
||||||
|
if to == "self" then to = self end
|
||||||
|
return peripheral.call(from,"pushItems",to,from_slot,count,to_slot)
|
||||||
|
else
|
||||||
|
if to == "self" then
|
||||||
|
turtle.select(from_slot)
|
||||||
|
turtle.transferTo(to_slot,count)
|
||||||
|
else
|
||||||
|
return peripheral.call(to,"pullItems",self,from_slot,count,to_slot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local limits_cache = {}
|
||||||
|
local function chest_list(chest)
|
||||||
|
if chest ~= "self" then
|
||||||
|
local c = peripheral.wrap(chest)
|
||||||
|
local l = c.list()
|
||||||
|
for i,item in pairs(l) do
|
||||||
|
--print(i)
|
||||||
|
if limits_cache[item.name] == nil then
|
||||||
|
limits_cache[item.name] = c.getItemLimit(i)
|
||||||
|
end
|
||||||
|
l[i].limit = limits_cache[item.name]
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
else
|
||||||
|
local l = {}
|
||||||
|
for i=1,16 do
|
||||||
|
l[i] = turtle.getItemDetail(i,true)
|
||||||
|
if l[i] then
|
||||||
|
--print(i)
|
||||||
|
l[i].limit = l[i].maxCount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function chest_size(chest)
|
||||||
|
if chest == "self" then return 16 end
|
||||||
|
return peripheral.call(chest,"size")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hopper_step(from,to,sources,dests,filters,options)
|
||||||
|
--print("hopper step")
|
||||||
|
-- get all of the chests' contents
|
||||||
|
-- which we will be updating internally
|
||||||
|
-- in order to not have to list the chests
|
||||||
|
-- over and over again
|
||||||
|
local source_lists = {}
|
||||||
|
local dest_lists = {}
|
||||||
|
for _,source_name in ipairs(sources) do
|
||||||
|
source_lists[source_name] = chest_list(source_name)
|
||||||
|
end
|
||||||
|
for _,dest_name in ipairs(dests) do
|
||||||
|
dest_lists[dest_name] = chest_list(dest_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we will be iterating over item types to be moved
|
||||||
|
-- as well as over source and destination chests
|
||||||
|
-- in order to capitalize on knowing when the destinations are full
|
||||||
|
-- and when the sources are empty
|
||||||
|
-- so we can stop hoppering early
|
||||||
|
local item_jobs = {}
|
||||||
|
|
||||||
|
-- we will also prioritize filling items into slots
|
||||||
|
-- that already have existing partial stacks of those items
|
||||||
|
-- ideally there shouldn't be that many partial stacks
|
||||||
|
-- so this won't be horribly slow
|
||||||
|
local partial_source_slots = {}
|
||||||
|
local partial_dest_slots = {}
|
||||||
|
|
||||||
|
-- for to/from limits we'll also need to know
|
||||||
|
-- how many items per chest we can move
|
||||||
|
-- of every item type
|
||||||
|
local chest_contains = {}
|
||||||
|
|
||||||
|
for source_name,source_list in pairs(source_lists) do
|
||||||
|
chest_contains[source_name] = chest_contains[source_name] or {}
|
||||||
|
for i,item in pairs(source_list) do
|
||||||
|
if not (options.from_slot and options.from_slot ~= i) then
|
||||||
|
if matches_filters(filters,item.name) then
|
||||||
|
if not item_jobs[item.name] then
|
||||||
|
item_jobs[item.name] = 0
|
||||||
|
partial_source_slots[item.name] = {}
|
||||||
|
partial_dest_slots[item.name] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
item_jobs[item.name] = item_jobs[item.name] + item.count
|
||||||
|
chest_contains[source_name][item.name] = (chest_contains[source_name][item.name] or 0) + item.count
|
||||||
|
if item.count > 0 and item.count < item.limit then
|
||||||
|
partial_source_slots[item.name] = partial_source_slots[item.name] or {}
|
||||||
|
partial_source_slots[item.name][item.count] = partial_source_slots[item.name][item.count] or {}
|
||||||
|
table.insert(partial_source_slots[item.name][item.count], {source_name,i})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for dest_name,dest_list in pairs(dest_lists) do
|
||||||
|
chest_contains[dest_name] = chest_contains[dest_name] or {}
|
||||||
|
for i,item in pairs(dest_list) do
|
||||||
|
if not (options.to_slot and options.to_slot ~= i) then
|
||||||
|
if (item_jobs[item.name] or 0) > 0 then -- item name matches filter if so
|
||||||
|
chest_contains[dest_name][item.name] = (chest_contains[dest_name][item.name] or 0) + item.count
|
||||||
|
if item.count > 0 and item.count < item.limit then
|
||||||
|
partial_dest_slots[item.name] = partial_dest_slots[item.name] or {}
|
||||||
|
partial_dest_slots[item.name][item.count] = partial_dest_slots[item.name][item.count] or {}
|
||||||
|
table.insert(partial_dest_slots[item.name][item.count], {dest_name,i})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--print(dump(partial_source_slots))
|
||||||
|
--print(dump(partial_dest_slots))
|
||||||
|
|
||||||
|
-- and now for the actual hoppering
|
||||||
|
for item_name,_ in pairs(item_jobs) do
|
||||||
|
-- we first do it for the partially filled source slots only
|
||||||
|
-- into partially filled destinations only
|
||||||
|
local s = partial_source_slots[item_name]
|
||||||
|
local source_counts = {}
|
||||||
|
for c,_ in pairs(s) do table.insert(source_counts,c) end
|
||||||
|
local d = partial_dest_slots[item_name]
|
||||||
|
local dest_counts = {}
|
||||||
|
for c,_ in pairs(d) do table.insert(dest_counts,c) end
|
||||||
|
table.sort(source_counts)
|
||||||
|
table.sort(dest_counts)
|
||||||
|
local si = 1 -- container index
|
||||||
|
local sii = nil -- slot index
|
||||||
|
local ssi = nil -- whole container index
|
||||||
|
local ssii = nil -- whole container slot
|
||||||
|
local di = #dest_counts -- container index
|
||||||
|
local dii = nil -- slot index
|
||||||
|
local ddi = nil -- whole container index
|
||||||
|
|
||||||
|
if si > #source_counts then
|
||||||
|
ssi = #sources
|
||||||
|
ssii = chest_size(sources[ssi])
|
||||||
|
end
|
||||||
|
|
||||||
|
local source_name, source_i, source_amount
|
||||||
|
local dest_name, dest_i, dest_amount
|
||||||
|
local function get_source()
|
||||||
|
if ssi == nil then
|
||||||
|
if not sii then sii = #s[source_counts[si]] end
|
||||||
|
local source_name, source_i = table.unpack(s[source_counts[si]][sii])
|
||||||
|
local source_amount = source_lists[source_name][source_i].count
|
||||||
|
return source_name, source_i, source_amount
|
||||||
|
else
|
||||||
|
while ssi > 0 do
|
||||||
|
if options.from_slot then ssii = options.from_slot end
|
||||||
|
local item_found = source_lists[sources[ssi]][ssii]
|
||||||
|
-- TODO: replace ~= with comparison operators
|
||||||
|
if item_found and item_found.count > 0 and item_found.name == item_name and chest_contains[sources[ssi]][item_name] ~= options.from_limit then
|
||||||
|
return sources[ssi], ssii, item_found.count
|
||||||
|
end
|
||||||
|
ssii = ssii - 1
|
||||||
|
if ssii <= 0 or (options.from_slot and ssii < options.from_slot) then
|
||||||
|
ssi = ssi - 1
|
||||||
|
if ssi <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
ssii = chest_size(sources[ssi])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function update_source(amount, transferred) -- planned vs actual transffered amount
|
||||||
|
source_lists[source_name][source_i].count = source_lists[source_name][source_i].count - amount
|
||||||
|
chest_contains[source_name][item_name] = (chest_contains[source_name][item_name] or 0) - amount
|
||||||
|
if ssi == nil then
|
||||||
|
if source_lists[source_name][source_i].count == 0 or chest_contains[source_name][item_name] == options.from_limit then
|
||||||
|
sii = sii - 1
|
||||||
|
end
|
||||||
|
if sii <= 0 then
|
||||||
|
si = si + 1
|
||||||
|
sii = nil
|
||||||
|
if si > #source_counts then
|
||||||
|
ssi = #sources
|
||||||
|
ssii = chest_size(sources[ssi])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function get_dest()
|
||||||
|
if di and di < 1 then
|
||||||
|
ddi = #dests
|
||||||
|
di = nil
|
||||||
|
dii = nil
|
||||||
|
end
|
||||||
|
if ddi == nil then
|
||||||
|
if not dii then dii = #d[dest_counts[di]] end
|
||||||
|
local dest_name, dest_i = table.unpack(d[dest_counts[di]][dii])
|
||||||
|
local dest_amount = dest_lists[dest_name][dest_i].limit - dest_lists[dest_name][dest_i].count
|
||||||
|
return dest_name, dest_i, dest_amount
|
||||||
|
else
|
||||||
|
if options.to_slot then
|
||||||
|
-- find chest where slot is empty
|
||||||
|
while true do
|
||||||
|
if ddi < 1 then break end
|
||||||
|
if (chest_contains[dests[ddi]][item_name] or 0) ~= (options.to_limit or math.huge) then
|
||||||
|
if dest_lists[dests[ddi]][options.to_slot] == nil then break end
|
||||||
|
if dest_lists[dests[ddi]][options.to_slot].count == 0 then break end
|
||||||
|
end
|
||||||
|
ddi = ddi - 1
|
||||||
|
end
|
||||||
|
return dests[ddi], options.to_slot, math.huge
|
||||||
|
else
|
||||||
|
-- just shove into the chest and move to the next one if 0 get moved
|
||||||
|
return dests[ddi], nil, math.huge
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function update_dest(amount, transferred)
|
||||||
|
chest_contains[dest_name][item_name] = (chest_contains[dest_name][item_name] or 0) + amount
|
||||||
|
if ddi == nil then
|
||||||
|
-- TODO: this needs to be a thing even if ddi is not nil, else the list becomes invalid and is not reusable
|
||||||
|
dest_lists[dest_name][dest_i].count = dest_lists[dest_name][dest_i].count + amount
|
||||||
|
if dest_lists[dest_name][dest_i].limit - dest_lists[dest_name][dest_i].count == 0 or chest_contains[dest_name][item_name] == options.to_limit then
|
||||||
|
dii = dii - 1
|
||||||
|
end
|
||||||
|
if dii <= 0 then
|
||||||
|
di = di - 1
|
||||||
|
dii = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
--print(transferred == 0 and (chest_contains[source_name][item_name] or 0) ~= options.from_limit)
|
||||||
|
if transferred == 0 and (chest_contains[source_name][item_name] or 0) ~= options.from_limit then
|
||||||
|
ddi = ddi - 1
|
||||||
|
end
|
||||||
|
--print(ddi)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if item_jobs[item_name] <= 0 then break end
|
||||||
|
source_name, source_i, source_amount = get_source()
|
||||||
|
if source_name == nil then break end
|
||||||
|
dest_name, dest_i, dest_amount = get_dest()
|
||||||
|
if dest_name == nil then break end
|
||||||
|
--print(dump(chest_contains))
|
||||||
|
local amount = math.min(source_amount,
|
||||||
|
dest_amount,
|
||||||
|
(options.to_limit or math.huge) - (chest_contains[dest_name][item_name] or 0),
|
||||||
|
(chest_contains[source_name][item_name] or 0) - (options.from_limit or 0)
|
||||||
|
)
|
||||||
|
local transferred = transfer(source_name,dest_name,source_i,dest_i,amount)
|
||||||
|
|
||||||
|
update_source(amount, transferred)
|
||||||
|
update_dest(amount, transferred)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hopper(from,to,filters,options)
|
||||||
|
options = default_options(options)
|
||||||
|
filters = default_filters(filters)
|
||||||
|
|
||||||
|
determine_self()
|
||||||
|
--print("SELF IS:")
|
||||||
|
--print(self)
|
||||||
|
|
||||||
|
local peripherals = peripheral.getNames()
|
||||||
|
if self then
|
||||||
|
table.insert(peripherals,"self")
|
||||||
|
end
|
||||||
|
|
||||||
|
local sources = {}
|
||||||
|
local destinations = {}
|
||||||
|
for i,per in ipairs(peripherals) do
|
||||||
|
if glob(from,per) then
|
||||||
|
-- prevent the source and the destination ever being the same
|
||||||
|
-- (if a chest matches both, it's only a destination)
|
||||||
|
if (not glob(to,per)) or (options.to_slot and options.from_slot and options.from_slot ~= options.to_slot) then
|
||||||
|
sources[#sources+1] = per
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if glob(to,per) then
|
||||||
|
destinations[#destinations+1] = per
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local valid = display_info(from,to,sources,destinations,filters,options)
|
||||||
|
if not valid then return end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
hopper_step(from,to,sources,destinations,filters,options)
|
||||||
|
if options.once then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
sleep(options.sleep)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
|
||||||
|
local function main()
|
||||||
|
|
||||||
|
if args[1] == "hopper" then
|
||||||
|
return hopper
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args < 2 then
|
||||||
|
print(help_message)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local from = args[1]
|
||||||
|
local to = args[2]
|
||||||
|
local options = {}
|
||||||
|
options.once = false
|
||||||
|
options.quiet = false
|
||||||
|
|
||||||
|
local filters = {}
|
||||||
|
local i=3
|
||||||
|
while i <= #args do
|
||||||
|
if glob("-*",args[i]) then
|
||||||
|
if args[i] == "-once" then
|
||||||
|
--print("(only once!)")
|
||||||
|
options.once = true
|
||||||
|
elseif args[i] == "-forever" then
|
||||||
|
options.once = false
|
||||||
|
elseif args[i] == "-quiet" then
|
||||||
|
options.quiet = true
|
||||||
|
elseif args[i] == "-verbose" then
|
||||||
|
options.quiet = false
|
||||||
|
elseif args[i] == "-from_slot" then
|
||||||
|
i = i+1
|
||||||
|
options.from_slot = tonumber(args[i])
|
||||||
|
elseif args[i] == "-to_slot" then
|
||||||
|
i = i+1
|
||||||
|
options.to_slot = tonumber(args[i])
|
||||||
|
elseif args[i] == "-from_limit" then
|
||||||
|
i = i+1
|
||||||
|
options.from_limit = tonumber(args[i])
|
||||||
|
elseif args[i] == "-to_limit" then
|
||||||
|
i = i+1
|
||||||
|
options.to_limit = tonumber(args[i])
|
||||||
|
elseif args[i] == "-sleep" then
|
||||||
|
i = i+1
|
||||||
|
options.sleep = tonumber(args[i])
|
||||||
|
else
|
||||||
|
print("UNKNOWN ARGUMENT: "..args[i])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
filters[#filters+1] = args[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i+1
|
||||||
|
end
|
||||||
|
|
||||||
|
hopper(from,to,filters,options)
|
||||||
|
end
|
||||||
|
|
||||||
|
return main()
|
||||||
|
|
||||||
150
scanner.lua
Normal file
150
scanner.lua
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
--- This renders a minimap showing nearby ores using the overlay glasses and block scanner.
|
||||||
|
|
||||||
|
--- We start the program by specifying a series of configuration options. Feel free to ignore these, and use the values
|
||||||
|
--- inline. Whilst you don't strictly speaking need a delay between each iteration, it does reduce the impact on the
|
||||||
|
--- server.
|
||||||
|
local scanInterval = 0.2
|
||||||
|
local renderInterval = 0.05
|
||||||
|
local scannerRange = 8
|
||||||
|
local scannerWidth = scannerRange * 2 + 1
|
||||||
|
|
||||||
|
--- These values aren't very exciting, they just control what the minimap looks like
|
||||||
|
local size = 0.5
|
||||||
|
local cellSize = 16
|
||||||
|
local offsetX = 75
|
||||||
|
local offsetY = 75
|
||||||
|
|
||||||
|
--- We end our configuration section by defining the ores we're interested in and what colour we'll draw them as. We
|
||||||
|
--- define some ores as having a higher priority, so large ore veins don't mask smaller veins of more precious ores.
|
||||||
|
local ores = {
|
||||||
|
["minecraft:ancient_debris"] = 20,
|
||||||
|
["minecraft:diamond_ore"] = 10,
|
||||||
|
["minecraft:emerald_ore"] = 10,
|
||||||
|
["minecraft:gold_ore"] = 8,
|
||||||
|
["minecraft:redstone_ore"] = 5,
|
||||||
|
["minecraft:lapis_ore"] = 5,
|
||||||
|
["minecraft:iron_ore"] = 2,
|
||||||
|
--["minecraft:nether_quartz_ore"] = 2,
|
||||||
|
["minecraft:coal_ore"] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local colours = {
|
||||||
|
["minecraft:ancient_debris"] = { 255,255,255 },
|
||||||
|
--["minecraft:nether_quartz_ore"] = { 255, 255, 255 },
|
||||||
|
["minecraft:coal_ore"] = { 150, 150, 150 },
|
||||||
|
["minecraft:iron_ore"] = { 255, 150, 50 },
|
||||||
|
["minecraft:lava"] = { 150, 75, 0 },
|
||||||
|
["minecraft:gold_ore"] = { 255, 255, 0 },
|
||||||
|
["minecraft:diamond_ore"] = { 0, 255, 255 },
|
||||||
|
["minecraft:redstone_ore"] = { 255, 0, 0 },
|
||||||
|
["minecraft:lapis_ore"] = { 0, 50, 255 },
|
||||||
|
["minecraft:emerald_ore"] = { 0, 255, 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Now let's get into the interesting stuff! Let's look for a neural interface and check we've got all the required
|
||||||
|
--- modules.
|
||||||
|
local modules = peripheral.find("neuralInterface")
|
||||||
|
if not modules then error("Must have a neural interface", 0) end
|
||||||
|
if not modules.hasModule("plethora:scanner") then error("The block scanner is missing", 0) end
|
||||||
|
if not modules.hasModule("plethora:glasses") then error("The overlay glasses are missing", 0) end
|
||||||
|
|
||||||
|
--- Now we've got our neural interface, let's extract the canvas and ensure nothing else is on it.
|
||||||
|
local canvas = modules.canvas()
|
||||||
|
canvas.clear()
|
||||||
|
|
||||||
|
--- We now need to set up our minimap. We create a 2D array of text objects around the player, each starting off
|
||||||
|
--- displaying an empty string. If we find an ore, we'll update their colour and text.
|
||||||
|
local block_text = {}
|
||||||
|
local blocks = {}
|
||||||
|
for x = -scannerRange, scannerRange, 1 do
|
||||||
|
block_text[x] = {}
|
||||||
|
blocks[x] = {}
|
||||||
|
|
||||||
|
for z = -scannerRange, scannerRange, 1 do
|
||||||
|
block_text[x][z] = canvas.addText({ 0, 0 }, " ", 0xFFFFFFFF, size)
|
||||||
|
blocks[x][z] = { y = nil, block = nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- We also create a marker showing the current player's location.
|
||||||
|
canvas.addText({ offsetX, offsetY }, "^", 0xFFFFFFFF, size * 2)
|
||||||
|
|
||||||
|
--- Our first big function is the scanner: this searches for ores near the player, finds the most important ones, and
|
||||||
|
--- updates the block table.
|
||||||
|
local function scan()
|
||||||
|
while true do
|
||||||
|
local scanned_blocks = modules.scan()
|
||||||
|
|
||||||
|
--- For each nearby position, we search the y axis for interesting ores. We look for the one which has
|
||||||
|
--- the highest priority and update the block information
|
||||||
|
for x = -scannerRange, scannerRange do
|
||||||
|
for z = -scannerRange, scannerRange do
|
||||||
|
local best_score, best_block, best_y = -1
|
||||||
|
for y = -scannerRange, scannerRange do
|
||||||
|
--- The block scanner returns blocks in a flat array, so we index into it with this rather scary formulae.
|
||||||
|
local scanned = scanned_blocks[scannerWidth ^ 2 * (x + scannerRange) + scannerWidth * (y + scannerRange) + (z + scannerRange) + 1]
|
||||||
|
|
||||||
|
--- If there is a block here, and it's more interesting than our previous ores, then let's use that!
|
||||||
|
if scanned then
|
||||||
|
local new_score = ores[scanned.name]
|
||||||
|
if new_score and new_score > best_score then
|
||||||
|
best_block = scanned.name
|
||||||
|
best_score = new_score
|
||||||
|
best_y = y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update our block table with this information.
|
||||||
|
blocks[x][z].block = best_block
|
||||||
|
blocks[x][z].y = best_y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- We wait for some delay before starting again. This isn't _strictly_ needed, but helps reduce server load
|
||||||
|
sleep(scanInterval)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The render function takes our block information generated in the previous function and updates the text elements.
|
||||||
|
local function render()
|
||||||
|
while true do
|
||||||
|
--- If possible, we rotate the map using the current player's look direction. If it's not available, we'll just
|
||||||
|
--- use north as up.
|
||||||
|
local meta = modules.getMetaOwner and modules.getMetaOwner()
|
||||||
|
local angle = meta and math.rad(-meta.yaw % 360) or math.rad(180)
|
||||||
|
|
||||||
|
--- Like before, loop over every nearby block and update something. Though this time we're updating objects on
|
||||||
|
--- the overlay canvas.
|
||||||
|
for x = -scannerRange, scannerRange do
|
||||||
|
for z = -scannerRange, scannerRange do
|
||||||
|
local text = block_text[x][z]
|
||||||
|
local block = blocks[x][z]
|
||||||
|
|
||||||
|
if block.block then
|
||||||
|
--- If we've got a block here, we update the position of our text element to account for rotation,
|
||||||
|
local px = math.cos(angle) * -x - math.sin(angle) * -z
|
||||||
|
local py = math.sin(angle) * -x + math.cos(angle) * -z
|
||||||
|
|
||||||
|
local sx = math.floor(px * size * cellSize)
|
||||||
|
local sy = math.floor(py * size * cellSize)
|
||||||
|
text.setPosition(offsetX + sx, offsetY + sy)
|
||||||
|
|
||||||
|
--- Then change the text and colour to match the location of the ore
|
||||||
|
text.setText(tostring(block.y))
|
||||||
|
text.setColor(table.unpack(colours[block.block]))
|
||||||
|
else
|
||||||
|
--- Otherwise we just make sure the text is empty. We don't need to faff about with clearing the
|
||||||
|
--- colour or position, as we'll change it next iteration anyway.
|
||||||
|
text.setText(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep(renderInterval)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- We now run our render and scan loops in parallel, continually updating our block list and redisplaying it to the
|
||||||
|
--- wearer.
|
||||||
|
parallel.waitForAll(render, scan)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue