diff --git a/.luacheckrc b/.luacheckrc index 93f08bc..eb3ba91 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -14,6 +14,7 @@ read_globals = { -- Minetest "vector", "ItemStack", "dump", "VoxelArea", + "VoxelManip", -- deps "minetest", @@ -24,6 +25,7 @@ read_globals = { "pipeworks", "screwdriver", "digilines", + "digiline", "mesecon", "techage" } diff --git a/README.md b/README.md index 0a50cc0..f1d2281 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,26 @@ Do you have too many cobblestones for one drawer? No problem, just add some drawer upgrades to your drawer! They are available in different sizes and are crafted by steel, gold, obsidian, diamonds or mithril. +## Digilines +The drawer controller is digilines-compatible. To request an item from the +surrounding drawers, send an itemstring to its channel: +* `"default:dirt 15"` +* `"default:cobble"` + +The items will be sent out the back of the drawer controller. + +To request the contents of a drawer network, send a table with the following +format: + +1. `command` (string) - `"get"` +2. `offset` (integer) - Used to paginate the results if the amount of drawers in + the network exceeds `drawers.CONTROLLER_MAX_COUNT`. Defaults to 1. +3. `max_count` (integer) - Must be between 1 and `CONTROLLER_MAX_COUNT`. + Defaults to `CONTROLLER_MAX_COUNT`. Maximum amount of drawers to return. + +A table will be sent back on the same channel containing each drawer's position +and the contents of each of their slots. + ## Notes This mod requires Minetest 5.0 or later. The `default` mod from MTG or the MineClone 2 mods are only optional dependencies for crafting recipes. diff --git a/init.lua b/init.lua index 020a40e..b632784 100755 --- a/init.lua +++ b/init.lua @@ -78,7 +78,6 @@ end dofile(MP .. "/lua/helpers.lua") dofile(MP .. "/lua/visual.lua") dofile(MP .. "/lua/api.lua") -dofile(MP .. "/lua/controller.lua") -- @@ -373,6 +372,12 @@ core.register_craft({ } }) +-- +-- Register drawer controller +-- + +dofile(MP .. "/lua/controller.lua") + -- -- Register drawer upgrade template -- diff --git a/lua/controller.lua b/lua/controller.lua index 2fda667..9441422 100644 --- a/lua/controller.lua +++ b/lua/controller.lua @@ -46,6 +46,30 @@ local pipeworks_loaded = core.get_modpath("pipeworks") and pipeworks local digilines_loaded = core.get_modpath("digilines") and digilines local techage_loaded = core.get_modpath("techage") and techage +local max_matches = tonumber(core.settings:get("drawers.controller_max_matches")) or 50 + +-- Cache content IDS of all registered drawer items +local controller_content_id +local trim_content_id = core.get_content_id("drawers:trim") +local drawer_content_ids = {} +for name, _ in pairs(core.registered_items) do + if core.get_item_group(name, "drawer") > 0 or core.get_item_group(name, "drawer_connector") > 0 then + drawer_content_ids[core.get_content_id(name)] = true + end +end + +-- Cache position offsets to find connected drawers +local offsets = {} +for dx = -1, 1 do + for dy = -1, 1 do + for dz = -1, 1 do + if dx ~= 0 or dy ~= 0 or dz ~= 0 then + table.insert(offsets, vector.new(dx, dy, dz)) + end + end + end +end + local function controller_formspec(pos) local formspec = "size[9,8.5]".. @@ -171,28 +195,42 @@ local function add_drawer_to_inventory(controllerInventory, pos) end end -local function find_connected_drawers(controller_pos, pos, foundPositions) - foundPositions = foundPositions or {} - pos = pos or controller_pos - - local newPositions = core.find_nodes_in_area( - {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1}, - {x = pos.x + 1, y = pos.y + 1, z = pos.z + 1}, - {"group:drawer", "group:drawer_connector"} - ) - - for _,p in ipairs(newPositions) do - -- check that this node hasn't been scanned yet - if not compare_pos(pos, p) and not contains_pos(foundPositions, p) - and pos_in_range(controller_pos, pos) then - -- add new position - table.insert(foundPositions, p) - -- search for other drawers from the new pos - find_connected_drawers(controller_pos, p, foundPositions) +local function find_connected_drawers(controller_pos) + local minp = vector.subtract(controller_pos, drawers.CONTROLLER_RANGE) + local maxp = vector.add(controller_pos, drawers.CONTROLLER_RANGE) + + local vm = VoxelManip() + local emin, emax = vm:read_from_map(minp, maxp) + local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax}) + local data = vm:get_data() + + local found = {} + local visited = {} + + local function dfs(pos) + local index = area:indexp(pos) + if visited[index] then return end + visited[index] = true + + local content_id = data[index] + if drawer_content_ids[content_id] then + if content_id ~= trim_content_id then + table.insert(found, pos) + end + elseif content_id ~= controller_content_id then + return + end + + for _, offset in ipairs(offsets) do + local neighbor = vector.add(pos, offset) + if pos_in_range(controller_pos, neighbor) then + dfs(neighbor) + end end end - return foundPositions + dfs(controller_pos) + return found end local function index_drawers(pos) @@ -297,6 +335,58 @@ local function controller_insert_to_drawers(pos, stack) return stack end +--[[ + Returns an array of drawers in the drawer network with their positions and slot + data. Slot data includes stored item, item count, and max count. + + Results can be paginated by specifying an `offset` and a `max_count`. +]] +local function controller_get_network_info(pos, offset, max_count) + local found_drawers = {} + local connected_drawers = find_connected_drawers(pos) + + -- Sort drawers by their positions to keep order for pagination + table.sort(connected_drawers, function(a, b) + return core.hash_node_position(a) < core.hash_node_position(b) + end) + + -- Offset must be an integer and >= 1 + offset = math.max(1, math.floor(tonumber(offset) or 1)) + -- Max count must be an integer, >= 1, and <= max_matches + max_count = math.floor(tonumber(max_count) or max_matches) + max_count = math.min(math.max(1, max_count), max_matches) + + for i = offset, offset + max_count - 1 do + local position = connected_drawers[i] + if not position then break end + + local node = core.get_node(position) + local drawer_meta = core.get_meta(position) + local node_def = core.registered_nodes[node.name] + local drawer_type = node_def.groups.drawer + + -- Record information of each slot + local slots = {} + for slot = 1, drawer_type do + -- 1x1 drawers don't have numbers in the meta fields + local slot_id = (drawer_type == 1 and "") or slot + local slot_name = drawer_meta:get_string("name" .. slot_id) + local slot_count = drawer_meta:get_int("count" .. slot_id) + local slot_max = drawer_meta:get_int("max_count" .. slot_id) + + table.insert(slots, { + name = slot_name, + count = slot_count, + max = slot_max + }) + end + + table.insert(found_drawers, {position=position, slots=slots}) + end + + return found_drawers +end + local function controller_can_dig(pos, player) local meta = core.get_meta(pos); local inv = meta:get_inventory() @@ -374,12 +464,21 @@ end local function controller_on_digiline_receive(pos, _, channel, msg) local meta = core.get_meta(pos) - if channel ~= meta:get_string("digilineChannel") then + if not msg or channel ~= meta:get_string("digilineChannel") then return end - if msg and type(msg) ~= "string" and type(msg) ~= "table" then - return -- Protect against ItemStack(...) errors + if type(msg) == "table" then + if msg.command == "get" then + digiline:receptor_send(pos, digilines.rules.default, channel, + controller_get_network_info(pos, msg.offset, msg.max_count) + ) + return + end + + elseif type(msg) ~= "string" then + -- Protect against ItemStack(...) errors + return end local item = ItemStack(msg) @@ -501,6 +600,7 @@ local function register_controller() end core.register_node("drawers:controller", def) + controller_content_id = core.get_content_id("drawers:controller") if techage_loaded then techage.register_node({"drawers:controller"}, { diff --git a/settingtypes.txt b/settingtypes.txt index e69de29..84c1f07 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -0,0 +1 @@ +drawers.controller_max_matches (Maximum controller request matches) int 50