Michal Cieslakiewicz a80a431ba5 minerchest: add Pipeworks tube support.
Minerchest now accepts items transported via Pipeworks pneumatic tubes.

Signed-off-by: Michal Cieslakiewicz <michal.cieslakiewicz@wp.pl>
2019-09-09 16:35:46 +02:00

598 lines
17 KiB
Lua

--[[
========================================================================
Miner's Chest
by Micu (c) 2019
Copyright (C) 2019 Michal Cieslakiewicz
This is source file for Miner's Chest - a high capacity storage chest
that automatically combines selected resources into respective blocks.
Chest is compatible with Techpack (Tubelib2 framework) and Pipeworks
(pneumatic tubes). It has capacity of 60 items. When used with Tubelib
it supports stack pulling (can be paired with HighPerf Pusher).
It automatically combines following items into blocks:
* steel ingot -> steel block
* copper ingot -> copper block
* bronze ingot -> bronze block
* tin ingot -> tin block
* gold ingot -> gold block
* silver ingot (moreores mod) -> silver block
* mithril ingot (moreores mod) -> mithril block
* brass ingot (basic_materials mod) -> brass block
* coal lump -> coal block
* mese crystal -> mese block
* diamond crystal -> diamond block
* wheat -> straw
* sand -> sandstone
* desert sand -> desert sandstone
* silver sand -> silver sandstone
* clay lump -> clay block
More allowed item combinations can be registered via API function.
Note: some mods can disable some of above crafting recipes or make
them non-reversible (Technic for example).
Chest supports only reversible combinations (example: metal blocks can
be converted back to ingots) of popular minerals and resources (hence
name Miner's Chest). Due to game internal design, chest requires some
spare slots to perform item reorganization as it may process only
things that are already in its inventory.
Features:
* automatic crafting of configured items into blocks
* automatic stack merging
* Tubelib I/O compatibility
* Pipeworks compatibility (accepts input from tubes)
* support for Tubelib stack pulling (can be paired with HighPerf Pusher)
* item prioritization for Tubelib pulling (stackable items go last)
* no defects (not a machine)
* support for standard SaferLua storage status (empty/loaded/full)
* storage status visual indicator and infotext
* infobar in inventory window
* no node timer
License: LGPLv2.1+
========================================================================
]]--
--[[
---------
Variables
---------
]]--
minerchest = {}
local INV_X = 12
local INV_Y = 5
local INV_SIZE = INV_X * INV_Y
local use_tubelib = minetest.global_exists("tubelib") and true or false
local use_pipeworks = minetest.global_exists("pipeworks") and true or false
--[[
----------------------
Public functions (API)
----------------------
]]--
local combdata = {}
-- Allow chest to combine items into respective blocks
-- source_item - source item name
-- block_item - destination block item name
-- Note: do not put any quantities here - function automatically
-- picks up recipe based on supplied parameters; it returns true
-- if completed successfully, false if error or already defined.
function minerchest.allow_item_combine(source_item, block_item)
if not source_item or not block_item then
return false
end
local source_stack = ItemStack(source_item)
local block_stack = ItemStack(block_item)
if not source_stack:is_known() or not block_stack:is_known() then
return false
end
local sn = source_stack:get_name()
local bn = block_stack:get_name()
if combdata[sn] then
return false
end
local fis = nil
local fib = nil
-- find recipe for block that uses source item only
local rt = minetest.get_all_craft_recipes(bn)
for _, r in ipairs(rt) do
if r.type == "normal" and r.method == "normal" then
local o = ItemStack(r.output)
if bn == o:get_name() then
local icnt = 0
local iok = true
for _, i in ipairs(r.items) do
local s = ItemStack(i)
if s:get_name() ~= sn then
iok = false
break
end
icnt = icnt + s:get_count()
end
if iok then
fis = sn .. " " .. icnt
fib = o:to_string()
break
end
end
end
end
if not fis or not fib then
minetest.log("warning", "minerchest: no combination recipe for (\"" ..
sn .. "\", \"" .. bn.. "\") - skipping.")
return false
end
-- verify if recipe is reversible
local isrev = false
rt = minetest.get_all_craft_recipes(sn)
for _, r in ipairs(rt) do
if r.type == "normal" and r.method == "normal" then
local o = ItemStack(r.output)
if sn == o:get_name() then
local iok = true
for _, i in ipairs(r.items) do
local s = ItemStack(i)
if s:get_name() ~= bn then
iok = false
break
end
end
if iok then
isrev = true
break
end
end
end
end
if not isrev then
minetest.log("warning", "minerchest: combination recipe (\"" ..
sn .. "\", \"" .. bn.. "\") is not reversible - skipping.")
return false
end
combdata[sn] = { items = fis, output = fib }
return true
end
--[[
--------
Formspec
--------
]]--
-- get node/item/tool description for tooltip
local function formspec_tooltip(name)
local def = minetest.registered_nodes[name] or
minetest.registered_craftitems[name] or
minetest.registered_items[name] or
minetest.registered_tools[name] or nil
return def and def.description or ""
end
-- display info bar between chest and player repositories
local function formspec_item_bar(width, y)
local cblen = 0
for i, _ in pairs(combdata) do
cblen = cblen + 1
end
local arrowup = use_tubelib and "tubelib_gui_arrow.png^[transformR90"
or "gui_furnace_arrow_fg.png"
local mx = math.max((width - cblen * 0.5 - 1) / 2, 0)
local itembar = "image[" .. tostring(mx) .. "," ..
tostring(y + 0.25) .. ";0.5,0.5;" .. arrowup .. "]"
local x = mx + 0.5
for i, c in pairs(combdata) do
local b = ItemStack(c.output)
b = b:get_name()
itembar = itembar ..
"item_image[" .. tostring(x) .. "," .. tostring(y) .. ";0.5,0.5;" .. b .. "]" ..
"tooltip[" .. tostring(x) .. "," .. tostring(y) .. ";0.5,0.5;" .. formspec_tooltip(b) .. "]" ..
"item_image[" .. tostring(x) .. "," .. tostring(y + 0.5) .. ";0.5,0.5;" .. i .. "]" ..
"tooltip[" .. tostring(x) .. "," .. tostring(y + 0.5) .. ";0.5,0.5;" .. formspec_tooltip(i) .. "]"
x = x + 0.5
if x >= width - mx - 0.5 then
break
end
end
local itembar = itembar .. "image[" .. tostring(x) .. "," ..
tostring(y + 0.25) .. ";0.5,0.5;" .. arrowup .. "]"
return itembar
end
-- formspec (with autoformatting)
local function formspec()
local sizex = math.max(INV_X, 8)
local invby = math.max(INV_Y, 2)
local plrx = tostring((sizex - 8) / 2)
return "size[" .. tostring(sizex) .. "," ..
tostring(invby + 5.75) .. "]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
"list[context;main;" .. tostring((sizex - INV_X) / 2).. "," ..
tostring((invby - INV_Y) / 2) .. ";" ..
tostring(INV_X) .. "," ..
tostring(INV_Y) .. ";]" ..
formspec_item_bar(sizex, invby + 0.25) ..
"list[current_player;main;" .. plrx .. "," ..
tostring(invby + 1.5) .. "4;8,1;]" ..
"list[current_player;main;" .. plrx .. "," ..
tostring(invby + 2.75) .. ";8,3;8]" ..
"listring[context;main]" ..
"listring[current_player;main]" ..
default.get_hotbar_bg(plrx, invby + 1.5)
end
--[[
-------
Helpers
-------
]]--
-- get_inv_state() function from Techpack by Joe7575, see tubelib/command.lua
-- (simple version, in case no tubelib mod is present)
local function get_inv_state(meta, list)
local inv = meta:get_inventory()
if inv:is_empty(list) then
return "empty"
else
local l = inv:get_list(list)
for _, i in ipairs(l) do
if i:is_empty() then
return "loaded"
end
end
end
return "full"
end
-- swap chest node at pos to reflect current fill state
local function update_chest_node(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number") or ""
local state = get_inv_state(meta, "main")
meta:set_string("infotext", "Miner Chest " .. number ..
" (" .. state .. ")")
local newname
if state == "full" then
newname = "minerchest:chest_full"
else
newname = "minerchest:chest"
end
if newname ~= node.name then
node.name = newname
minetest.swap_node(pos, node)
end
end
-- pile up all items to maximize free space
local function pile_up_items(inv, list)
local invsz = inv:get_size(list)
local itbl = {}
for i = 1, invsz do
local s = inv:get_stack(list, i)
if not s:is_empty() then
itbl[#itbl + 1] = s:to_string() -- otherwise you get reference!
s:clear()
inv:set_stack(list, i, s)
end
end
for i = 1, #itbl do
inv:add_item(list, itbl[i])
end
end
-- reorganize and combine items
local function combine_chest_items(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if inv:is_empty("main") then
return
end
-- pile up before
pile_up_items(inv, "main")
-- combine items when applicable
for i, c in pairs(combdata) do
local is = ItemStack(c.items)
local bs = ItemStack(c.output)
local iscnt = is:get_count()
while true do
local taken = inv:remove_item("main", is)
if taken:is_empty() then
break
elseif taken:get_count() < iscnt or
not inv:room_for_item("main", bs) then
inv:add_item("main", taken) -- put back
break
end
inv:add_item("main", bs)
end
end
-- pile up after
pile_up_items(inv, "main")
end
-- pipeworks tube support
local pipeworks_tube = {}
if use_pipeworks then
pipeworks_tube = {
input_inventory = "main",
connect_sides = {left = 1, right = 1, back = 1,
front = 1, bottom = 1, top = 1},
insert_object = function(pos, node, stack, direction, owner)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local ret = inv:add_item("main", stack)
combine_chest_items(pos)
update_chest_node(pos)
return ret
end,
can_insert = function(pos, node, stack, direction, owner)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:room_for_item("main", stack)
end,
}
end
-- tubelib takes items in a round-robin fashion - this function
-- modifies this method a little by skipping items that can be
-- combined into blocks - until only these remain
local function set_next_tubelib_item(meta, list)
local inv = meta:get_inventory()
if inv:is_empty(list) then
return
end
local invsz = inv:get_size(list)
local i = meta:get_int("tubelib_startpos") or 0
local c = 0
while c < invsz do
local ni = (i % invsz) + 1
local s = inv:get_stack(list, ni)
if not s:is_empty() and not combdata[s:get_name()] then
break
end
i = ni
c = c + 1
end
meta:set_int("tubelib_startpos", i)
end
--[[
---------
Callbacks
---------
]]--
-- do not allow to dig protected or non-empty chest
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:is_empty("main")
end
-- cleanup after digging
local function after_dig_node(pos, oldnode, oldmetadata, digger)
if use_tubelib then
tubelib.remove_node(pos)
end
if use_pipeworks then
pipeworks.after_dig(pos)
end
end
-- init after placement
local function after_place_node(pos, placer, itemstack, pointed_thing)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("main", INV_SIZE)
meta:set_string("owner", placer:get_player_name())
if use_tubelib then
local number = tubelib.add_node(pos, "minerchest:chest")
meta:set_string("number", number)
end
if use_pipeworks then
pipeworks.after_place(pos, placer)
end
meta:set_string("formspec", formspec())
update_chest_node(pos)
end
-- common function for on_metadata_inventory_* callbacks
local function on_metadata_inventory_change(pos)
combine_chest_items(pos)
update_chest_node(pos)
end
--[[
-----------------
Node registration
-----------------
]]--
minetest.register_node("minerchest:chest", {
description = "Miner Chest",
tiles = {
-- up, down, right, left, back, front
"minerchest_plate.png",
"minerchest_plate.png",
"minerchest_side.png",
"minerchest_side.png",
"minerchest_side.png",
"minerchest_front.png",
},
drawtype = "nodebox",
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = { choppy = 2, cracky = 2, crumbly = 2,
tubedevice = 1, tubedevice_receiver = 1 },
is_ground_content = false,
sounds = default.node_sound_metal_defaults(),
after_place_node = after_place_node,
can_dig = can_dig,
after_dig_node = after_dig_node,
on_rotate = screwdriver.disallow,
on_metadata_inventory_move = on_metadata_inventory_change,
on_metadata_inventory_put = on_metadata_inventory_change,
on_metadata_inventory_take = on_metadata_inventory_change,
tube = pipeworks_tube,
})
minetest.register_node("minerchest:chest_full", {
description = "Miner Chest",
tiles = {
-- up, down, right, left, back, front
"minerchest_plate.png",
"minerchest_plate.png",
"minerchest_side.png",
"minerchest_side.png",
"minerchest_side.png",
"minerchest_front_full.png",
},
drawtype = "nodebox",
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = { choppy = 2, cracky = 2, crumbly = 2,
tubedevice = 1, tubedevice_receiver = 1,
not_in_creative_inventory = 1 },
is_ground_content = false,
sounds = default.node_sound_metal_defaults(),
drop = "minerchest:chest",
after_place_node = after_place_node,
can_dig = can_dig,
after_dig_node = after_dig_node,
on_rotate = screwdriver.disallow,
on_metadata_inventory_move = on_metadata_inventory_change,
on_metadata_inventory_put = on_metadata_inventory_change,
on_metadata_inventory_take = on_metadata_inventory_change,
tube = pipeworks_tube,
})
if use_tubelib then
tubelib.register_node("minerchest:chest", { "minerchest:chest_full" }, {
on_push_item = function(pos, side, item)
local meta = minetest.get_meta(pos)
local ret = tubelib.put_item(meta, "main", item)
combine_chest_items(pos)
update_chest_node(pos)
return ret
end,
on_pull_item = function(pos, side)
local meta = minetest.get_meta(pos)
set_next_tubelib_item(meta, "main")
local ret = tubelib.get_item(meta, "main")
combine_chest_items(pos)
update_chest_node(pos)
return ret
end,
on_pull_stack = function(pos, side)
local meta = minetest.get_meta(pos)
set_next_tubelib_item(meta, "main")
local ret = tubelib.get_stack(meta, "main")
combine_chest_items(pos)
update_chest_node(pos)
return ret
end,
on_unpull_item = function(pos, side, item)
local meta = minetest.get_meta(pos)
local ret = tubelib.put_item(meta, "main", item)
combine_chest_items(pos)
update_chest_node(pos)
return ret
end,
on_recv_message = function(pos, topic, payload)
if topic == "state" then
local meta = minetest.get_meta(pos)
return tubelib.get_inv_state(meta, "main")
else
return "unsupported"
end
end,
})
end
--[[
--------
Crafting
--------
]]--
minetest.register_craft({
output = "minerchest:chest",
recipe = {
{ "default:steelblock", use_tubelib and "tubelib:tubeS"
or "group:wood", "default:goldblock" },
{ "group:wood", "", "group:wood" },
{ "default:copperblock", "group:wood", "default:tinblock" },
},
})
--[[
------------
Combinations
------------
]]--
minerchest.allow_item_combine("default:steel_ingot", "default:steelblock")
minerchest.allow_item_combine("default:copper_ingot", "default:copperblock")
minerchest.allow_item_combine("default:bronze_ingot", "default:bronzeblock")
minerchest.allow_item_combine("default:tin_ingot", "default:tinblock")
minerchest.allow_item_combine("default:gold_ingot", "default:goldblock")
minerchest.allow_item_combine("default:coal_lump", "default:coalblock")
minerchest.allow_item_combine("default:diamond", "default:diamondblock")
minerchest.allow_item_combine("default:mese_crystal", "default:mese")
minerchest.allow_item_combine("default:sand", "default:sandstone")
minerchest.allow_item_combine("default:desert_sand", "default:desert_sandstone")
minerchest.allow_item_combine("default:silver_sand", "default:silver_sandstone")
minerchest.allow_item_combine("default:clay_lump", "default:clay")
minerchest.allow_item_combine("farming:wheat", "farming:straw")
if minetest.global_exists("moreores") then
minerchest.allow_item_combine("moreores:silver_ingot", "moreores:silver_block")
minerchest.allow_item_combine("moreores:mithril_ingot", "moreores:mithril_block")
end
if minetest.global_exists("basic_materials") then
minerchest.allow_item_combine("basic_materials:brass_ingot", "basic_materials:brass_block")
end
if minetest.global_exists("technic") then
minerchest.allow_item_combine("technic:cast_iron_ingot", "technic:cast_iron_block")
minerchest.allow_item_combine("technic:carbon_steel_ingot", "technic:carbon_steel_block")
minerchest.allow_item_combine("technic:stainless_steel_ingot", "technic:stainless_steel_block")
minerchest.allow_item_combine("technic:lead_ingot", "technic:lead_block")
minerchest.allow_item_combine("technic:zinc_ingot", "technic:zinc_block")
minerchest.allow_item_combine("technic:chromium_ingot", "technic:chromium_block")
end