diff --git a/api/api.lua b/api/api.lua index 0061d83..1fd8064 100644 --- a/api/api.lua +++ b/api/api.lua @@ -14,3 +14,4 @@ dofile(path.."/lava_furnace_recipe.lua") dofile(path.."/trashcan.lua") dofile(path.."/vaccuum_chest.lua") dofile(path.."/crafter_auto.lua") +dofile(path.."/crafting_supplier.lua") diff --git a/api/crafting_supplier.lua b/api/crafting_supplier.lua new file mode 100644 index 0000000..db88c19 --- /dev/null +++ b/api/crafting_supplier.lua @@ -0,0 +1,196 @@ + +local S = logistica.TRANSLATOR + +local FORMSPEC_NAME = "logistica_craftsup" +local ON_OFF_BUTTON = "on_off_btn" +local INV_MAIN = "main" +local INV_CRAFT = "crf" +local INV_HOUT = "hout" + +local forms = {} + +local function update_craft_output(inv) + local inputList = inv:get_list(INV_CRAFT) + local out, _ = minetest.get_craft_result({ + method = "normal", + width = 3, + items = inputList + }) + inv:set_stack(INV_MAIN, 1, out.item) +end + +local function get_craftsup_formspec(pos) + local posForm = "nodemeta:"..pos.x..","..pos.y..","..pos.z + local isOn = logistica.is_machine_on(pos) + + return "formspec_version[4]" .. + "size[10.5,13]" .. + logistica.ui.background.. + logistica.ui.on_off_btn(isOn, 1.1, 2.6, ON_OFF_BUTTON, S("Enable")).. + "label[0.4,0.5;"..S("Crafts items when requested by Network. Excess stored below.").."]".. + "list["..posForm..";"..INV_CRAFT..";3.4,1.5;3,3;0]".. + "list["..posForm..";"..INV_MAIN..";7.1,2.75;1,1;0]".. + "label[4.6,1.2;Recipe]".. + "label[0.5,5.6;"..S("Excess items, provided as supply. If full\\, excess will be thrown out.").."]".. + "list["..posForm..";main;0.4,5.9;8,1;1]".. + "list[current_player;main;0.4,7.8;8,4;0]".. + "listring["..posForm..";"..INV_MAIN.."]".. + "listring[current_player;main]" +end + +local function show_craftsup_formspec(playerName, pos) + forms[playerName] = {position = pos} + minetest.show_formspec(playerName, FORMSPEC_NAME, get_craftsup_formspec(pos)) +end + +local function on_player_receive_fields(player, formname, fields) + if not player or not player:is_player() then return false end + if formname ~= FORMSPEC_NAME then return false end + local playerName = player:get_player_name() + if not forms[playerName] then return false end + local pos = forms[playerName].position + if minetest.is_protected(pos, playerName) then return true end + + if fields.quit then + forms[playerName] = nil + elseif fields[ON_OFF_BUTTON] then + logistica.toggle_machine_on_off(pos) + show_craftsup_formspec(player:get_player_name(), pos) + end + return true +end + +local function on_craftsup_rightclick(pos, node, clicker, itemstack, pointed_thing) + if not clicker or not clicker:is_player() then return end + if minetest.is_protected(pos, clicker:get_player_name()) then return end + show_craftsup_formspec(clicker:get_player_name(), pos) +end + +local function after_place_craftsup(pos, placer, itemstack) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size(INV_MAIN, 9) + inv:set_size(INV_CRAFT, 9) + inv:set_width(INV_CRAFT, 3) + inv:set_size(INV_HOUT, 9) + logistica.set_node_tooltip_from_state(pos) + logistica.on_supplier_change(pos) +end + +local function allow_craftsup_storage_inv_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then return 0 end + if listname == INV_CRAFT then + local inv = minetest.get_meta(pos):get_inventory() + local st = inv:get_stack(listname, index) + st:add_item(stack) + inv:set_stack(listname, index, st) + update_craft_output(minetest.get_meta(pos):get_inventory()) + logistica.update_cache_at_pos(pos, LOG_CACHE_SUPPLIER) + end + return 0 +end + +local function allow_craftsup_inv_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then return 0 end + if listname == INV_CRAFT then + local inv = minetest.get_meta(pos):get_inventory() + local st = inv:get_stack(listname, index) + st:take_item(stack:get_count()) + inv:set_stack(listname, index, st) + update_craft_output(minetest.get_meta(pos):get_inventory()) + logistica.update_cache_at_pos(pos, LOG_CACHE_SUPPLIER) + return 0 + elseif listname == INV_MAIN then + if index == 1 then return 0 + else return stack:get_count() end + end + return 0 +end + +local function allow_craftsup_inv_move(pos, from_list, from_index, to_list, to_index, count, player) + return 0 +end + +local function on_craftsup_inventory_put(pos, listname, index, stack, player) + update_craft_output(minetest.get_meta(pos):get_inventory()) + logistica.update_cache_at_pos(pos, LOG_CACHE_SUPPLIER) +end + +local function on_craftsup_inventory_take(pos, listname, index, stack, player) + update_craft_output(minetest.get_meta(pos):get_inventory()) + logistica.update_cache_at_pos(pos, LOG_CACHE_SUPPLIER) +end + +local function can_dig_craftsup(pos, player) + local main = minetest.get_meta(pos):get_inventory():get_list(INV_MAIN) or {} + for i = 2, #main do + if not main[i]:is_empty() then return false end + end + return true +end + +---------------------------------------------------------------- +-- Minetest registration +---------------------------------------------------------------- + +minetest.register_on_player_receive_fields(on_player_receive_fields) + +minetest.register_on_leaveplayer(function(objRef, timed_out) + if objRef:is_player() then + forms[objRef:get_player_name()] = nil + end +end) + +---------------------------------------------------------------- +-- Public Registration API +---------------------------------------------------------------- +-- `simpleName` is used for the description and for the name (can contain spaces) +-- `inventorySize` should be 16 at max +function logistica.register_crafting_supplier(desc, name, tiles) + local lname = string.lower(name:gsub(" ", "_")) + local supplier_name = "logistica:"..lname + logistica.craftsups[supplier_name] = true + local grps = {oddly_breakable_by_hand = 3, cracky = 3 } + grps[logistica.TIER_ALL] = 1 + local def = { + description = desc, + drawtype = "normal", + tiles = tiles, + paramtype = "light", + paramtype2 = "facedir", + is_ground_content = false, + groups = grps, + drop = supplier_name, + sounds = logistica.node_sound_metallic(), + after_place_node = after_place_craftsup, + after_destruct = logistica.on_supplier_change, + on_rightclick = on_craftsup_rightclick, + allow_metadata_inventory_put = allow_craftsup_storage_inv_put, + allow_metadata_inventory_take = allow_craftsup_inv_take, + allow_metadata_inventory_move = allow_craftsup_inv_move, + on_metadata_inventory_put = on_craftsup_inventory_put, + on_metadata_inventory_take = on_craftsup_inventory_take, + can_dig = can_dig_craftsup, + logistica = { + on_power = function(pos, power) logistica.set_node_tooltip_from_state(pos, nil, power) end + } + } + + minetest.register_node(supplier_name, def) + + local def_disabled = table.copy(def) + 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 = { oddly_breakable_by_hand = 3, cracky = 3, choppy = 3, not_in_creative_inventory = 1 } + def_disabled.on_construct = nil + def_disabled.after_destruct = nil + def_disabled.on_punch = nil + def_disabled.on_rightclick = nil + def_disabled.on_timer = nil + def_disabled.logistica = nil + + minetest.register_node(supplier_name.."_disabled", def_disabled) + +end diff --git a/logic/access_point_formspec.lua b/logic/access_point_formspec.lua index eca5043..d685313 100644 --- a/logic/access_point_formspec.lua +++ b/logic/access_point_formspec.lua @@ -234,8 +234,8 @@ function logistica.access_point_allow_take(pos, listname, index, _stack, player) -- remove the sometimes manually added count display stack:get_meta():set_string("count_meta", nil) if stackMax > 1 then - local taken = nil - local acceptTaken = function(st) taken = st; return 0 end + local taken = ItemStack("") + local acceptTaken = function(st) taken:add_item(st); return 0 end logistica.take_stack_from_network(stack, network, acceptTaken) if not taken or taken:is_empty() then return 0 end return math.min(taken:get_count(), stackMax) diff --git a/logic/autocrafting_logic.lua b/logic/autocrafting_logic.lua index d5fb2b0..85b5105 100644 --- a/logic/autocrafting_logic.lua +++ b/logic/autocrafting_logic.lua @@ -37,7 +37,8 @@ end -- public functions -- returns true if something was crafted, false if nothing was crafted -function logistica.autocrafting_produce_single_item(inv, recipeList3x3Name, sourceListName, outputListName) +-- optSourceListName is optional: if nil, no checks will be made if enough materials exist +function logistica.autocrafting_produce_single_item(inv, recipeList3x3Name, optSourceListName, outputListName) local recipeList = inv:get_list(recipeList3x3Name) or {} local craftRes = get_combined_crafting_ouputs(recipeList) @@ -48,23 +49,26 @@ function logistica.autocrafting_produce_single_item(inv, recipeList3x3Name, sour for _, st in ipairs(craftRes.extras) do if not inv:room_for_item(outputListName, st) then return false end end - -- check if source has enough materials - local recCounts = count_items(recipeList) - local srcCounts = count_items(inv:get_list(sourceListName) or {}) - for name, count in pairs(recCounts) do - if srcCounts[name] == nil or srcCounts[name] < count then return false end - end - -- remove items from source - for name, _count in pairs(recCounts) do - local count = _count - repeat - local stack = ItemStack(name) - local take = math.min(count, stack:get_stack_max()) - stack:set_count(take) - count = count - take - inv:remove_item(sourceListName, stack) - until (count == 0) + if optSourceListName ~= nil then + -- check if source has enough materials + local recCounts = count_items(recipeList) + local srcCounts = count_items(inv:get_list(optSourceListName) or {}) + for name, count in pairs(recCounts) do + if srcCounts[name] == nil or srcCounts[name] < count then return false end + end + + -- remove items from source + for name, _count in pairs(recCounts) do + local count = _count + repeat + local stack = ItemStack(name) + local take = math.min(count, stack:get_stack_max()) + stack:set_count(take) + count = count - take + inv:remove_item(optSourceListName, stack) + until (count == 0) + end end -- add the output diff --git a/logic/crafting_supplier.lua b/logic/crafting_supplier.lua new file mode 100644 index 0000000..a86eae2 --- /dev/null +++ b/logic/crafting_supplier.lua @@ -0,0 +1,115 @@ +local INV_MAIN = "main" +local INV_CRAFT = "crf" +local INV_HOUT = "hout" + +local function count_items_to_stack(list) + local map = {} + for _, stack in ipairs(list) do + if not stack:is_empty() then + local stName = stack:get_name() + if not map[stName] then map[stName] = 0 end + map[stName] = map[stName] + stack:get_count() + end + end + local items = {} + local i = 0 + for name, count in pairs(map) do + i = i + 1 + local item = ItemStack(name) ; item:set_count(count) + items[i] = item + end + return items +end + +local function consume_from_network(craftItems, times, network) + if times <= 0 then return end + local acceptItem = function (_) return 0 end + for _, itemStack in ipairs(craftItems) do + local consumeStack = ItemStack(itemStack) ; consumeStack:set_count(itemStack:get_count() * times) + logistica.take_stack_from_network(consumeStack, network, acceptItem, true, false, false) + end +end + +-- returns 0 if craftItems could not be taken from network, returns 1 if they could +local function consume_for_craft(craftItems, network) + local itemTaken = ItemStack("") + local acceptItem = function(st) itemTaken:add_item(st) ; return 0 end + for _, itemStack in ipairs(craftItems) do + itemTaken:clear() + logistica.take_stack_from_network(itemStack, network, acceptItem, true, false, true) + if itemTaken:get_count() < itemStack:get_count() then + return 0 + end + end + consume_from_network(craftItems, 1, network) + return 1 +end + +function logistica.take_item_from_crafting_supplier(pos, _takeStack, network, collectorFunc, useMetadata, dryRun) + local takeStack = ItemStack(_takeStack) + local remaining = takeStack:get_count() + local takeStackName = takeStack:get_name() + + -- first check existing supply, ignore the 1st slot (which is for the crafted item) + remaining = logistica.take_item_from_supplier(pos, takeStack, network, collectorFunc, useMetadata, dryRun, 1) + if remaining <= 0 then return 0 end -- we're done + + -- only craft if machine is on + if not logistica.is_machine_on(pos) then return _takeStack:get_count() end + + -- if we still have a number of requested itsm to fulfil, try crafting them + takeStack:set_count(remaining) + local inv = minetest.get_meta(pos):get_inventory() + local craftStack = inv:get_stack(INV_MAIN, 1) + + -- if names are different, we can't craft this request + if inv:is_empty(INV_CRAFT) or craftStack:get_name() ~= takeStack:get_name() then + return remaining + end + + inv:set_list(INV_HOUT, {}) + local numCrafted = 0 + local isEnough = false + repeat + logistica.autocrafting_produce_single_item(inv, INV_CRAFT, nil, INV_HOUT) + -- if we can craft from network + local items = count_items_to_stack(inv:get_list(INV_CRAFT)) + local numCanCraft = consume_for_craft(items, network) + numCrafted = numCrafted + numCanCraft + + isEnough = inv:contains_item(INV_HOUT, takeStack) or numCanCraft == 0 or numCrafted >= 99 + until (isEnough) + + if numCrafted == 0 then return remaining end -- nothing could be crafted + remaining = math.max(0, remaining - numCrafted) + + -- give the item to the collector + local taken = inv:remove_item(INV_HOUT, takeStack) + local leftover = collectorFunc(taken) + + -- now move any extras from the hidden to the main inventory - deleting extras (TODO: maybe drop them) + local extraNotTaken = 0 + local toInsert = {} + for i, st in ipairs(inv:get_list(INV_HOUT)) do + if st:get_name() == takeStackName then + extraNotTaken = extraNotTaken + st:get_count() + else + table.insert(toInsert, st) + end + end + taken:set_count(leftover + extraNotTaken) + + if not taken:is_empty() then + local main = inv:get_list(INV_MAIN) or {} + for i = 2, #main do + taken = main[i]:add_item(taken) + end + inv:set_list(INV_MAIN, main) + end + + for _, insertStack in ipairs(toInsert) do + inv:add_item(INV_MAIN, insertStack) + end + + return remaining +end diff --git a/logic/logic.lua b/logic/logic.lua index b789694..b01bfd7 100644 --- a/logic/logic.lua +++ b/logic/logic.lua @@ -17,3 +17,4 @@ dofile(path.."/lava_furnace_guide_formspec.lua") dofile(path.."/trashcan.lua") dofile(path.."/vaccuum_chest.lua") dofile(path.."/autocrafting_logic.lua") +dofile(path.."/crafting_supplier.lua") diff --git a/logic/supplier.lua b/logic/supplier.lua index 8a4aeff..3e7d747 100644 --- a/logic/supplier.lua +++ b/logic/supplier.lua @@ -38,8 +38,9 @@ function logistica.put_item_in_supplier(pos, stack) return leftover end --- returns the leftover stack that still needs to be fulfilled (or empty stack if entire request was fulfilled) -function logistica.take_item_from_supplier(supplierPos, stackToTake, network, collectorFunc, useMetadata, dryRun) +-- returns the number of items remaining to be fulfilled, or 0 if entire request was fulfilled +function logistica.take_item_from_supplier(supplierPos, stackToTake, network, collectorFunc, useMetadata, dryRun, optIgnorePosition) + if optIgnorePosition == nil then optIgnorePosition = -1 end local eq = function(s1, s2) return s1:get_name() == s2:get_name() end if stackToTake:get_stack_max() == 1 and useMetadata then eq = function(s1, s2) return s1:equals(s2) end end logistica.load_position(supplierPos) @@ -48,7 +49,7 @@ function logistica.take_item_from_supplier(supplierPos, stackToTake, network, co local supplierInv = minetest.get_meta(supplierPos):get_inventory() local supplyList = supplierInv:get_list(META_SUPPLIER_LIST) for i, supplyStack in ipairs(supplyList) do - if eq(supplyStack, stackToTake) then + if i ~= optIgnorePosition and eq(supplyStack, stackToTake) then local supplyCount = supplyStack:get_count() if supplyCount >= remaining then -- enough to fulfil requested local toSend = ItemStack(supplyStack) ; toSend:set_count(remaining) diff --git a/registration/machines_api_reg.lua b/registration/machines_api_reg.lua index dfd995d..2e631f9 100644 --- a/registration/machines_api_reg.lua +++ b/registration/machines_api_reg.lua @@ -80,6 +80,16 @@ logistica.register_controller("simple_controller", { node_box = { type = "regular"}, }) +-------------------------------- +-- Crafting Supplier +-------------------------------- + +logistica.register_crafting_supplier("Crafting Supplier", "crafting_supplier", { + "logistica_crafting_supplier_top.png", + "logistica_crafting_supplier_bottom.png", + "logistica_crafting_supplier_side.png", +}) + -------------------------------- -- Network Importer -------------------------------- diff --git a/registration/node_recipes.lua b/registration/node_recipes.lua index a98c93e..dd6511c 100644 --- a/registration/node_recipes.lua +++ b/registration/node_recipes.lua @@ -116,3 +116,12 @@ minetest.register_craft({ {L("silverin_plate"), "", L("silverin_plate")}, } }) + +minetest.register_craft({ + output = L("crafting_supplier"), + recipe = { + {L("silverin_plate"), "default:chest", L("silverin_plate")}, + {L("silverin_circuit"), L("photonizer"), L("silverin_circuit")}, + {L("silverin_plate"), L("optic_cable"), L("silverin_plate")}, + } +}) diff --git a/textures/logistica_crafting_supplier_bottom.png b/textures/logistica_crafting_supplier_bottom.png new file mode 100644 index 0000000..994bd7e Binary files /dev/null and b/textures/logistica_crafting_supplier_bottom.png differ diff --git a/textures/logistica_crafting_supplier_side.png b/textures/logistica_crafting_supplier_side.png new file mode 100644 index 0000000..5f76e83 Binary files /dev/null and b/textures/logistica_crafting_supplier_side.png differ diff --git a/textures/logistica_crafting_supplier_top.png b/textures/logistica_crafting_supplier_top.png new file mode 100644 index 0000000..c94f475 Binary files /dev/null and b/textures/logistica_crafting_supplier_top.png differ