diff --git a/README.md b/README.md index 6f7cd3f..fce9630 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +# 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. \ No newline at end of file diff --git a/api/api.lua b/api/api.lua index 5f56fe3..debf5e8 100644 --- a/api/api.lua +++ b/api/api.lua @@ -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") diff --git a/api/cables.lua b/api/cables.lua index 5a677b6..cda0e97 100644 --- a/api/cables.lua +++ b/api/cables.lua @@ -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) - - diff --git a/api/controller.lua b/api/controller.lua index 5dff78c..1e31030 100644 --- a/api/controller.lua +++ b/api/controller.lua @@ -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(), diff --git a/api/mass_storage.lua b/api/mass_storage.lua new file mode 100644 index 0000000..3e29992 --- /dev/null +++ b/api/mass_storage.lua @@ -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) diff --git a/init.lua b/init.lua index 98a9e34..a6e4db1 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/logic/groups.lua b/logic/groups.lua index d8284bd..6845ec5 100644 --- a/logic/groups.lua +++ b/logic/groups.lua @@ -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) diff --git a/logic/logic.lua b/logic/logic.lua index 6ba8922..773630d 100644 --- a/logic/logic.lua +++ b/logic/logic.lua @@ -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") diff --git a/logic/network_logic.lua b/logic/network_logic.lua index 3c2b14b..8760e10 100644 --- a/logic/network_logic.lua +++ b/logic/network_logic.lua @@ -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 \ No newline at end of file diff --git a/logic/push_pull.lua b/logic/push_pull.lua new file mode 100644 index 0000000..c070e33 --- /dev/null +++ b/logic/push_pull.lua @@ -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 diff --git a/logic/storage.lua b/logic/storage.lua new file mode 100644 index 0000000..934892c --- /dev/null +++ b/logic/storage.lua @@ -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 diff --git a/mod.conf b/mod.conf index 9941a05..a97977b 100644 --- a/mod.conf +++ b/mod.conf @@ -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 diff --git a/textures/logistica_basic_mass_storage.png b/textures/logistica_basic_mass_storage.png new file mode 100644 index 0000000..3e5029f Binary files /dev/null and b/textures/logistica_basic_mass_storage.png differ diff --git a/textures/logistica_deep_storage_eight.png b/textures/logistica_deep_storage_eight.png new file mode 100644 index 0000000..57f2f91 Binary files /dev/null and b/textures/logistica_deep_storage_eight.png differ diff --git a/textures/logistica_deep_storage_sixteen.png b/textures/logistica_deep_storage_sixteen.png new file mode 100644 index 0000000..45d3559 Binary files /dev/null and b/textures/logistica_deep_storage_sixteen.png differ diff --git a/textures/logistica_controller_disabled.png b/textures/logistica_disabled.png similarity index 100% rename from textures/logistica_controller_disabled.png rename to textures/logistica_disabled.png diff --git a/textures/logistica_formspec_background.png b/textures/logistica_formspec_background.png new file mode 100644 index 0000000..3c339f6 Binary files /dev/null and b/textures/logistica_formspec_background.png differ diff --git a/textures/logistica_icon_filter.png b/textures/logistica_icon_filter.png new file mode 100644 index 0000000..04672bb Binary files /dev/null and b/textures/logistica_icon_filter.png differ diff --git a/textures/logistica_icon_input.png b/textures/logistica_icon_input.png new file mode 100644 index 0000000..c5a598e Binary files /dev/null and b/textures/logistica_icon_input.png differ diff --git a/textures/logistica_icon_mass_storage.png b/textures/logistica_icon_mass_storage.png new file mode 100644 index 0000000..90e282a Binary files /dev/null and b/textures/logistica_icon_mass_storage.png differ diff --git a/textures/logistica_icon_storage.png b/textures/logistica_icon_storage.png new file mode 100644 index 0000000..8d3de44 Binary files /dev/null and b/textures/logistica_icon_storage.png differ diff --git a/textures/logistica_icon_upgrade.png b/textures/logistica_icon_upgrade.png new file mode 100644 index 0000000..7d3022e Binary files /dev/null and b/textures/logistica_icon_upgrade.png differ diff --git a/textures/logistica_wand.png b/textures/logistica_wand.png new file mode 100644 index 0000000..22d5d22 Binary files /dev/null and b/textures/logistica_wand.png differ diff --git a/util/common.lua b/util/common.lua index c0aed65..3d01c6f 100644 --- a/util/common.lua +++ b/util/common.lua @@ -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 \ No newline at end of file diff --git a/util/ui.lua b/util/ui.lua new file mode 100644 index 0000000..2f8ff8f --- /dev/null +++ b/util/ui.lua @@ -0,0 +1,3 @@ +logistica.ui = {} + +logistica.ui.background = "bgcolor[#0000]background9[0,0;1,1;logistica_formspec_background.png;true;4]" \ No newline at end of file diff --git a/util/util.lua b/util/util.lua index 45bbe64..e00756d 100644 --- a/util/util.lua +++ b/util/util.lua @@ -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")