commit bc97a4b6341a983f27abd927c71f489b1410391c Author: Zenon Seth Date: Sat Oct 28 22:42:09 2023 +0100 Initial commit, working network setup, controller and cable. WIP diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f0d5ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,49 @@ +License of source code +---------------------- + +GNU Lesser General Public License, version 2.1 +Copyright (C) 2023-2023 Zenon Seth + +This program is free software; you can redistribute it and/or modify it under the terms +of the GNU Lesser General Public License as published by the Free Software Foundation; +either version 2.1 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 Lesser General Public License for more details: +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + + +Licenses of media (textures) +---------------------------- + +Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) +Copyright (C) 2023-2023 Zenon Seth + +You are free to: +Share — copy and redistribute the material in any medium or format. +Adapt — remix, transform, and build upon the material for any purpose, even commercially. +The licensor cannot revoke these freedoms as long as you follow the license terms. + +Under the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and +indicate if changes were made. You may do so in any reasonable manner, but not in any way +that suggests the licensor endorses you or your use. + +ShareAlike — If you remix, transform, or build upon the material, you must distribute +your contributions under the same license as the original. + +No additional restrictions — You may not apply legal terms or technological measures that +legally restrict others from doing anything the license permits. + +Notices: + +You do not have to comply with the license for elements of the material in the public +domain or where your use is permitted by an applicable exception or limitation. +No warranties are given. The license may not give you all of the permissions necessary +for your intended use. For example, other rights such as publicity, privacy, or moral +rights may limit how you use the material. + +For more details: +http://creativecommons.org/licenses/by-sa/3.0/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f7cd3f --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Flowerbeds +Flowerbeds are blocks that allow easier growing of flowers. +Once you place a flower on top of a flowerbed, it will quickly grow in nearby empty flowerbeds. + +This growing is not limited by the usual restriction of flora density, so a single flower planted in a large flowerbed field will eventually fill the entire field with the flower. + +In order to grow new flowers, flowerbeds must be on the same vertical height, and adjecent - even diagonally - to each other. Flowerbeds also do not work at all at depths of -50 or deeper. + +Flowerbeds are crafted from 1 coal, 1 dirt and 1 wood placed vertically. Different woods yields different looking flowerbeds, but they all share the same functionality and can work with each other. \ No newline at end of file diff --git a/api/api.lua b/api/api.lua new file mode 100644 index 0000000..5f56fe3 --- /dev/null +++ b/api/api.lua @@ -0,0 +1,4 @@ +local path = logistica.MODPATH.."/api" + +dofile(path.."/cables.lua") +dofile(path.."/controller.lua") diff --git a/api/cables.lua b/api/cables.lua new file mode 100644 index 0000000..5a677b6 --- /dev/null +++ b/api/cables.lua @@ -0,0 +1,64 @@ + + +-- todo: rework this to make tiers not tied to cable name and to be optional + +-- Main function to register a new cable of certain tier +function logistica.register_cable(tier, size) + local ltier = string.lower(tier) + local cable_name = "logistica:" .. ltier .. "_cable" + local cable_group = logistica.get_cable_group(ltier) + logistica.cables[cable_name] = tier + logistica.tiers[ltier] = true + + local node_box = { + type = "connected", + fixed = { -size, -size, -size, size, size, size }, + connect_top = { -size, -size, -size, size, 0.5, size }, -- y+ + connect_bottom = { -size, -0.5, -size, size, size, size }, -- y- + connect_front = { -size, -size, -0.5, size, size, size }, -- z- + connect_back = { -size, -size, size, size, size, 0.5 }, -- z+ + connect_left = { -0.5, -size, -size, size, size, size }, -- x- + connect_right = { -size, -size, -size, 0.5, size, size }, -- x+ + } + + local def = { + description = tier .. " Cable", + tiles = { "logistica_" .. ltier .. "_cable.png" }, + inventory_image = "logistica_" .. ltier .. "_cable_inv.png", + wield_image = "logistica_" .. ltier .. "_cable_inv.png", + groups = { + choppy = 3, + oddly_breakable_by_hand = 2, + [cable_group] = 1, + }, + sounds = default.node_sound_metal_defaults(), + drop = cable_name, + paramtype = "light", + sunlight_propagates = true, + drawtype = "nodebox", + node_box = node_box, + connects_to = { "group:" .. cable_group, "group:"..logistica.get_machine_group(ltier), logistica.GROUP_ALL }, + on_construct = function(pos) logistica.on_cable_change(pos, nil) end, + after_destruct = function(pos, oldnode) logistica.on_cable_change(pos, oldnode) end, + } + + minetest.register_node(cable_name, def) + + local def_broken = {} + for k, v in pairs(def) do def_broken[k] = v end + def_broken.tiles = { "logistica_" .. ltier .. "_cable.png^logistica_broken.png" } + def_broken.inventory_image = "logistica_" .. ltier .. "_cable_inv.png^logistica_broken.png" + def_broken.groups = { choppy = 3, oddly_breakable_by_hand = 2, not_in_creative_inventory = 1 } + def_broken.description = "Broken " .. tier .. " Cable" + def_broken.node_box = { type = "fixed", fixed = { -0.5, -size, -size, 0.5, size, size } } + def_broken.on_construct = nil + def_broken.after_destruct = nil + + minetest.register_node(cable_name .. "_broken", def_broken) +end + +logistica.register_cable("Copper", 1 / 8) +logistica.register_cable("Silver", 1 / 8) +logistica.register_cable("Gold", 1 / 8) + + diff --git a/api/controller.lua b/api/controller.lua new file mode 100644 index 0000000..5dff78c --- /dev/null +++ b/api/controller.lua @@ -0,0 +1,77 @@ + +--[[ + The definition table will get the fololwing fields overriden (and currently originals are not called): + - on_construct + - after_destruct + - on_timer + + The definition must also provide a `logistica_controller` table. This table should contains: + { + get_max_demand_processing = function(pos) + -- function that will be called to determine how many demand nodes this controller can process per tick + + get_max_storage_ + } + + simpleName is used for the node registration, and will, if necessary, be converted + to lowerspace and all spaces replaced with _ + + tier may be `nil` which will result in the controller connecting to everything +]] +function logistica.register_controller(simpleName, def, tier) + local controller_group = nil + if not tier then + tier = logistica.TIER_ALL + controller_group = logistica.TIER_ALL + else + local ltier = string.lower(tier) + logistica.tiers[ltier] = true + controller_group = logistica.get_machine_group(ltier) + end + local controller_name = "logistica:" .. string.lower(simpleName:gsub(" ", "_")) .. "_controller" + logistica.controllers[controller_name] = tier + + local on_construct = function(pos) + logistica.start_controller_timer(pos) + logistica.on_controller_change(pos, nil) + end + local after_destruct = logistica.on_controller_change + local on_timer = logistica.on_controller_timer + + if not def.groups then + def.groups = {} + end + def.groups[controller_group] = 1 + def.on_construct = on_construct + def.after_destruct = after_destruct + def.on_timer = on_timer + def.drop = controller_name + + minetest.register_node(controller_name, def) + + local def_disabled = {} + for k, v in pairs(def) do def_disabled[k] = v end + local tiles_disabled = {} + for k, v in pairs(def.tiles) do tiles_disabled[k] = v.."^logistica_controller_disabled.png" end + def_disabled.tiles = tiles_disabled + def_disabled.groups = { choppy = 3, oddly_breakable_by_hand = 2 } + def_disabled.on_construct = nil + def_disabled.after_desctruct = nil + def_disabled.on_timer = nil + + minetest.register_node(controller_name.."_disabled", def_disabled) +end + +logistica.register_controller("Simple Controller", { + description = "Simple Controller", + tiles = { "logistica_silver_cable.png" }, + groups = { + choppy = 3, + oddly_breakable_by_hand = 2, + }, + sounds = default.node_sound_metal_defaults(), + paramtype = "light", + sunlight_propagates = false, + drawtype = "normal", + node_box = { type = "regular"}, +}) diff --git a/api/demander.lua b/api/demander.lua new file mode 100644 index 0000000..24ea958 --- /dev/null +++ b/api/demander.lua @@ -0,0 +1,28 @@ +--[[ + Demander nodes should provide a table called `logistica_demander` in their definition + This table should define:: + { + get_demanded = function(pos) + -- required + -- pos: The position of this demander node + -- This will be called to check what item demands are required. + -- The function must return a table of `itemstack` definitions that are needed, e.g.: + -- {"default:cobble 3", "default:stick 2"} + -- if the function returns an empty table or nil, no items will be supplied + + receive_items = function(pos, itemstack) + -- required + -- pos: The position of this demanded node + -- itemstack: an ItemStack object instance + -- called to provide items, as requested by get_demand + -- multiple itemstack requests will result in multiple calls to this function + -- each call providing one itemstack (which may contain multiple items in the stack) + } + + Currently demanders will connect to all network tiers - network tiers only differ +]] + +function logistica.register_demander(simpleName, definition) + local demander_name = "logistica:demander_"..simpleName + +end diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..98a9e34 --- /dev/null +++ b/init.lua @@ -0,0 +1,12 @@ +logistica = {} + +logistica.MODNAME = minetest.get_current_modname() or "logistica" +logistica.MODPATH = minetest.get_modpath(logistica.MODNAME) + +-- order of loading files DOES matter +dofile(logistica.MODPATH.."/util/util.lua") +dofile(logistica.MODPATH.."/logic/logic.lua") +dofile(logistica.MODPATH.."/tools/tools.lua") + +-- api should be kept last +dofile(logistica.MODPATH.."/api/api.lua") diff --git a/logic/controller.lua b/logic/controller.lua new file mode 100644 index 0000000..126d301 --- /dev/null +++ b/logic/controller.lua @@ -0,0 +1,51 @@ +--[[ +Outline of controller tick/s: +1. Gather demand from each demander, add them to queue + - smart queue needed, unify demand per demander +2. For the first N demands, check each supplier and if applicable fulfil demand +3. Gather all storage slots + - cached, hopefully +4. For each storage slot, check each supplier, and pull up to S items per slot into storage +]] + +local TIMER_DURATION_SHORT = 0.5 +local TIMER_DURATION_LONG = 2.0 + +function logistica.start_controller_timer(pos, duration) + if duration == nil then duration = TIMER_DURATION_LONG end + local timer = minetest.get_node_timer(pos) + timer:start(duration) +end + +function logistica.on_controller_timer(pos, elapsed) + local node = minetest.get_node(pos) + if not node then return false end -- what? + if node.name:find("_disabled") then return false end -- disabled controllers don't do anything + + local had_demand = false + + local network = logistica.get_network_or_nil(pos) + if not network then + logistica.on_controller_change(pos, nil) -- this should re-scan the network + end + network = logistica.get_network_or_nil(pos) + if not network then return true end -- something went wrong, retry again + + local nodes_in_demand = {} + for demander,_ in pairs(network.demanders) do + -- check for demand + end + + -- for each demand, check suppliers + + + -- for each demand, check storage + + if had_demand then + logistica.start_controller_timer(pos, TIMER_DURATION_SHORT) + else + logistica.start_controller_timer(pos, TIMER_DURATION_LONG) + end + + return false +end diff --git a/logic/groups.lua b/logic/groups.lua new file mode 100644 index 0000000..d8284bd --- /dev/null +++ b/logic/groups.lua @@ -0,0 +1,95 @@ +logistica.cables = {} +logistica.machines = {} +logistica.controllers = {} +logistica.demanders = {} +logistica.suppliers = {} +logistica.storage = {} +-- logistica.demand_and_supplier = {} +logistica.tiers = {} +logistica.TIER_ALL = "logistica_all_tiers" +logistica.GROUP_ALL = "group:" .. logistica.TIER_ALL + +function logistica.get_cable_group(tier) + return "logistica_" .. tier .. "_cable" +end + +function logistica.get_machine_group(tier) + return "logistica_" .. tier .. "_machine" +end + +function logistica.is_cable(name) + if logistica.cables[name] then + return true + else + return false + end +end + +function logistica.is_machine(name) + if logistica.machines[name] then + return true + else + return false + end +end + +function logistica.is_demander(name) + if logistica.demanders[name] then + return true + else + return false + end +end + +function logistica.is_supplier(name) + if logistica.suppliers[name] then + return true + else + return false + end +end + +function logistica.is_storage(name) + if logistica.storage[name] then + return true + else + return false + end +end + +function logistica.is_controller(name) + if logistica.controllers[name] then + return true + else + return false + end +end + +function logistica.get_item_tiers(name) + local tiers = {} + for tier,_ in pairs(logistica.tiers) do + local cable_group = logistica.get_cable_group(tier) + local machine_group = logistica.get_machine_group(tier) + if minetest.get_item_group(name, cable_group) > 0 then + tiers[tier] = true + end + if minetest.get_item_group(name, machine_group) > 0 then + tiers[tier] = true + end + if minetest.get_item_group(name, logistica.TIER_ALL) > 0 then + tiers[logistica.TIER_ALL] = true + end + end + return tiers +end + +function logistica.do_tiers_match(tiers1, tiers2) + for t1, _ in pairs(tiers1) do + for t2, _ in pairs(tiers2) do + if t1 == logistica.TIER_ALL or t2 == logistica.TIER_ALL or t1 == t2 then + return true + end + end + end + return false +end \ No newline at end of file diff --git a/logic/logic.lua b/logic/logic.lua new file mode 100644 index 0000000..6ba8922 --- /dev/null +++ b/logic/logic.lua @@ -0,0 +1,4 @@ +local path = logistica.MODPATH .. "/logic" +dofile(path .. "/groups.lua") +dofile(path .. "/network_logic.lua") +dofile(path .. "/controller.lua") diff --git a/logic/network_logic.lua b/logic/network_logic.lua new file mode 100644 index 0000000..3c2b14b --- /dev/null +++ b/logic/network_logic.lua @@ -0,0 +1,281 @@ +local networks = {} +local HARD_NETWORK_NODE_LIMIT = 1000 -- A network cannot consist of more than this many nodes +local CREATE_NETWORK_STATUS_OK = 0 +local CREATE_NETWORK_STATUS_FAIL_OTHER_NETWORK = -1 +local CREATE_NETWORK_STATUS_TOO_MANY_NODES = -2 + +local adjecent = { + vector.new( 1, 0, 0), + vector.new( 0, 1, 0), + vector.new( 0, 0, 1), + vector.new(-1, 0, 0), + vector.new( 0, -1, 0), + vector.new( 0, 0, -1), +} + +function logistica.get_network_name_or_nil(pos) + local hash = minetest.hash_node_position(pos) + for nHash, network in pairs(networks) do + if hash == nHash then return network.name end + if network.cables[hash] then return network.name end + if network.machines[hash] then return network.name end + end + return nil +end + +function logistica.get_network_or_nil(pos) + local hash = minetest.hash_node_position(pos) + for nHash, network in pairs(networks) do + if hash == nHash then return network end + if network.cables[hash] then return network end + if network.machines[hash] then return network end + end + return nil +end + +function logistica.get_network_id_or_nil(pos) + local network = logistica.get_network_or_nil(pos) + if not network then return nil else return network.controller end +end + +---------------------------------------------------------------- +-- Network operation functions +---------------------------------------------------------------- + +local function dumb_remove_from_network(networkName, pos) + local network = networks[networkName] + if not network then return false end + local hashedPos = minetest.hash_node_position(pos) + if network.cables[hashedPos] then + network.cables[hashedPos] = nil + return true + end + if network.machines[hashedPos] then + network.machines[hashedPos] = nil + return true + end + if network.controller == hashedPos then + networks[networkName] = nil -- removing the controller removes the whole network + return true + end + return false +end + +local function dumb_add_pos_to_network(networkName, pos) + local network = networks[networkName] + if not network then return false end + local node = minetest.get_node(pos) + local hashedPos = minetest.hash_node_position(pos) + if logistica.is_cable(node.name) then + network.cables[hashedPos] = true + elseif logistica.is_machine(node.name) then + network.machines[hashedPos] = true + else -- can't dumb-add a controller to a network + return false + end + return true +end + +local function clear_network(networkName) + local network = networks[networkName] + if not network then return false end + networks[networkName] = nil +end + +local function break_cable(pos) + local node = minetest.get_node_or_nil(pos) + if node and logistica.is_cable(node.name) then + logistica.swap_node(pos, node.name .. "_broken") + end +end + +local function recursive_scan_for_nodes_for_controller(network, positions, numScanned) + if #positions <= 0 then return CREATE_NETWORK_STATUS_OK end + if not numScanned then numScanned = #positions + else numScanned = numScanned + #positions end + + if numScanned > HARD_NETWORK_NODE_LIMIT then + return CREATE_NETWORK_STATUS_TOO_MANY_NODES + end + + local connections = {} + for _, pos in pairs(positions) do + logistica.load_position(pos) + local tiers = logistica.get_item_tiers(minetest.get_node(pos).name) + local isAllTier = tiers[logistica.TIER_ALL] == true + for _, offset in pairs(adjecent) do + local otherPos = vector.add(pos, offset) + logistica.load_position(otherPos) + local otherHash = minetest.hash_node_position(otherPos) + local tiersMatch = isAllTier + if tiersMatch ~= true then + local otherTiers = logistica.get_item_tiers(minetest.get_node(otherPos).name) + tiersMatch = logistica.do_tiers_match(tiers, otherTiers) + end + if tiersMatch + and network.controller ~= otherHash + and network.machines[otherHash] == nil + and network.cables[otherHash] == nil then + local otherNode = minetest.get_node(otherPos) + if logistica.is_cable(otherNode.name) then + local existingNetwork = logistica.get_network_id_or_nil(otherPos) + if existingNetwork then + return CREATE_NETWORK_STATUS_FAIL_OTHER_NETWORK + else + network.cables[otherHash] = true + table.insert(connections, otherPos) + end + elseif logistica.is_demander(otherNode.name) then + network.machines[otherHash] = true + network.demanders[otherHash] = true + elseif logistica.is_supplier(otherNode.name) then + network.machines[otherHash] = true + network.suppliers[otherHash] = true + elseif logistica.is_storage(otherNode.name) then + network.machines[otherHash] = true + network.storage[otherHash] = true + end + end + end -- end inner for loop + end -- end outer for loop + -- We have nested loops so we can do tail recursion + return recursive_scan_for_nodes_for_controller(network, connections, numScanned) +end + +local function add_network(controllerPosition) + local node = minetest.get_node(controllerPosition) + if not node.name:find("_controller") or not node.name:find("logistica:") then return false end + local meta = minetest.get_meta(controllerPosition) + local controllerHash = minetest.hash_node_position(controllerPosition) + local network = {} + local networkName = logistica.get_network_name_for(controllerPosition) + networks[controllerHash] = network + meta:set_string("infotext", "Controller of Network: "..networkName) + network.controller = controllerHash + network.name = networkName + network.machines = {} + network.cables = {} + network.demanders = {} + network.suppliers = {} + network.storage = {} + local status = recursive_scan_for_nodes_for_controller(network, {controllerPosition}) + local errorMsg = nil + if status == CREATE_NETWORK_STATUS_FAIL_OTHER_NETWORK then + errorMsg = "Cannot connect to already existing network!" + logistica.swap_node(controllerPosition, node.name.."_disabled") + elseif status == CREATE_NETWORK_STATUS_TOO_MANY_NODES then + errorMsg = "Controller max nodes limit of "..HARD_NETWORK_NODE_LIMIT.." nodes per network exceeded!" + end + if errorMsg ~= nil then + networks[controllerHash] = nil + meta:set_string("infotext", "ERROR: "..errorMsg) + end +end + +---------------------------------------------------------------- +-- worker functions for cable/machine/controllers +---------------------------------------------------------------- + +local function rescan_network(networkName) + local network = networks[networkName] + if not network then return false end + if not network.controller then return false end + local conHash = network.controller + local controllerPosition = minetest.get_position_from_hash(conHash) + clear_network(networkName) + add_network(controllerPosition) +end + +local function find_cable_connections(pos, node) + local connections = {} + for _, offset in pairs(adjecent) do + local otherPos = vector.add(pos, offset) + local otherNode = minetest.get_node_or_nil(otherPos) + if otherNode then + if otherNode.name == node.name then + table.insert(connections, otherPos) + elseif minetest.get_item_group(otherNode, logistica.GROUP_ALL) > 0 then + table.insert(connections, otherPos) + else -- check if adjecent node is a machine of same tier + local nodeTiers = logistica.get_item_tiers(node.name) + local otherTiers = logistica.get_item_tiers(otherNode.name) + if logistica.do_tiers_match(nodeTiers, otherTiers) then + table.insert(connections, otherPos) + end + end + end + end + return connections +end + +local function try_to_add_network(pos) + add_network(pos) +end + +local function find_machine_connections(pos, node) + +end + +---------------------------------------------------------------- +-- global namespaced functions +---------------------------------------------------------------- + +function logistica.on_cable_change(pos, oldNode) + local node = oldNode or minetest.get_node(pos) + local meta = minetest.get_meta(pos) + local placed = (oldNode == nil) -- if oldNode is nil, we placed it + + local connections = find_cable_connections(pos, node) + if not connections or #connections < 1 then return end -- nothing to update + local networkEnd = #connections == 1 + + if networkEnd then + if not placed then -- removed a network end + dumb_remove_from_network(pos) + else + local otherNode = minetest.get_node(connections[1]) + if logistica.is_cable(otherNode.name) or logistica.is_controller(otherNode.name) then + local otherNetwork = logistica.get_network_id_or_nil(connections[1]) + if otherNetwork then + dumb_add_pos_to_network(otherNetwork, pos) + end + end + end + return + end + + -- We have more than 1 connected nodes - either cables or machines, something needs recalculating + local connectedNetworks = {} + for _, connectedPos in pairs(connections) do + local otherNetwork = logistica.get_network_id_or_nil(connectedPos) + if otherNetwork then + connectedNetworks[otherNetwork] = true + end + end + local firstNetwork = nil + local numNetworks = 0 + for k,_ in pairs(connectedNetworks) do + numNetworks = numNetworks + 1 + if firstNetwork == nil then firstNetwork = k end + end + if numNetworks <= 0 then return end -- still nothing to update + if numNetworks == 1 then + rescan_network(firstNetwork) + else + -- two or more connected networks (should only happen on place) + -- this cable can't work here, break it, and nothing to update + break_cable(pos) + end +end + +function logistica.on_controller_change(pos, oldNode) + local node = oldNode or minetest.get_node(pos) + local meta = minetest.get_meta(pos) + local hashPos = minetest.hash_node_position(pos) + local placed = (oldNode == nil) -- if oldNode is nil, we placed a new one + if placed == true then + try_to_add_network(pos) + else + clear_network(hashPos) + end +end \ No newline at end of file diff --git a/logic/processing_queue.lua b/logic/processing_queue.lua new file mode 100644 index 0000000..91a5c59 --- /dev/null +++ b/logic/processing_queue.lua @@ -0,0 +1,58 @@ + +logistica.proq = {} + +local QUEUE_KEY = "log_proq" +local DELIM = "|" + +-- listOfPositions must be a list (naturally numbered table) of position vectors +local function save_queue(meta, listOfPositions) + local tableOfStrings = {} + for _,v in ipairs(listOfPositions) do + table.insert(tableOfStrings, vector.to_string(v)) + end + local singleString = table.concat(tableOfStrings, DELIM) + meta:set_string(singleString) +end + +-- listOfPositions must be a list (naturally numbered table) of position vectors +function logistica.proq.add(pos, listOfPositions) + local meta = minetest.get_meta(pos) + local positions = logistica.proq.get_all(pos) + for _, v in ipairs(listOfPositions) do + table.insert(positions, v) + end + save_queue(meta, positions) +end + +-- returns a table of up to the next N positions +function logistica.proq.pop_next(pos, count) + local meta = minetest.get_meta(pos) + local positions = logistica.proq.get_all(pos) + local ret = {} + local rem = {} + for i, v in ipairs(positions) do + if (i <= count) then + table.insert(ret, v) + else + table.insert(rem, v) + end + end + save_queue(meta, rem) + return ret +end + +function logistica.proq.get_all(pos) + local meta = minetest.get_meta(pos) + if not meta:contains(QUEUE_KEY) then return {} end + local compressedString = meta:get_string(QUEUE_KEY) + local positionStrings = string.split(compressedString, DELIM, false) + local positions = {} + for _, v in ipairs(positionStrings) do + local vector = vector.from_string(v) + if vector then + table.insert(positions, vector) + end + end + return positions +end + diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..9941a05 --- /dev/null +++ b/mod.conf @@ -0,0 +1,6 @@ +name = logistica +depends = default +min_minetest_version = 5.0 +author = ZenonSeth +description = On-demand item transportation +title = Logistica diff --git a/textures/logistica_broken.png b/textures/logistica_broken.png new file mode 100644 index 0000000..81f3706 Binary files /dev/null and b/textures/logistica_broken.png differ diff --git a/textures/logistica_controller_disabled.png b/textures/logistica_controller_disabled.png new file mode 100644 index 0000000..3772a5a Binary files /dev/null and b/textures/logistica_controller_disabled.png differ diff --git a/textures/logistica_copper_cable.png b/textures/logistica_copper_cable.png new file mode 100644 index 0000000..fa377e4 Binary files /dev/null and b/textures/logistica_copper_cable.png differ diff --git a/textures/logistica_copper_cable_inv.png b/textures/logistica_copper_cable_inv.png new file mode 100644 index 0000000..4f7d3b3 Binary files /dev/null and b/textures/logistica_copper_cable_inv.png differ diff --git a/textures/logistica_copper_controller.png b/textures/logistica_copper_controller.png new file mode 100644 index 0000000..fa377e4 Binary files /dev/null and b/textures/logistica_copper_controller.png differ diff --git a/textures/logistica_gold_cable.png b/textures/logistica_gold_cable.png new file mode 100644 index 0000000..da267d1 Binary files /dev/null and b/textures/logistica_gold_cable.png differ diff --git a/textures/logistica_gold_cable_inv.png b/textures/logistica_gold_cable_inv.png new file mode 100644 index 0000000..f819e0e Binary files /dev/null and b/textures/logistica_gold_cable_inv.png differ diff --git a/textures/logistica_gold_controller.png b/textures/logistica_gold_controller.png new file mode 100644 index 0000000..da267d1 Binary files /dev/null and b/textures/logistica_gold_controller.png differ diff --git a/textures/logistica_network_tool.png b/textures/logistica_network_tool.png new file mode 100644 index 0000000..71d1547 Binary files /dev/null and b/textures/logistica_network_tool.png differ diff --git a/textures/logistica_silver_cable.png b/textures/logistica_silver_cable.png new file mode 100644 index 0000000..d006985 Binary files /dev/null and b/textures/logistica_silver_cable.png differ diff --git a/textures/logistica_silver_cable_inv.png b/textures/logistica_silver_cable_inv.png new file mode 100644 index 0000000..74e8b13 Binary files /dev/null and b/textures/logistica_silver_cable_inv.png differ diff --git a/textures/logistica_silver_controller.png b/textures/logistica_silver_controller.png new file mode 100644 index 0000000..d006985 Binary files /dev/null and b/textures/logistica_silver_controller.png differ diff --git a/tools/tools.lua b/tools/tools.lua new file mode 100644 index 0000000..76b8f94 --- /dev/null +++ b/tools/tools.lua @@ -0,0 +1,19 @@ + +minetest.register_craftitem("logistica:network_tool",{ + description = "Logistica Network Tool\nUse on a node to see network info", + inventory_image = "logistica_network_tool.png", + wield_image = "logistica_network_tool.png", + stack_max = 1, + on_place = function(itemstack, placer, pointed_thing) + local pos = pointed_thing.under + if not placer or not pos then return end + local node = minetest.get_node_or_nil(pos) + if not node or node.name:find("logistica:") == nil then return end + local network = logistica.get_network_name_or_nil(pos) or "" + -- minetest.chat_send_player(placer:get_player_name(), "Network: "..network) + logistica.show_short_popup( + placer:get_player_name(), + "("..pos.x..","..pos.y..","..pos.z..") Network: "..network + ) + end +}) \ No newline at end of file diff --git a/util/common.lua b/util/common.lua new file mode 100644 index 0000000..c0aed65 --- /dev/null +++ b/util/common.lua @@ -0,0 +1,43 @@ + + +local charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +local function rand_str(length, seed) + math.randomseed(seed) + local ret = {} + local r + for i = 1, length do + r = math.random(1, #charset) + table.insert(ret, charset:sub(r, r)) + end + return table.concat(ret) +end + +---------------------------------------------------------------- +-- global namespaced functions +---------------------------------------------------------------- + +function logistica.load_position(pos) + if pos.x < -30912 or pos.y < -30912 or pos.z < -30912 or + pos.x > 30927 or pos.y > 30927 or pos.z > 30927 then return end + if minetest.get_node_or_nil(pos) then + return + end + local vm = minetest.get_voxel_manip() + vm:read_from_map(pos, pos) +end + +function logistica.swap_node(pos, newName) + local node = minetest.get_node(pos) + if node.name ~= newName then + node.name = newName + minetest.swap_node(pos, node) + end +end + +function logistica.get_network_name_for(pos) + local p1 = rand_str(3, pos.x) + local p2 = rand_str(3, pos.y) + local p3 = rand_str(3, pos.z) + return p1.."-"..p2.."-"..p3 +end + diff --git a/util/hud.lua b/util/hud.lua new file mode 100644 index 0000000..946c71d --- /dev/null +++ b/util/hud.lua @@ -0,0 +1,33 @@ + +local playerHud = {} + +function logistica.show_short_popup(playerName, text) + local player = minetest.get_player_by_name(playerName) + if not player then return end + + if playerHud[playerName] then + player:hud_remove(playerHud[playerName].hudId) + playerHud[playerName].job:cancel() + playerHud[playerName] = nil + end + local hudId = player:hud_add({ + hud_elem_type = "text", + position = {x = 0.5, y = 0.5}, + offset = {x = 0, y = 40}, + text = text, + scale = { x = 1, y = 1}, + alignment = { x = 0.5, y = 0 }, + number = 0xDFDFDF, + }) + playerHud[playerName] = {} + playerHud[playerName].hudId = hudId + local job = minetest.after(3, function() + local pl = minetest.get_player_by_name(playerName) + if not pl then return end + if not playerHud[playerName] then return end + pl:hud_remove(playerHud[playerName].hudId) + playerHud[playerName] = nil + end) + playerHud[playerName].job = job + +end \ No newline at end of file diff --git a/util/rotations.lua b/util/rotations.lua new file mode 100644 index 0000000..ba2a006 --- /dev/null +++ b/util/rotations.lua @@ -0,0 +1,44 @@ +local rots = {} +for i=0,23 do rots[i] = {} end +local function p(a,b,c) return vector.new(a,b,c) end + + rots[0].up=p( 0, 1, 0); rots[0].forward=p( 0, 0,-1); rots[0].left=p( 1, 0, 0) + rots[1].up=p( 0, 1, 0); rots[1].forward=p(-1, 0, 0); rots[1].left=p( 0, 0,-1) + rots[2].up=p( 0, 1, 0); rots[2].forward=p( 0, 0, 1); rots[2].left=p(-1, 0, 0) + rots[3].up=p( 0, 1, 0); rots[3].forward=p( 1, 0, 0); rots[3].left=p( 0, 0, 1) + + rots[4].up=p( 0, 0, 1); rots[4].forward=p( 0, 1, 0); rots[4].left=p( 1, 0, 0) + rots[5].up=p( 0, 0, 1); rots[5].forward=p(-1, 0, 0); rots[5].left=p( 0, 1, 0) + rots[6].up=p( 0, 0, 1); rots[6].forward=p( 0,-1, 0); rots[6].left=p(-1, 0, 0) + rots[7].up=p( 0, 0, 1); rots[7].forward=p( 1, 0, 0); rots[7].left=p( 0,-1, 0) + + rots[8].up=p( 0, 0,-1); rots[8].forward=p( 0,-1, 0); rots[8].left=p( 1, 0, 0) + rots[9].up=p( 0, 0,-1); rots[9].forward=p(-1, 0, 0); rots[9].left=p( 0,-1, 0) +rots[10].up=p( 0, 0,-1); rots[10].forward=p( 0, 1, 0); rots[10].left=p(-1, 0, 0) +rots[11].up=p( 0, 0,-1); rots[11].forward=p( 1, 0, 0); rots[11].left=p( 0, 1, 0) + +rots[12].up=p( 1, 0, 0); rots[12].forward=p( 0, 0,-1); rots[12].left=p( 0,-1, 0) +rots[13].up=p( 1, 0, 0); rots[13].forward=p( 0, 1, 0); rots[13].left=p( 0, 0,-1) +rots[14].up=p( 1, 0, 0); rots[14].forward=p( 0, 0, 1); rots[14].left=p( 0, 1, 0) +rots[15].up=p( 1, 0, 0); rots[15].forward=p( 0,-1, 0); rots[15].left=p( 0, 0, 1) + +rots[16].up=p(-1, 0, 0); rots[16].forward=p( 0, 0,-1); rots[16].left=p( 0, 1, 0) +rots[17].up=p(-1, 0, 0); rots[17].forward=p( 0,-1, 0); rots[17].left=p( 0, 0,-1) +rots[18].up=p(-1, 0, 0); rots[18].forward=p( 0, 0, 1); rots[18].left=p( 0,-1, 0) +rots[19].up=p(-1, 0, 0); rots[19].forward=p( 0, 1, 0); rots[19].left=p( 0, 0, 1) + +rots[20].up=p( 0,-1, 0); rots[20].forward=p( 0, 0,-1); rots[20].left=p(-1, 0, 0) +rots[21].up=p( 0,-1, 0); rots[21].forward=p( 1, 0, 0); rots[21].left=p( 0, 0,-1) +rots[22].up=p( 0,-1, 0); rots[22].forward=p( 0, 0, 1); rots[22].left=p( 1, 0, 0) +rots[23].up=p( 0,-1, 0); rots[23].forward=p(-1, 0, 0); rots[23].left=p( 0, 0, 1) + +for i=0,23 do + rots[i].down = vector.multiply(rots[i].up, -1) + rots[i].backward = vector.multiply(rots[i].forward, -1) + rots[i].right = vector.multiply(rots[i].left, -1) +end + +function logistica.get_rot_directions(param2) + if param2 < 0 or param2 > 23 then return nil end + return rots[param2] +end diff --git a/util/util.lua b/util/util.lua new file mode 100644 index 0000000..45bbe64 --- /dev/null +++ b/util/util.lua @@ -0,0 +1,39 @@ +local path = logistica.MODPATH.."/util" + +dofile(path.."/common.lua") +dofile(path.."/rotations.lua") +dofile(path.."/hud.lua") + +function logistica.set_infotext(pos, txt) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", txt) +end + +function logistica.ttos(val, name, skipnewlines, depth) + skipnewlines = skipnewlines or true + depth = depth or 0 + + local tmp = string.rep(" ", depth) + + if name then tmp = tmp .. name .. " = " end + + if type(val) == "table" then + tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") + + for k, v in pairs(val) do + tmp = tmp .. logistica.ttos(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") + end + + tmp = tmp .. string.rep(" ", depth) .. "}" + elseif type(val) == "number" then + tmp = tmp .. tostring(val) + elseif type(val) == "string" then + tmp = tmp .. string.format("%q", val) + elseif type(val) == "boolean" then + tmp = tmp .. (val and "true" or "false") + else + tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" + end + + return tmp +end