Initial commit, working network and storage implementation. WIP

This commit is contained in:
Zenon Seth 2023-11-04 06:35:32 +00:00
parent bc97a4b634
commit 195b597504
26 changed files with 639 additions and 125 deletions

View File

@ -1,9 +1,33 @@
# 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.
# Logistica
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.
Logistica is a item transport, distribution and storage mod.
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.
The core principle behind this mod is that item transportation is demand-driven: no item is moved unless it is requested somewhere.
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.
# Nodes
All nodes require to be connected to an active network to perform their tasks. Nodes without a network, or nodes with conflicting network connections will not do anything.
## Controller
A Controller is a node required to create an active network. Exactly one controller can be conneted to a network, and removing it will disconnect all other devices connected to the network.
## Cables
Cables are simple nodes that connect a controller to other nodes, allowing the establishing of a network. Placing a cable that bridges two different active networks will burn that cable. Burned cables can simply be picked up, and return normal cables. TODO Cables come in TODO variants, all being identical in function, but different variants do not connect to each other, allowing parallel networks.
## Demander
A Demander is a node that targets any other node with an inventory, and can be configured to request items from its network to keep the target's inventory full of a specific number of specific items.
## Supplier
A Supplier is a node that takes items from another node's inventory, configuratble to only inject certain items, and tries to fulfil any supply for that item in its network. Suppliers have a tick rate and each tick they pick one inventory slot of the target's invenotry and check if there's demand anywhere on the network for that type of item. On the following tick, the supplier will move onto the next slot of the target's inventory and check that.
There are two variants:
### Item Supplier
This supplier takes only 1 item from each inventory slot it checks and fulfils one single demand with it.
### Stack Supplier
This supplier will take the whole stack, or as many items as are needed out of a stack, and try to fulfil any demand on its network with those items.
## Storage
Storage nodes provide mass-storage for items. They also act as suppliers for any demand. Storage nodes are more efficient at fulfilling demand over suppliers because they do not need to check one inventory slot at a time, but instead consider their entire inventory.

View File

@ -2,3 +2,5 @@ local path = logistica.MODPATH.."/api"
dofile(path.."/cables.lua")
dofile(path.."/controller.lua")
dofile(path.."/mass_storage.lua")
dofile(path.."/demander.lua")

View File

