Initial commit, working network setup, controller and cable. WIP
49
LICENSE
Normal 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
@ -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
@ -0,0 +1,4 @@
|
||||
local path = logistica.MODPATH.."/api"
|
||||
|
||||
dofile(path.."/cables.lua")
|
||||
dofile(path.."/controller.lua")
|
64
api/cables.lua
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
|
58
logic/processing_queue.lua
Normal 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
@ -0,0 +1,6 @@
|
||||
name = logistica
|
||||
depends = default
|
||||
min_minetest_version = 5.0
|
||||
author = ZenonSeth
|
||||
description = On-demand item transportation
|
||||
title = Logistica
|
BIN
textures/logistica_broken.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
textures/logistica_controller_disabled.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/logistica_copper_cable.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
textures/logistica_copper_cable_inv.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
textures/logistica_copper_controller.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
textures/logistica_gold_cable.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
textures/logistica_gold_cable_inv.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
textures/logistica_gold_controller.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
textures/logistica_network_tool.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/logistica_silver_cable.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
textures/logistica_silver_cable_inv.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
textures/logistica_silver_controller.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
19
tools/tools.lua
Normal 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
@ -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
@ -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
@ -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
@ -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
|