Commit b5728d33 authored by Fedja Beader's avatar Fedja Beader
Browse files

Add inventory networks CSM, a generic successor to inventory/biofuel.lua

parent 630bf7f7
--[[
Minetest+specing Client-Side Mod (CSM) for automatically moving items between inventories
Copyright (C) 2021 Fedja Beader
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
--]]
-- general idea:
-- register multiple locations (node, inventory, detached) hereafter refered to as 'machines'
-- each machine can be either a converter or storage
-- each converter has an associated recipe
-- -> accepts predetermined amount of different items and produces an amount of output items
-- -> for now, we assume that output is only one. (battery recipe has two - bucket and battery)
--
-- in each step, we:
-- 1) pick one machine from the list
-- 2) go through all other machines and collect available amount of input material
-- 3) move as much input material as possible (up to max_stack), such that there will be no recipe leftovers
--[[
register_on_jumpdrive_jump(function (jump)
l:info("translating coordinates on jump")
for i,m in pairs(machines) do
translate_position( jump, m.pos)
end
end)
--]]
local l = Logger:new("invnet")
local mod_storage = core.get_mod_storage()
local invnet = minetest.deserialize(mod_storage:get_string("network")) or {}
invnet.enable = invnet.enable or false
invnet.machines = invnet.machines or {}
local function invnet_reset()
invnet.enable = false
invnet.machines = {}
end
local function invnet_list()
for i,m in pairs(invnet.machines) do
l:info("Machine #" .. i .. " at " .. minetest.pos_to_string(m.pos) .. ": ")
for name,count in pairs(m.recipe or {}) do
l:info(" recipe consumes " .. count .. " of " .. name)
end
for name,count in pairs(m.output or {}) do
l:info(" recipe produces " .. count .. " of " .. name)
end
end
end
local function invnet_setup_item_move()
-- Pick a machine
local machines = invnet.machines
local machine = nil
for _ = 1,100 do
local m_index = math.random(1, #machines)
machine = machines[m_index]
l:debug("Testing machine " .. m_index)
if machine.recipe then -- skip chests
break
end
end
if not machine or not machine.recipe then return false end
l:info("Selected machine at " .. minetest.pos_to_string(machine.pos))
-- for each input item adjust total max_stack to minimum of them all, then
-- try to move at once as many stacks as there are items in the recipe
local sources = {}
local max_stack = math.huge -- max transfer size
local total_stacks = 0
for item_name,_ in pairs(machine.recipe) do
max_stack = math.min(max_stack, ItemStack(item_name):get_stack_max())
--l:debug(" corrected stack max to " .. max_stack .. " for " .. item_name)
end
l:debug(" corrected stack max to " .. max_stack)
for item_name,count in pairs(machine.recipe) do
local from = ItemLocations:new()
for _,src_machine in pairs(machines) do
from:collect(src_machine.pos, src_machine.output_list_name, item_name)
end
from:sort_by_count_dsc() -- larger stacks first
for i = 1,count do
if i > #from.locations or from.locations[i].count < max_stack then
l:debug(" insufficient stacks for item " .. item_name .. ", got only " .. (i-1))
return false
end
end
sources[item_name] = from
total_stacks = total_stacks + count
end
-- Do we have enough free slots to deposit items to?
local to = ItemLocations:new()
to:collect(machine.pos, machine.input_list_name, "")
if #to.locations < total_stacks then
l:debug(" insufficient space to deposit " .. total_stacks .. " stacks")
return false
end
-- If we made it to here, then we have enough items to craft the recipe => setup moving
-- list of from_loc & to_loc & count
local move_list = {}
local dst_i = 1
for item_name,from in pairs(sources) do
local count = machine.recipe[item_name]
for i = 1,count do
local src_loc = from.locations[i]
local dst_loc = to.locations[dst_i]
dst_i = dst_i + 1
l:debug(" will move " .. max_stack .. "*" .. item_name
.. " from " .. src_loc:to_string() .. " to " .. dst_loc:to_string())
table.insert(move_list, { src = src_loc, dst = dst_loc, count = max_stack })
end
end
-- schedule actual moves
for i,item in pairs(move_list) do
queue:prepend(function() return queued_move_stack(item.src, item.dst, item.count) end)
if math.mod(i,5) == 4 then -- add a small delay
queue:prepend(function() return 0.2 end)
end
end
return true
end
function invnet_step()
if not invnet.enable then return end
--l:debug("inventory network step")
local my_pos = core.localplayer:get_pos()
for _,m in pairs(invnet.machines) do
local distance = vector.distance(my_pos, m.pos)
if distance > 8 then
l:info("Too far (" .. distance .. ") from inventory " .. minetest.pos_to_string(m.pos) .. ", waiting")
core.after(15, invnet_step) -- use after as it takes forever
return
end
end
if invnet_setup_item_move() then -- setup as many hops as possible
--queue:append(invnet_step)
core.after(2, invnet_step) -- use after as it takes forever
queue:walk()
else
core.after(4, invnet_step) -- use after as it takes forever
end
end
local function get_machine_in_list(pos)
for i,m in pairs(invnet.machines) do
if vector.distance(m.pos, pos) < 0.1 then
return i
end
end
return #invnet.machines+1
end
local wait_for_punch = false
core.register_on_punchnode(function(pos, node)
if not wait_for_punch then return end
local machine_index = get_machine_in_list(pos)
if node.name == "biofuel:refinery" or node.name == "biofuel:refinery_active" then
local m = {}
m.recipe = {
["default:stick"] = 4,
}
m.input_list_name = "src"
m.output = {
["biofuel:bottle_fuel"] = 1
}
m.output_list_name = "dst"
m.pos = pos
invnet.machines[machine_index] = m
l:info("Added refinery")
elseif node.name == "pipeworks:autocrafter" then
local inv = core.get_inventory(pos)
local item_string = inv["output"][1]:get_name()
if item_string == "" then
l:err("autocrafter unconfigured")
else
local output_gain = inv["output"][1]:get_count()
local m = {}
m.recipe = {}
for _,stack in pairs(inv["recipe"]) do
local name = stack:get_name()
if name ~= "" then
m.recipe[name] = (m.recipe[name] or 0) + 1
end
end
m.input_list_name = "src"
m.output = {
[item_string] = output_gain
}
m.output_list_name = "dst"
m.pos = pos
invnet.machines[machine_index] = m
l:info("Added autocrafter for: " .. output_gain .. " of " .. item_string)
end
else
l:info("Unknown node punched: " .. node.name .. " adding it as source chest")
local inv = core.get_inventory(pos)
local m = {}
if inv["dst"] then
m.output_list_name = "dst"
elseif inv["main"] then
m.output_list_name = "main"
end
if m.output_list_name then
m.pos = pos
invnet.machines[machine_index] = m
end
end
mod_storage:set_string("network", minetest.serialize(invnet))
end)
local function help()
return ".invnet: inventory networks (automatic item moving between inventories), usage:\n"
.. ".invnet add -- toggles adding nodes to the network\n"
.. ".invnet list -- lists all inventories in the network\n"
.. ".invnet reset -- clears the entire network\n"
.. ".invnet start -- make other players jealous\n"
.. ".invnet stop -- regain use of your chat\n"
end
core.register_chatcommand("invnet", {
func = function(param)
local args = string.split(param, ' ')
if args[1] == "add" then
if wait_for_punch then
wait_for_punch = false
else
l:info("punch nodes to add them into inventory network. repeat this command to stop")
wait_for_punch = true
end
elseif args[1] == "list" then
invnet_list()
elseif args[1] == "reset" then
invnet_reset()
elseif args[1] == "start" then
invnet.enable = true
invnet_step()
elseif args[1] == "stop" then
invnet.enable = false
else
l:info(help())
end
end,
})
name = inventory_networks
depends = library
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment