Initial commit, working network setup, controller and cable. WIP

This commit is contained in:
Zenon Seth 2023-10-28 22:42:09 +01:00
commit bc97a4b634
30 changed files with 916 additions and 0 deletions

49
LICENSE Normal file
View File

@ -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/

9
README.md Normal file
View File

@ -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.

4
api/api.lua Normal file
View File

@ -0,0 +1,4 @@
local path = logistica.MODPATH.."/api"
dofile(path.."/cables.lua")
dofile(path.."/controller.lua")

64
api/cables.lua Normal file
View File

@ -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)

77
api/controller.lua Normal file
View File

@ -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"},
})

28
api/demander.lua Normal file
View File

@ -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

12
init.lua Normal file
View File

@ -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")

51
logic/controller.lua Normal file
View File

@ -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

95
logic/groups.lua Normal file
View File

@ -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

4
logic/logic.lua Normal file
View File

@ -0,0 +1,4 @@
local path = logistica.MODPATH .. "/logic"
dofile(path .. "/groups.lua")
dofile(path .. "/network_logic.lua")
dofile(path .. "/controller.lua")

281
logic/network_logic.lua Normal file
View File

@ -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

View File

@ -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

6
mod.conf Normal file
View File

@ -0,0 +1,6 @@
name = logistica
depends = default
min_minetest_version = 5.0
author = ZenonSeth
description = On-demand item transportation
title = Logistica

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

19
tools/tools.lua Normal file
View File

@ -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 "<NONE>"
-- 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
})

43
util/common.lua Normal file
View File

@ -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

33
util/hud.lua Normal file
View File

@ -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

44
util/rotations.lua Normal file
View File

@ -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

39
util/util.lua Normal file
View File

@ -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