360 lines
11 KiB
Lua
360 lines
11 KiB
Lua
--[[
|
|
|
|
Tube Library
|
|
============
|
|
|
|
Copyright (C) 2017 Joachim Stolberg
|
|
|
|
LGPLv2.1+
|
|
See LICENSE.txt for more information
|
|
|
|
command.lua:
|
|
|
|
User interface functions for message communication
|
|
and StackItem push/pulling.
|
|
|
|
]]--
|
|
|
|
-------------------------------------------------------------------
|
|
-- Data base storage
|
|
-------------------------------------------------------------------
|
|
local storage = minetest.get_mod_storage()
|
|
local Key2Number = minetest.deserialize(storage:get_string("Key2Number")) or {}
|
|
local NextNumber = minetest.deserialize(storage:get_string("NextNumber")) or 1
|
|
local Number2Pos = minetest.deserialize(storage:get_string("Number2Pos")) or {}
|
|
|
|
local function update_mod_storage()
|
|
storage:set_string("Key2Number", minetest.serialize(Key2Number))
|
|
storage:set_string("NextNumber", minetest.serialize(NextNumber))
|
|
storage:set_string("Number2Pos", minetest.serialize(Number2Pos))
|
|
end
|
|
|
|
minetest.register_on_shutdown(function()
|
|
update_mod_storage()
|
|
end)
|
|
|
|
-- store data each hour
|
|
minetest.after(60*60, update_mod_storage)
|
|
|
|
|
|
local Name2Name = {} -- translation table
|
|
|
|
-------------------------------------------------------------------
|
|
-- Local helper functions
|
|
-------------------------------------------------------------------
|
|
-- Localize functions to avoid table lookups (better performance).
|
|
local string_find = string.find
|
|
local string_split = string.split
|
|
local minetest_is_protected = minetest.is_protected
|
|
local tubelib_NodeDef = tubelib.NodeDef
|
|
|
|
|
|
local function get_facedir(placer)
|
|
if placer then
|
|
return minetest.dir_to_facedir(placer:get_look_dir(), false)
|
|
end
|
|
return 0
|
|
end
|
|
|
|
local function legacy_node(node)
|
|
if node and tubelib.legacyNodes[node.name] then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Determine position related node number for addressing purposes
|
|
local function get_number(pos)
|
|
local key = tubelib.get_key_str(pos)
|
|
if not Key2Number[key] then
|
|
Key2Number[key] = NextNumber
|
|
NextNumber = NextNumber + 1
|
|
end
|
|
return string.format("%.04u", Key2Number[key])
|
|
end
|
|
|
|
local FuelTbl = {
|
|
["default:tree"] = true,
|
|
["default:wood"] = true,
|
|
["default:leaves"] = true,
|
|
["default:jungletree"] = true,
|
|
["default:junglewood"] = true,
|
|
["default:jungleleaves"] = true,
|
|
["default:pine_tree"] = true,
|
|
["default:pine_wood"] = true,
|
|
["default:pine_needles"] = true,
|
|
["default:acacia_tree"] = true,
|
|
["default:acacia_wood"] = true,
|
|
["default:acacia_leaves"] = true,
|
|
["default:aspen_tree"] = true,
|
|
["default:aspen_wood"] = true,
|
|
["default:aspen_leaves"] = true,
|
|
["default:coalblock"] = true,
|
|
}
|
|
|
|
-------------------------------------------------------------------
|
|
-- API helper functions
|
|
-------------------------------------------------------------------
|
|
|
|
-- Determine neighbor position based on current pos, node facedir
|
|
-- and the side F(orward), R(ight), B(ackward), L(eft), D(own), U(p).
|
|
-- The function considers tubes in addition.
|
|
function tubelib.get_pos(pos, facedir, side)
|
|
local offs = {F=0, R=1, B=2, L=3, D=4, U=5}
|
|
local dst_pos = table.copy(pos)
|
|
facedir = (facedir + offs[side]) % 4
|
|
local dir = tubelib.facedir_to_dir(facedir)
|
|
dst_pos = vector.add(dst_pos, dir)
|
|
local node = minetest.get_node(dst_pos)
|
|
if node and string_find(node.name, "tubelib:tube") then
|
|
local _pos = minetest.string_to_pos(minetest.get_meta(dst_pos):get_string("dest_pos"))
|
|
-- two possible reasons, why _pos == pos:
|
|
-- 1) wrong side of a single tube node
|
|
-- 2) node connected with itself. In this case "dest_pos2" is not available
|
|
if vector.equals(_pos, pos) then --
|
|
_pos = minetest.string_to_pos(minetest.get_meta(dst_pos):get_string("dest_pos2"))
|
|
end
|
|
if _pos == nil then
|
|
dst_pos = pos
|
|
else
|
|
dst_pos = _pos
|
|
end
|
|
node = minetest.get_node(dst_pos)
|
|
end
|
|
-- translate the current node name into the base name, used at registration
|
|
if Name2Name[node.name] then
|
|
node.name = Name2Name[node.name]
|
|
end
|
|
return node, dst_pos
|
|
end
|
|
|
|
-- Generate a key string based on the given pos table,
|
|
-- Used internaly as table key,
|
|
function tubelib.get_key_str(pos)
|
|
pos = minetest.pos_to_string(pos)
|
|
return '"'..string.sub(pos, 2, -2)..'"'
|
|
end
|
|
|
|
-- 6D variant of the facedir to dir conversion
|
|
function tubelib.facedir_to_dir(facedir)
|
|
local table = {[0] =
|
|
{x=0, y=0, z=1},
|
|
{x=1, y=0, z=0},
|
|
{x=0, y=0, z=-1},
|
|
{x=-1, y=0, z=0},
|
|
{x=0, y=-1, z=0},
|
|
{x=0, y=1, z=0},
|
|
}
|
|
return table[facedir]
|
|
end
|
|
|
|
-- Check the given list of numbers.
|
|
-- Returns true if number(s) is/are valid.
|
|
function tubelib.check_numbers(numbers)
|
|
if numbers then
|
|
for _,num in ipairs(string_split(numbers, " ")) do
|
|
if Number2Pos[num] == nil then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Determines and returns the node position number based on the given pos.
|
|
function tubelib.get_node_number(pos, name)
|
|
local number = get_number(pos)
|
|
Number2Pos[number] = {
|
|
pos = pos,
|
|
name = name,
|
|
}
|
|
return number
|
|
end
|
|
|
|
-- Remove node from the position list
|
|
function tubelib.remove_node(pos)
|
|
local number = get_number(pos)
|
|
if Number2Pos[number] then
|
|
Number2Pos[number] = {
|
|
pos = pos,
|
|
name = nil,
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
-- Function returns { pos, name } for the node on the given position number.
|
|
function tubelib.get_node_info(dest_num)
|
|
if Number2Pos[dest_num] then
|
|
return Number2Pos[dest_num]
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------
|
|
-- Node register function
|
|
-------------------------------------------------------------------
|
|
|
|
-- Register node for tubelib communication
|
|
-- Call this function only at load time!
|
|
-- Param 'add_names' tbd TODO
|
|
-- Param 'node_definition' is a table according to:
|
|
--
|
|
-- {
|
|
-- on_pull_item = func(pos),
|
|
-- -- The function shall return an item stack with one element
|
|
-- -- like ItemStack("default:cobble") or nil.
|
|
-- on_push_item = func(pos, item),
|
|
-- on_recv_message = func(pos, topic, payload),
|
|
-- }
|
|
--
|
|
function tubelib.register_node(name, add_names, node_definition)
|
|
tubelib.knownNodes[name] = true
|
|
tubelib_NodeDef[name] = node_definition
|
|
Name2Name[name] = name
|
|
for _,n in ipairs(add_names) do
|
|
tubelib.knownNodes[n] = true
|
|
Name2Name[n] = name
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------
|
|
-- Send message function
|
|
-------------------------------------------------------------------
|
|
|
|
-- Send a message to all blocks referenced by 'numbers', a list of
|
|
-- one or more destination addressses separated by blanks.
|
|
-- The message is based on the topic string (e.g. "start") and
|
|
-- topic related payload.
|
|
-- The placer and clicker names are needed to check the protection rights.
|
|
-- If everybody should be able to send a message, use nil for clicker_name.
|
|
function tubelib.send_message(numbers, placer_name, clicker_name, topic, payload)
|
|
for _,num in ipairs(string_split(numbers, " ")) do
|
|
if Number2Pos[num] and Number2Pos[num].name then
|
|
local data = Number2Pos[num]
|
|
if placer_name and not minetest_is_protected(data.pos, placer_name) then
|
|
if clicker_name == nil or not minetest_is_protected(data.pos, clicker_name) then
|
|
if data and data.name and tubelib_NodeDef[data.name].on_recv_message then
|
|
tubelib_NodeDef[data.name].on_recv_message(data.pos, topic, payload)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------
|
|
-- Client side Push/Pull item functions
|
|
-------------------------------------------------------------------
|
|
|
|
-- Param 'pos', 'facedir', and 'side' are used to determine the neighbor position.
|
|
-- Param 'pos' is the own position
|
|
-- Param 'facedir' is the own node facedir
|
|
-- Param 'side' is one of F(orward), R(ight), B(ackward), L(eft), D(own), U(p)
|
|
-- relative to the placers view to the node.
|
|
-- The function returns an item stack with one element like ItemStack("default:cobble")
|
|
-- or nil.
|
|
function tubelib.pull_items(pos, facedir, side)
|
|
local node, src_pos = tubelib.get_pos(pos, facedir, side)
|
|
if tubelib_NodeDef[node.name] and tubelib_NodeDef[node.name].on_pull_item then
|
|
return tubelib_NodeDef[node.name].on_pull_item(src_pos)
|
|
elseif legacy_node(node) then
|
|
local meta = minetest.get_meta(src_pos)
|
|
local inv = meta:get_inventory()
|
|
if node.name == "default:furnace" or node.name == "default:furnace_active" then
|
|
return tubelib.get_item(inv, "dst")
|
|
else
|
|
return tubelib.get_item(inv, "main")
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Param 'pos', 'facedir', and 'side' are used to determine the neighbor position.
|
|
-- Param 'pos' is the own position
|
|
-- Param 'facedir' is the own node facedir
|
|
-- Param 'side' is one of F(orward), R(ight), B(ackward), L(eft), D(own), U(p)
|
|
-- relative to the placers view to the node.
|
|
-- Param 'item' is an item stack with one element like ItemStack("default:cobble")
|
|
function tubelib.push_items(pos, facedir, side, items)
|
|
local node, dst_pos = tubelib.get_pos(pos, facedir, side)
|
|
if tubelib_NodeDef[node.name] and tubelib_NodeDef[node.name].on_push_item then
|
|
return tubelib_NodeDef[node.name].on_push_item(dst_pos, items)
|
|
elseif legacy_node(node) then
|
|
local meta = minetest.get_meta(dst_pos)
|
|
local inv = meta:get_inventory()
|
|
if node.name == "default:furnace" or node.name == "default:furnace_active" then
|
|
minetest.get_node_timer(dst_pos):start(1.0)
|
|
if FuelTbl[items:get_name()] == true then
|
|
return tubelib.put_item(inv, "fuel", items)
|
|
else
|
|
return tubelib.put_item(inv, "src", items)
|
|
end
|
|
else
|
|
return tubelib.put_item(inv, "main", items)
|
|
end
|
|
elseif node and node.name == "air" then
|
|
minetest.add_item(dst_pos, items)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------
|
|
-- Server side helper functions
|
|
-------------------------------------------------------------------
|
|
|
|
-- Get one item from the given ItemList. The position within the list
|
|
-- is randomly selected so that different items stack will be considered.
|
|
-- Returns nil if ItemList is empty.
|
|
function tubelib.get_item(inv, listname)
|
|
if inv:is_empty(listname) then
|
|
--print("nil")
|
|
return nil
|
|
end
|
|
local stack, slot
|
|
local size = inv:get_size(listname)
|
|
local offs = math.random(size)
|
|
--print("tubelib.get_item", offs)
|
|
for idx = 1, size do
|
|
local slot = ((idx + offs) % size) + 1
|
|
local items = inv:get_stack(listname, slot)
|
|
if items:get_count() > 0 then
|
|
local taken = items:take_item(1)
|
|
inv:set_stack(listname, slot, items)
|
|
return taken
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Get one item from the given ItemList, specified by stack number (1..n).
|
|
-- Returns nil if ItemList is empty.
|
|
function tubelib.get_this_item(inv, listname, number)
|
|
if inv:is_empty(listname) then
|
|
return nil
|
|
end
|
|
|
|
local items = inv:get_stack(listname, number)
|
|
if items:get_count() > 0 then
|
|
local taken = items:take_item(1)
|
|
inv:set_stack(listname, number, items)
|
|
return taken
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
-- Put the given item into the given ItemList.
|
|
-- Function returns false if ItemList is full.
|
|
function tubelib.put_item(inv, listname, item)
|
|
if inv:room_for_item(listname, item) then
|
|
inv:add_item(listname, item)
|
|
return true
|
|
end
|
|
return false
|
|
end
|