Files
lwcomponents/hopper.lua
2022-03-06 16:37:25 +10:00

460 lines
11 KiB
Lua

local utils, mod_storage = ...
local S = utils.S
if utils.hopper_supported then
local hopper_interval = 1.0
local hopper_list = minetest.deserialize (mod_storage:get_string ("hopper_list"))
if type (hopper_list) ~= "table" then
hopper_list = {}
end
local function add_hopper_to_list (pos)
hopper_list[minetest.pos_to_string (pos, 0)] = true
mod_storage:set_string ("hopper_list", minetest.serialize (hopper_list))
end
local function remove_hopper_from_list (pos)
hopper_list[minetest.pos_to_string (pos, 0)] = nil
mod_storage:set_string ("hopper_list", minetest.serialize (hopper_list))
end
local input_dir =
{
[0] = { x = 0, y = 1, z = 0 },
{ x = 0, y = 0, z = 1 },
{ x = 0, y = 0, z = -1 },
{ x = 1, y = 0, z = 0 },
{ x = -1, y = 0, z = 0 },
{ x = 0, y = -1, z = 0 }
}
local function get_input_dir (node)
return input_dir[math.floor (node.param2 / 4)]
end
local function get_output_dir (node)
if node then
if node.name == "lwcomponents:hopper" then
return vector.multiply (get_input_dir (node), -1)
elseif node.name == "lwcomponents:hopper_horz" then
return minetest.facedir_to_dir (node.param2)
end
end
return nil
end
local function get_drop (pos)
local objs = minetest.get_objects_inside_radius (pos, 1)
for _, obj in pairs (objs) do
local obj_pos = (obj.get_pos and obj:get_pos ())
if obj_pos and utils.is_drop (obj) then
obj_pos = vector.round (obj_pos)
if vector.equals (pos, obj_pos) then
local stack = ItemStack (obj:get_luaentity ().itemstring)
if stack and not stack:is_empty () then
stack:set_count (1)
return stack, obj
end
end
end
end
end
local function take_drop (obj)
if utils.is_drop (obj) then
local stack = ItemStack (obj:get_luaentity ().itemstring)
if stack and not stack:is_empty () then
stack:set_count (stack:get_count () - 1)
if stack:is_empty () then
obj:get_luaentity().itemstring = ""
obj:remove()
else
obj:get_luaentity().itemstring = stack:to_string ()
end
end
end
end
local function next_item_to_take (src_pos, src_node, src_inv_name)
if not src_inv_name or not minetest.registered_nodes[src_node.name] then
return
end
local src_meta = minetest.get_meta (src_pos)
local src_inv = (src_meta and src_meta:get_inventory ()) or nil
if src_inv then
local slots = src_inv:get_size (src_inv_name)
for slot = 1, slots, 1 do
local stack = src_inv:get_stack (src_inv_name, slot)
if stack and not stack:is_empty () then
stack:set_count (1)
return stack, slot
end
end
end
end
local function take_item (src_pos, src_inv_name, slot)
local src_meta = minetest.get_meta (src_pos)
local src_inv = (src_meta and src_meta:get_inventory ()) or nil
if src_inv then
local stack = src_inv:get_stack (src_inv_name, slot)
if stack and not stack:is_empty () then
stack:set_count (stack:get_count () - 1)
src_inv:set_stack (src_inv_name, slot, stack)
end
end
end
local function call_allow_metadata_inventory_put (def, pos, listname, index, stack, placer)
if def.allow_metadata_inventory_put and placer then
local result, count = pcall (def.allow_metadata_inventory_put,
pos, listname, index, stack, placer)
if result then
return count
end
end
return utils.settings.default_stack_max
end
local function call_on_metadata_inventory_put (def, pos, listname, index, stack, placer)
if def.on_metadata_inventory_put and placer then
pcall (def.on_metadata_inventory_put, pos, listname, index, stack, placer)
end
end
local function place_item (dest_pos, dest_node, dest_inv_name, stack, placer)
local dest_def = minetest.registered_nodes[dest_node.name]
if not dest_inv_name or not dest_def then
return
end
local dest_meta = minetest.get_meta (dest_pos)
local dest_inv = (dest_meta and dest_meta:get_inventory ()) or nil
if dest_inv then
local slots = dest_inv:get_size (dest_inv_name)
-- find existing stack
for slot = 1, slots, 1 do
local inv_stack = dest_inv:get_stack (dest_inv_name, slot)
if inv_stack and not inv_stack:is_empty () and
utils.is_same_item (inv_stack, stack) and
inv_stack:get_count () < inv_stack:get_stack_max () and
(call_allow_metadata_inventory_put (dest_def, dest_pos, dest_inv_name, slot, stack, placer) > 0) then
inv_stack:set_count (inv_stack:get_count () + 1)
dest_inv:set_stack (dest_inv_name, slot, inv_stack)
call_on_metadata_inventory_put (dest_def, dest_pos, dest_inv_name, slot, stack, placer)
return true
end
end
-- find empty slot
for slot = 1, slots, 1 do
local inv_stack = dest_inv:get_stack (dest_inv_name, slot)
if not inv_stack or inv_stack:is_empty () and
(call_allow_metadata_inventory_put (dest_def, dest_pos, dest_inv_name, slot, stack, placer) > 0) then
dest_inv:set_stack (dest_inv_name, slot, stack)
call_on_metadata_inventory_put (dest_def, dest_pos, dest_inv_name, slot, stack, placer)
return true
end
end
end
return false
end
local function get_player_object (pos)
local meta = minetest.get_meta (pos)
local placer_name = "<unknown>"
if meta then
placer_name = meta:get_string ("placer_name")
local placer = minetest.get_player_by_name (placer_name)
if placer then
return placer
end
if placer_name == "" then
placer_name = "<unknown>"
end
end
return utils.get_dummy_player (true, placer_name)
end
local function run_hopper_action (pos)
local node = utils.get_far_node (pos)
local dest_dir = get_output_dir (node)
if dest_dir then
local dest_pos = vector.add (pos, dest_dir)
local dest_node = utils.get_far_node (dest_pos)
if dest_node then
local registered_dest_invs = hopper.get_registered_inventories_for (dest_node.name)
if registered_dest_invs then
local placer = get_player_object (pos)
local src_pos = vector.add (pos, get_input_dir (node))
local drop = nil
local stack = nil
local slot = nil
local src_inv_name = nil
stack, drop = get_drop (src_pos)
if not stack then
local src_node = utils.get_far_node (src_pos)
if src_node then
local registered_src_invs = hopper.get_registered_inventories_for (src_node.name)
if registered_src_invs then
src_inv_name = registered_src_invs["top"]
stack, slot = next_item_to_take (src_pos, src_node, src_inv_name)
end
end
end
if stack then
local dest_side = (node.name == "lwcomponents:hopper" and "bottom") or "side"
local dest_inv_name = registered_dest_invs[dest_side]
if place_item (dest_pos, dest_node, dest_inv_name, stack, placer) then
if drop then
take_drop (drop)
else
take_item (src_pos, src_inv_name, slot)
end
end
end
end
end
end
end
local function on_construct (pos)
add_hopper_to_list (pos)
end
local function on_destruct (pos)
remove_hopper_from_list (pos)
end
local function after_place_node (pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta (pos)
meta:set_string ("placer_name", (placer and placer:get_player_name ()) or "")
-- If return true no item is taken from itemstack
return false
end
local function on_place (itemstack, placer, pointed_thing)
if pointed_thing and pointed_thing.type == "node" then
local stack = ItemStack (itemstack)
local dir = vector.direction (pointed_thing.above, pointed_thing.under)
if dir.y == 0 then
minetest.item_place (ItemStack ("lwcomponents:hopper_horz"), placer, pointed_thing)
if not utils.is_creative (placer) then
stack:set_count (stack:get_count () - 1)
end
return stack
end
end
return minetest.item_place (itemstack, placer, pointed_thing)
end
minetest.register_node ("lwcomponents:hopper", {
description = S("Conduit Hopper"),
tiles = { "lwcomponents_hopper_top.png", "lwcomponents_hopper_vert_spout.png",
"lwcomponents_hopper_side.png", "lwcomponents_hopper_side.png",
"lwcomponents_hopper_side.png", "lwcomponents_hopper_side.png" },
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.3125, -0.5, -0.3125, 0.3125, -0.25, 0.3125}, -- spout_vert
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, -0.3125, 0.5, 0.5}, -- funnel_1
{-0.5, 0, 0.3125, 0.5, 0.5, 0.5}, -- funnel_2
{-0.5, 0, -0.5, 0.5, 0.5, -0.3125}, -- funnel_3
{0.3125, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel_4
}
},
selection_box = {
type = "fixed",
fixed = {
{-0.3125, -0.5, -0.3125, 0.3125, -0.25, 0.3125}, -- spout_vert
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel
}
},
collision_box = {
type = "fixed",
fixed = {
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel
{-0.3125, -0.3125, 0.5, 0.3125, 0.3125, 0.3125}, -- spout_side
}
},
paramtype = "light",
param1 = 0,
paramtype2 = "facedir",
param2 = 0,
drop = "lwcomponents:hopper",
groups = { cracky = 3 },
sounds = default.node_sound_stone_defaults (),
on_construct = on_construct,
on_destruct = on_destruct,
after_place_node = after_place_node,
on_place = on_place,
})
minetest.register_node ("lwcomponents:hopper_horz", {
description = S("Conduit Hopper"),
tiles = { "lwcomponents_hopper_top.png", "lwcomponents_hopper_bottom.png",
"lwcomponents_hopper_side.png", "lwcomponents_hopper_side.png",
"lwcomponents_hopper_side_spout.png", "lwcomponents_hopper_side.png" },
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, -0.3125, 0.5, 0.5}, -- funnel_1
{-0.5, 0, 0.3125, 0.5, 0.5, 0.5}, -- funnel_2
{-0.5, 0, -0.5, 0.5, 0.5, -0.3125}, -- funnel_3
{0.3125, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel_4
{-0.3125, -0.3125, 0.5, 0.3125, 0.3125, 0.3125}, -- spout_side
}
},
selection_box = {
type = "fixed",
fixed = {
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel
{-0.3125, -0.3125, 0.5, 0.3125, 0.3125, 0.3125}, -- spout_side
}
},
collision_box = {
type = "fixed",
fixed = {
{-0.375, -0.375, -0.375, 0.375, 0.3125, 0.375}, -- body
{-0.5, 0, -0.5, 0.5, 0.5, 0.5}, -- funnel
{-0.3125, -0.3125, 0.5, 0.3125, 0.3125, 0.3125}, -- spout_side
}
},
paramtype = "light",
param1 = 0,
paramtype2 = "facedir",
param2 = 0,
drop = "lwcomponents:hopper",
groups = { cracky = 3, not_in_creative_inventory = 1 },
sounds = default.node_sound_stone_defaults (),
on_construct = on_construct,
on_destruct = on_destruct,
after_place_node = after_place_node,
})
local function run_hoppers ()
for spos, _ in pairs (hopper_list) do
run_hopper_action (minetest.string_to_pos (spos))
end
minetest.after (hopper_interval, run_hoppers)
end
minetest.register_on_mods_loaded (function ()
minetest.after (3.0, run_hoppers)
end)
end -- utils.hopper_supported