@ -27,6 +27,7 @@ function logistica.register_cable(tier, size)
inventory_image = "logistica_" .. ltier .. "_cable_inv.png",
wield_image = "logistica_" .. ltier .. "_cable_inv.png",
groups = {
cracky = 3,
choppy = 3,
oddly_breakable_by_hand = 2,
[cable_group] = 1,
@ -48,17 +49,16 @@ function logistica.register_cable(tier, size)
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.groups = { cracky = 3, 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.connects_to = nil
def_broken.on_construct = nil
def_broken.after_destruct = nil
minetest.register_node(cable_name .. "_broken", def_broken)
minetest.register_node(cable_name .. "_disabled", def_broken)
end
logistica.register_cable("Copper", 1 / 8)
logistica.register_cable("Silver", 1 / 8)
logistica.register_cable("Gold", 1 / 8)

View File

@ -52,9 +52,9 @@ function logistica.register_controller(simpleName, def, tier)
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
for k, v in pairs(def.tiles) do tiles_disabled[k] = v.."^logistica_disabled.png" end
def_disabled.tiles = tiles_disabled
def_disabled.groups = { choppy = 3, oddly_breakable_by_hand = 2 }
def_disabled.groups = { oddly_breakable_by_hand = 3 }
def_disabled.on_construct = nil
def_disabled.after_desctruct = nil
def_disabled.on_timer = nil
@ -66,7 +66,6 @@ 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(),

182
api/mass_storage.lua Normal file
View File

@ -0,0 +1,182 @@
local function get_mass_storage_upgrade_inv(posForm, numUpgradeSlots)
if numUpgradeSlots <= 0 then return "" end
local upIconX = 1.5 + 1.25 * (7 - numUpgradeSlots) -- sort of hardcoded
local upInvX = upIconX + 1.25
local y = 3.5
return "image["..upIconX..","..y..";1,1;logistica_icon_upgrade.png]" ..
"list["..posForm..";upgrade;"..upInvX..","..y..";"..numUpgradeSlots..",1;0]"
end
-- formspec
local function get_mass_storage_formspec(pos, numUpgradeSlots)
local posForm = "nodemeta:"..pos.x..","..pos.y..","..pos.z
local upgradeInvString = get_mass_storage_upgrade_inv(posForm, numUpgradeSlots)
return "formspec_version[4]"..
"size[12,10.5]" ..
logistica.ui.background..
"list[current_player;main;1.5,5;8,4;0]" ..
"list["..posForm..";storage;1.5,2.1;8,1;0]" ..
"list["..posForm..";filter;1.5,1;8,1;0]" ..
"image[0.25,1;1,1;logistica_icon_filter.png]" ..
"list["..posForm..";main;1.5,3.5;1,1;0]" ..
"image[0.25,2.1;1,1;logistica_icon_mass_storage.png]" ..
"image[0.25,3.5;1,1;logistica_icon_input.png]"..
"listring[current_player;main]"..
"listring["..posForm..";main]"..
"listring["..posForm..";storage]"..
"listring[current_player;main]"..
"listring["..posForm..";main]"..
"listring[current_player;main]"..
upgradeInvString
end
-- callbacks
local function after_place_mass_storage(pos, placer, numSlots, numUpgradeSlots)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
local inv = meta:get_inventory()
inv:set_size("main", 1)
inv:set_size("filter", numSlots)
inv:set_size("storage", numSlots)
inv:set_size("upgrade", numUpgradeSlots)
-- and connect to network
logistica.on_storage_change(pos)
end
local function allow_mass_storage_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player) then return 0 end
if from_list == "main" and to_list == "main" then return count end
if from_list == "filter" and to_list == "filter" then return count end
return 0
end
local function allow_mass_storage_inv_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player) then return 0 end
if listname == "storage" then return 0 end
if listname == "main" then
return logistica.try_to_add_item_to_storage(pos, stack, true)
end
if listname == "filter" then
if stack:get_stack_max() == 1 then return 0 end
local copyStack = ItemStack(stack:get_name())
copyStack:set_count(1)
local inv = minetest.get_meta(pos):get_inventory()
inv:set_stack("filter", index, copyStack)
return 0
end
return stack:get_count()
end
local function allow_mass_storage_inv_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player) then return 0 end
if listname == "storage" then
return logistica.clamp(stack:get_count(), 0, stack:get_stack_max())
end
if listname == "main" then return stack:get_count() end
if listname == "filter" then
local inv = minetest.get_meta(pos):get_inventory()
if not inv:get_stack("storage", index):is_empty() then return 0 end
local stack = inv:get_stack("filter", index)
stack:clear()
inv:set_stack("filter", index, stack)
return 0
end
return stack:get_count()
end
local function on_mass_storage_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player) then return 0 end
end
local function on_mass_storage_inv_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player) then return 0 end
if listname == "main" then
local taken = logistica.try_to_add_item_to_storage(pos, stack)
if taken > 0 then
local inv = minetest.get_meta(pos):get_inventory()
local fullstack = inv:get_stack(listname, index)
if taken == fullstack:get_count() then
fullstack:clear()
else
fullstack:set_count(fullstack:get_count() - taken)
end
inv:set_stack(listname, index, fullstack)
end
end
end
local function on_mass_storage_inv_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player) then return 0 end
end
local function on_mass_storage_right_click(pos, node, clicker, itemstack, pointed_thing)
local numUpgradeSlots = minetest.registered_nodes[node.name].logistica.numUpgradeSlots
minetest.show_formspec(
clicker:get_player_name(),
"mass_storage_formspec",
get_mass_storage_formspec(pos, numUpgradeSlots)
)
end
local function on_mass_storage_preserve_metadata(pos, oldnode, oldmeta, drops)
local drop = drops[1]
if not drop then return end
end
----------------------------------------------------------------
-- Public Registration API
----------------------------------------------------------------
function logistica.register_mass_storage(simpleName, numSlots, numItemsPerSlot, numUpgradeSlots)
local lname = string.lower(string.gsub(simpleName, " ", "_"))
local storageName = "logistica:mass_storage_"..lname
local grps = {cracky = 3, choppy = 3, oddly_breakable_by_hand = 2}
numUpgradeSlots = logistica.clamp(numUpgradeSlots, 0, 4)
grps[logistica.TIER_ALL] = 1
logistica.mass_storage[storageName] = true
local def = {
description = simpleName.." Mass Storage",
tiles = { "logistica_"..lname.."_mass_storage.png" },
groups = grps,
after_place_node = function(pos, placer, itemstack)
after_place_mass_storage(pos, placer, numSlots, numUpgradeSlots)
end,
after_destruct = logistica.on_storage_change,
drop = storageName,
logistica = {
maxItems = numItemsPerSlot,
numSlots = numSlots,
numUpgradeSlots = numUpgradeSlots,
},
allow_metadata_inventory_put = allow_mass_storage_inv_put,
allow_metadata_inventory_take = allow_mass_storage_inv_take,
allow_metadata_inventory_move = allow_mass_storage_inv_move,
on_metadata_inventory_put = on_mass_storage_inv_put,
on_metadata_inventory_take = on_mass_storage_inv_take,
on_metadata_inventory_move = on_mass_storage_inv_move,
on_rightclick = on_mass_storage_right_click,
preserve_metadata = on_mass_storage_preserve_metadata,
stack_max = 1,
}
minetest.register_node(storageName, 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_disabled.png" end
def_disabled.tiles = tiles_disabled
def_disabled.groups = { cracky = 3, choppy = 3, oddly_breakable_by_hand = 2 }
minetest.register_node(storageName.."_disabled", def_disabled)
end
logistica.register_mass_storage("Basic", 8, 512, 4)

View File

@ -8,5 +8,5 @@ dofile(logistica.MODPATH.."/util/util.lua")
dofile(logistica.MODPATH.."/logic/logic.lua")
dofile(logistica.MODPATH.."/tools/tools.lua")
-- api should be kept last
-- api should be below the other files except the registrations
dofile(logistica.MODPATH.."/api/api.lua")

View File

@ -3,7 +3,8 @@ logistica.machines = {}
logistica.controllers = {}
logistica.demanders = {}
logistica.suppliers = {}
logistica.storage = {}
logistica.mass_storage = {}
logistica.item_storage = {}
-- logistica.demand_and_supplier = {}
logistica.tiers = {}
logistica.TIER_ALL = "logistica_all_tiers"
@ -18,51 +19,31 @@ function logistica.get_machine_group(tier)
end
function logistica.is_cable(name)
if logistica.cables[name] then
return true
else
return false
end
return logistica.cables[name] ~= nil
end
function logistica.is_machine(name)
if logistica.machines[name] then
return true
else
return false
end
return logistica.machines[name] ~= nil
end
function logistica.is_demander(name)
if logistica.demanders[name] then
return true
else
return false
end
return logistica.demanders[name] ~= nil
end
function logistica.is_supplier(name)
if logistica.suppliers[name] then
return true
else
return false
end
return logistica.suppliers[name] ~= nil
end
function logistica.is_storage(name)
if logistica.storage[name] then
return true
else
return false
end
function logistica.is_mass_storage(name)
return logistica.mass_storage[name] ~= nil
end
function logistica.is_item_storage(name)
return logistica.item_storage[name] ~= nil
end
function logistica.is_controller(name)
if logistica.controllers[name] then
return true
else
return false
end
return logistica.controllers[name] ~= nil
end
function logistica.get_item_tiers(name)

View File

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

View File

@ -1,8 +1,9 @@
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 STATUS_OK = 0
local CREATE_NETWORK_STATUS_FAIL_OTHER_NETWORK = -1
local CREATE_NETWORK_STATUS_TOO_MANY_NODES = -2
local ADD_STORAGE_MULTIPLE_NETWORKS = -1
local adjecent = {
vector.new( 1, 0, 0),
@ -12,13 +13,25 @@ local adjecent = {
vector.new( 0, -1, 0),
vector.new( 0, 0, -1),
}
local function has_machine(network, id)
if not network then return false end
if network.demanders and network.demanders[id] then
return true
elseif network.suppliers and network.suppliers[id] then
return true
elseif network.mass_storage and network.mass_storage[id] then
return true
else
return false
end
end
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
if has_machine(network, hash) then return network.name end
end
return nil
end
@ -28,7 +41,7 @@ function logistica.get_network_or_nil(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
if has_machine(network, hash) then return network end
end
return nil
end
@ -38,6 +51,7 @@ function logistica.get_network_id_or_nil(pos)
if not network then return nil else return network.controller end
end
----------------------------------------------------------------
-- Network operation functions
----------------------------------------------------------------
@ -50,8 +64,16 @@ local function dumb_remove_from_network(networkName, pos)
network.cables[hashedPos] = nil
return true
end
if network.machines[hashedPos] then
network.machines[hashedPos] = nil
if network.mass_storage[hashedPos] then
network.mass_storage[hashedPos] = nil
return true
end
if network.demanders[hashedPos] then
network.demanders[hashedPos] = nil
return true
end
if network.suppliers[hashedPos] then
network.suppliers[hashedPos] = nil
return true
end
if network.controller == hashedPos then
@ -61,36 +83,73 @@ local function dumb_remove_from_network(networkName, pos)
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")
local function break_logistica_node(pos)
local node = minetest.get_node(pos)
logistica.swap_node(pos, node.name .. "_disabled")
end
--[[ updates the storage cach which holds where items may be found
The cache is in the followiing format:
network.storage_cache = {
itemName = {
storagePositionHash1 = true,
storagePositionHash2 = true,
}
}
]]
function logistica.updateStorageCache(network)
-- this maybe can be improved by doing add/remove operations, but increases complexity
-- for now, do full rescan
network.storage_cache = {}
for storageHash, _ in pairs(network.mass_storage) do
local storagePos = minetest.get_position_from_hash(storageHash)
logistica.load_position(storagePos)
local meta = minetest.get_meta(storagePos)
local inv = meta:get_inventory():get_list("filter")
for _, itemStack in pairs(inv) do
local name = itemStack:get_name()
if not network.storage_cache[name] then network.storage_cache[name] = {} end
network.storage_cache[name][storageHash] = true
end
end
end
local function dumb_add_storage_to_network(networkId, pos)
local network = networks[networkId]
if not network then return false end
local node = minetest.get_node(pos)
local hashedPos = minetest.hash_node_position(pos)
if logistica.is_mass_storage(node.name) then
network.mass_storage[hashedPos] = true
return true
elseif logistica.is_item_storage(node.name) then
network.item_storage[hashedPos] = true
else -- can't dumb-add other things
return false
end
end
local function dumb_add_cable_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
return true
else -- can't dumb-add other things
return false
end
end
local function recursive_scan_for_nodes_for_controller(network, positions, numScanned)
if #positions <= 0 then return CREATE_NETWORK_STATUS_OK end
if #positions <= 0 then return STATUS_OK end
if not numScanned then numScanned = #positions
else numScanned = numScanned + #positions end
@ -114,7 +173,7 @@ local function recursive_scan_for_nodes_for_controller(network, positions, numSc
end
if tiersMatch
and network.controller ~= otherHash
and network.machines[otherHash] == nil
and not has_machine(network, otherHash)
and network.cables[otherHash] == nil then
local otherNode = minetest.get_node(otherPos)
if logistica.is_cable(otherNode.name) then
@ -126,14 +185,12 @@ local function recursive_scan_for_nodes_for_controller(network, positions, numSc
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
elseif logistica.is_mass_storage(otherNode.name) then
network.mass_storage[otherHash] = true
dumb_add_storage_to_network(network.controller, otherPos)
end
end
end -- end inner for loop
@ -142,7 +199,7 @@ local function recursive_scan_for_nodes_for_controller(network, positions, numSc
return recursive_scan_for_nodes_for_controller(network, connections, numScanned)
end
local function add_network(controllerPosition)
local function create_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)
@ -153,18 +210,22 @@ local function add_network(controllerPosition)
meta:set_string("infotext", "Controller of Network: "..networkName)
network.controller = controllerHash
network.name = networkName
network.machines = {}
network.cables = {}
network.demanders = {}
network.suppliers = {}
network.storage = {}
network.mass_storage = {}
network.item_storage = {}
network.storage_cache = {}
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")
break_logistica_node(pos)
elseif status == CREATE_NETWORK_STATUS_TOO_MANY_NODES then
errorMsg = "Controller max nodes limit of "..HARD_NETWORK_NODE_LIMIT.." nodes per network exceeded!"
elseif status == STATUS_OK then
-- controller scan skips updating storage cache, do so now
logistica.updateStorageCache(network)
end
if errorMsg ~= nil then
networks[controllerHash] = nil
@ -183,7 +244,7 @@ local function rescan_network(networkName)
local conHash = network.controller
local controllerPosition = minetest.get_position_from_hash(conHash)
clear_network(networkName)
add_network(controllerPosition)
create_network(controllerPosition)
end
local function find_cable_connections(pos, node)
@ -208,14 +269,44 @@ local function find_cable_connections(pos, node)
return connections
end
local function try_to_add_network(pos)
add_network(pos)
local function try_to_add_network(pos)
create_network(pos)
end
local function find_machine_connections(pos, node)
local function try_to_add_mass_storage_to_network(pos)
local connectedNetworks = {}
for _, adj in pairs(adjecent) do
local otherPos = vector.add(pos, adj)
local otherNetwork = logistica.get_network_id_or_nil(otherPos)
if otherNetwork then
connectedNetworks[otherNetwork] = true
end
end
local firstNetworId = nil
local numNetworks = 0
for k,_ in pairs(connectedNetworks) do
numNetworks = numNetworks + 1
if firstNetworId == nil then firstNetworId = k end
end
if numNetworks <= 0 then return STATUS_OK end -- nothing to connect to
if numNetworks >= 2 then
break_logistica_node(pos) -- swap out storage node for disabled one
minetest.get_meta(pos):set_string("infotext", "ERROR: cannot connect to multiple networks!")
return ADD_STORAGE_MULTIPLE_NETWORKS
end
-- else, we have 1 network, add us to it!
dumb_add_storage_to_network(firstNetworId, pos)
logistica.updateStorageCache(networks[firstNetworId])
return STATUS_OK
end
local function remove_mass_storage_from_network(pos)
local hash = minetest.hash_node_position(pos)
local network = logistica.get_network_or_nil(pos)
if not network then return end
if not network.mass_storage[hash] then return end
network.mass_storage[hash] = nil
end
----------------------------------------------------------------
-- global namespaced functions
----------------------------------------------------------------
@ -237,7 +328,7 @@ function logistica.on_cable_change(pos, oldNode)
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)
dumb_add_cable_to_network(otherNetwork, pos)
end
end
end
@ -264,13 +355,12 @@ function logistica.on_cable_change(pos, oldNode)
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)
break_logistica_node(pos)
meta:set_string("infotext", "ERROR: cannot connect to multiple networks!")
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
@ -278,4 +368,13 @@ function logistica.on_controller_change(pos, oldNode)
else
clear_network(hashPos)
end
end
function logistica.on_storage_change(pos, oldNode)
local placed = (oldNode == nil) -- if oldNode is nil, we placed a new one
if placed == true then
try_to_add_mass_storage_to_network(pos)
else
remove_mass_storage_from_network(pos)
end
end

126
logic/push_pull.lua Normal file
View File

@ -0,0 +1,126 @@
local M_SCAN_POS = "logistica_scanpos"
-- attempts to take 1 item from the targetMeta, rotating over all slots
function logistica.get_item(pullerMeta, targetMeta, listname)
if targetMeta == nil or targetMeta.get_inventory == nil then return nil end
local inv = targetMeta:get_inventory()
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
local startpos = pullerMeta:get_int(M_SCAN_POS) or 0
for i = startpos, startpos + size do
i = (i % size) + 1
local items = inv:get_stack(listname, inv)
if items:get_count() > 0 then
local taken = items:take_item(1)
inv:set_stack(listname, i, items)
pullerMeta:set_int(M_SCAN_POS, i)
return taken
end
end
pullerMeta:set_int("tubelib_startpos", 0)
return nil
end
function logistica.get_specific_item(meta, listname, slotNumber, numItems)
if meta == nil or meta.get_inventory == nil then return nil end
local inv = meta:get_inventory()
if inv:is_empty(listname) then
return nil
end
if numItems == nil then numItems = 1 end
local items = inv:get_stack(listname, slotNumber)
if items:get_count() > 0 then
local taken = items:take_item(numItems)
inv:set_stack(listname, slotNumber, items)
return taken
end
return nil
end
-- Try to put item in list, returns false if failed, true otherwise
function logistica.put_item(meta, listname, item)
if meta == nil or meta.get_inventory == nil then return false end
local inv = meta:get_inventory()
if inv:room_for_item(listname, item) then
inv:add_item(listname, item)
return true
end
return false
end
-- Take the number of items from the given ItemList.
-- Returns nil if the requested number is not available.
function logistica.get_num_items(meta, listname, num)
if meta == nil or meta.get_inventory == nil then return nil end
local inv = meta:get_inventory()
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
for idx = 1, size do
local items = inv:get_stack(listname, idx)
if items:get_count() >= num then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
return taken
end
end
return nil
end
function logistica.get_stack(pullerMeta, targetMeta, listname)
local inv = targetMeta:get_inventory()
local item = logistica.get_item(pullerMeta, targetMeta, listname)
if item and item:get_stack_max() > 1 and inv:contains_item(listname, item) then
-- try to remove a complete stack
item:set_count(math.min(98, item:get_stack_max() - 1))
local taken = inv:remove_item(listname, item)
-- add the already removed
taken:set_count(taken:get_count() + 1)
return taken
end
return item
end
-- Return "full", "loaded", or "empty" depending
-- on the number of fuel stack items.
-- Function only works on fuel inventories with one stacks/99 items
function logistica.fuelstate(meta, listname, item)
if meta == nil or meta.get_inventory == nil then return nil end
local inv = meta:get_inventory()
if inv:is_empty(listname) then
return "empty"
end
local list = inv:get_list(listname)
if #list == 1 and list[1]:get_count() == 99 then
return "full"
else
return "loaded"
end
end
-- Return "full", "loaded", or "empty" depending
-- on the inventory load.
-- Full is returned, when no empty stack is available.
function logistica.get_inv_state(meta, listname)
if meta == nil or meta.get_inventory == nil then return nil end
local inv = meta:get_inventory()
local state
if inv:is_empty(listname) then
state = "empty"
else
local list = inv:get_list(listname)
state = "full"
local num = 0
for i, item in ipairs(list) do
if item:is_empty() then
return "loaded"
end
end
end
return state
end

89
logic/storage.lua Normal file
View File

@ -0,0 +1,89 @@
function logistica.get_mass_storage_max_size(pos)
local node = minetest.get_node(pos)
if not node then return 0 end
local def = minetest.registered_nodes[node.name]
if def and def.logistica and def.logistica.maxItems then
-- TODO: account for upgrades
return def.logistica.maxItems
end
return 0
end
function logistica.get_mass_storage_num_slots(pos)
local node = minetest.get_node(pos)
if not node then return 0 end
local def = minetest.registered_nodes[node.name]
if def and def.logistica and def.logistica.numSlots then
-- TODO: account for upgrades
return def.logistica.numSlots
end
return 0
end
-- try to insert the item into the storage, returning how many items were taken
function logistica.try_to_add_item_to_storage(pos, inputStack, dryRun)
local node = minetest.get_node(pos)
if not logistica.is_mass_storage(node.name) and not logistica.is_item_storage(node.name) then return 0 end
local isMassStorage = string.find(node.name, "mass")
local inv = minetest.get_meta(pos):get_inventory()
if isMassStorage then
local maxItems = logistica.get_mass_storage_max_size(pos)
local numSlots = logistica.get_mass_storage_num_slots(pos)
local indices = {}
for i = 1, numSlots do
local v = inv:get_stack("filter", i)
if v:get_name() == inputStack:get_name() then
table.insert(indices, i)
end
end
local remainingStack = ItemStack(inputStack)
for _, index in ipairs(indices) do
local storageStack = inv:get_stack("storage", index)
local canInsert = logistica.clamp(maxItems - storageStack:get_count(), 0, remainingStack:get_count())
if canInsert > 0 then
local toInsert = ItemStack(inputStack:get_name())
toInsert:set_count(storageStack:get_count() + canInsert)
if not dryRun then
inv:set_stack("storage", index, toInsert)
end
if canInsert >= remainingStack:get_count() then
return inputStack:get_count() -- nothing more to check, return early
else
remainingStack:set_count(remainingStack:get_count() - canInsert)
end
end
end
return inputStack:get_count() - remainingStack:get_count()
else -- it's not mass storage, must be tool storage
if inputStack:get_stack_max() == 1 and inv:room_for_item("main", inputStack) then
-- tool storage only takes individual items
inv:add_item("main", inputStack)
return 1
end
end
return 0
end
-- takes a list of ItemStacks and returns a single string representation
function logistica.inv_to_table(list)
local itemStackStrings = {}
for _,v in pairs(list) do
itemStackStrings:insert(v:to_string())
end
end
function logistica.table_to_inv(string)
end
local LIST_SEPARATOR = "L|L"
-- takes a inventory and returns a single string represetation
function logistica.serliaze_inventory(list)
end
-- takes a inventory and returns a single string represetation
function logistica.deserliaze_inventory(list)
end

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

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: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
textures/logistica_wand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -41,3 +41,42 @@ function logistica.get_network_name_for(pos)
return p1.."-"..p2.."-"..p3
end
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
function logistica.clamp(v, min, max)
if v < min then return min end
if v > max then return max end
return v
end

3
util/ui.lua Normal file
View File

@ -0,0 +1,3 @@
logistica.ui = {}
logistica.ui.background = "bgcolor[#0000]background9[0,0;1,1;logistica_formspec_background.png;true;4]"

View File

@ -3,37 +3,4 @@ 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
dofile(path.."/ui.lua")