diff --git a/bags.lua b/bags.lua index 6f07314..75ea13b 100644 --- a/bags.lua +++ b/bags.lua @@ -9,18 +9,19 @@ local F = minetest.formspec_escape unified_inventory.register_page("bags", { get_formspec = function(player) local player_name = player:get_player_name() - local formspec = "background[0.06,0.99;7.92,7.52;ui_bags_main_form.png]" - formspec = formspec.."label[0,0;"..F(S("Bags")).."]" - formspec = formspec.."button[0,2;2,0.5;bag1;"..F(S("Bag @1", 1)).."]" - formspec = formspec.."button[2,2;2,0.5;bag2;"..F(S("Bag @1", 2)).."]" - formspec = formspec.."button[4,2;2,0.5;bag3;"..F(S("Bag @1", 3)).."]" - formspec = formspec.."button[6,2;2,0.5;bag4;"..F(S("Bag @1", 4)).."]" - formspec = formspec.."listcolors[#00000000;#00000000]" - formspec = formspec.."list[detached:"..F(player_name).."_bags;bag1;0.5,1;1,1;]" - formspec = formspec.."list[detached:"..F(player_name).."_bags;bag2;2.5,1;1,1;]" - formspec = formspec.."list[detached:"..F(player_name).."_bags;bag3;4.5,1;1,1;]" - formspec = formspec.."list[detached:"..F(player_name).."_bags;bag4;6.5,1;1,1;]" - return {formspec=formspec} + return { formspec = table.concat({ + "background[0.06,0.99;7.92,7.52;ui_bags_main_form.png]", + "label[0,0;" .. F(S("Bags")) .. "]", + "button[0,2;2,0.5;bag1;" .. F(S("Bag @1", 1)) .. "]", + "button[2,2;2,0.5;bag2;" .. F(S("Bag @1", 2)) .. "]", + "button[4,2;2,0.5;bag3;" .. F(S("Bag @1", 3)) .. "]", + "button[6,2;2,0.5;bag4;" .. F(S("Bag @1", 4)) .. "]", + "listcolors[#00000000;#00000000]", + "list[detached:" .. F(player_name) .. "_bags;bag1;0.5,1;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag2;2.5,1;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag3;4.5,1;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag4;6.5,1;1,1;]" + }) } end, }) @@ -38,37 +39,39 @@ local function get_player_bag_stack(player, i) }):get_stack("bag" .. i, 1) end -for i = 1, 4 do - local bi = i - unified_inventory.register_page("bag"..bi, { +for bag_i = 1, 4 do + unified_inventory.register_page("bag" .. bag_i, { get_formspec = function(player) - local stack = get_player_bag_stack(player, bi) + local stack = get_player_bag_stack(player, bag_i) local image = stack:get_definition().inventory_image - local formspec = ("image[7,0;1,1;"..image.."]" - .."label[0,0;"..F(S("Bag @1", bi)).."]" - .."listcolors[#00000000;#00000000]" - .."list[current_player;bag"..bi.."contents;0,1;8,3;]" - .."listring[current_name;bag"..bi.."contents]" - .."listring[current_player;main]") + local fs = { + "image[7,0;1,1;" .. image .. "]", + "label[0,0;" .. F(S("Bag @1", bag_i)) .. "]", + "listcolors[#00000000;#00000000]", + "list[current_player;bag" .. bag_i .. "contents;0,1;8,3;]", + "listring[current_name;bag" .. bag_i .. "contents]", + "listring[current_player;main]" + } local slots = stack:get_definition().groups.bagslots if slots == 8 then - formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_sm_form.png]" + fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_sm_form.png]" elseif slots == 16 then - formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_med_form.png]" + fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_med_form.png]" elseif slots == 24 then - formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_lg_form.png]" + fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_lg_form.png]" end local player_name = player:get_player_name() -- For if statement. - if unified_inventory.trash_enabled or unified_inventory.is_creative(player_name) or minetest.get_player_privs(player_name).give then - formspec = (formspec.."background[6.06,0;0.92,0.92;ui_bags_trash.png]" - .."list[detached:trash;main;6,0.1;1,1;]") + if unified_inventory.trash_enabled + or unified_inventory.is_creative(player_name) + or minetest.get_player_privs(player_name).give then + fs[#fs + 1] = "background[6.06,0;0.92,0.92;ui_bags_trash.png]" + .. "list[detached:trash;main;6,0.1;1,1;]" end local inv = player:get_inventory() for i = 1, 4 do local def = get_player_bag_stack(player, i):get_definition() - local button if def.groups.bagslots then - local list_name = "bag"..i.."contents" + local list_name = "bag" .. i .. "contents" local size = inv:get_size(list_name) local used = 0 for si = 1, size do @@ -78,14 +81,12 @@ for i = 1, 4 do end end local img = def.inventory_image - local label = F(S("Bag @1", i)).."\n"..used.."/"..size - button = "image_button["..(i+1)..",0;1,1;"..img..";bag"..i..";"..label.."]" - else - button = "" + local label = F(S("Bag @1", i)) .. "\n" .. used .. "/" .. size + fs[#fs + 1] = string.format("image_button[%i,0;1,1;%s;bag%i;%s]", + i + 1, img, i, label) end - formspec = formspec..button end - return {formspec=formspec} + return { formspec = table.concat(fs) } end, }) end @@ -95,12 +96,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) return end for i = 1, 4 do - if fields["bag"..i] then + if fields["bag" .. i] then local stack = get_player_bag_stack(player, i) if not stack:get_definition().groups.bagslots then return end - unified_inventory.set_inventory_formspec(player, "bag"..i) + unified_inventory.set_inventory_formspec(player, "bag" .. i) return end end @@ -110,7 +111,7 @@ local function save_bags_metadata(player, bags_inv) local is_empty = true local bags = {} for i = 1, 4 do - local bag = "bag"..i + local bag = "bag" .. i if not bags_inv:is_empty(bag) then -- Stack limit is 1, otherwise use stack:to_string() bags[i] = bags_inv:get_stack(bag, 1):get_name() @@ -133,7 +134,7 @@ local function load_bags_metadata(player, bags_inv) if not bags_meta then -- Backwards compatiblity for i = 1, 4 do - local bag = "bag"..i + local bag = "bag" .. i if not player_inv:is_empty(bag) then -- Stack limit is 1, otherwise use stack:to_string() bags[i] = player_inv:get_stack(bag, 1):get_name() @@ -143,7 +144,7 @@ local function load_bags_metadata(player, bags_inv) end -- Fill detached slots for i = 1, 4 do - local bag = "bag"..i + local bag = "bag" .. i bags_inv:set_size(bag, 1) bags_inv:set_stack(bag, 1, bags[i] or "") end @@ -155,7 +156,7 @@ local function load_bags_metadata(player, bags_inv) -- Clean up deprecated garbage after saving for i = 1, 4 do - local bag = "bag"..i + local bag = "bag" .. i player_inv:set_size(bag, 0) end end @@ -163,9 +164,9 @@ end minetest.register_on_joinplayer(function(player) local player_inv = player:get_inventory() local player_name = player:get_player_name() - local bags_inv = minetest.create_detached_inventory(player_name.."_bags",{ + local bags_inv = minetest.create_detached_inventory(player_name .. "_bags",{ on_put = function(inv, listname, index, stack, player) - player:get_inventory():set_size(listname.."contents", + player:get_inventory():set_size(listname .. "contents", stack:get_definition().groups.bagslots) save_bags_metadata(player, inv) end, @@ -175,14 +176,14 @@ minetest.register_on_joinplayer(function(player) return 0 end local player_inv = player:get_inventory() - local old_slots = player_inv:get_size(listname.."contents") + local old_slots = player_inv:get_size(listname .. "contents") if new_slots >= old_slots then return 1 end -- using a smaller bag, make sure it fits - local old_list = player_inv:get_list(listname.."contents") + local old_list = player_inv:get_list(listname .. "contents") local new_list = {} local slots_used = 0 local use_new_list = false @@ -196,7 +197,7 @@ minetest.register_on_joinplayer(function(player) end if new_slots >= slots_used then if use_new_list then - player_inv:set_list(listname.."contents", new_list) + player_inv:set_list(listname .. "contents", new_list) end return 1 end @@ -204,13 +205,13 @@ minetest.register_on_joinplayer(function(player) return 0 end, allow_take = function(inv, listname, index, stack, player) - if player:get_inventory():is_empty(listname.."contents") then + if player:get_inventory():is_empty(listname .. "contents") then return stack:get_count() end return 0 end, on_take = function(inv, listname, index, stack, player) - player:get_inventory():set_size(listname.."contents", 0) + player:get_inventory():set_size(listname .. "contents", 0) save_bags_metadata(player, inv) end, allow_move = function() diff --git a/doc/mod_api.txt b/doc/mod_api.txt new file mode 100644 index 0000000..c0be129 --- /dev/null +++ b/doc/mod_api.txt @@ -0,0 +1,95 @@ +unified_inventory API +===================== + +This file provides information about the API of unified_inventory. + + +Misc functions +-------------- +Grouped by use-case, afterwards sorted alphabetically. + +* `unified_inventory.is_creative(name)` + * Checks whether creative is enabled or the player has `creative` + + +Pages +----- + +Register a new page: The callback inside this function is called on user input. + + unified_inventory.register_page("pagename", { + get_formspec = function(player) + -- ^ `player` is an `ObjectRef` + -- Compute the formspec string here + return { + formspec = "button[2,2;2,1;mybutton;Press me]", + -- ^ Final form of the formspec to display + draw_inventory = false, -- default `true` + -- ^ Optional. Hides the player's `main` inventory list + draw_item_list = false, -- default `true` + -- ^ Optional. Hides the item list on the right side + formspec_prepend = false, -- default `false` + -- ^ Optional. When `false`: Disables the formspec prepend + } + end, + }) + + +Buttons +------- + +Register a new button for the bottom row: + + unified_inventory.register_button("skins", { + type = "image", + image = "skins_skin_button.png", + tooltip = "Skins", + hide_lite = true + -- ^ Button is hidden when following two conditions are met: + -- Configuration line `unified_inventory_lite = true` + -- Player does not have the privilege `ui_full` + }) + + + +Crafting +-------- + +The code blocks below document each possible parameter using exemplary values. + +Provide information to display custom craft types: + + unified_inventory.register_craft_type("mytype", { + -- ^ Unique identifier for `register_craft` + description = "Sample Craft", + -- ^ Text shown below the crafting arrow + icon = "dummy.png", + -- ^ Image shown above the crafting arrow + width = 3, + height = 3, + -- ^ Maximal input dimensions of the recipes + dynamic_display_size = function(craft) + -- ^ `craft` is the definition from `register_craft` + return { + width = 2, + height = 3 + } + end, + -- ^ Optional callback to change the displayed recipe size + uses_crafting_grid = true, + }) + +Register a non-standard craft recipe: + + unified_inventory.register_craft({ + output = "default:foobar", + type = "mytype", + -- ^ Standard craft type or custom (see `register_craft_type`) + items = { + { "default:foo" }, + { "default:bar" } + }, + width = 3, + -- ^ Same as `minetest.register_recipe` + }) + diff --git a/group.lua b/group.lua index 23e2587..c7e09be 100644 --- a/group.lua +++ b/group.lua @@ -2,19 +2,21 @@ local S = unified_inventory.gettext function unified_inventory.canonical_item_spec_matcher(spec) local specname = ItemStack(spec):get_name() - if specname:sub(1, 6) == "group:" then - local group_names = specname:sub(7):split(",") + if specname:sub(1, 6) ~= "group:" then return function (itemname) - local itemdef = minetest.registered_items[itemname] - for _, group_name in ipairs(group_names) do - if (itemdef.groups[group_name] or 0) == 0 then - return false - end - end - return true + return itemname == specname end - else - return function (itemname) return itemname == specname end + end + + local group_names = specname:sub(7):split(",") + return function (itemname) + local itemdef = minetest.registered_items[itemname] + for _, group_name in ipairs(group_names) do + if (itemdef.groups[group_name] or 0) == 0 then + return false + end + end + return true end end @@ -25,23 +27,11 @@ end function unified_inventory.extract_groupnames(groupname) local specname = ItemStack(groupname):get_name() - if specname:sub(1, 6) == "group:" then - local group_names = specname:sub(7):split(",") - if #group_names == 1 then - return group_names[1], 1 - end - local s = "" - for g=1,#group_names do - if g > 1 then - -- List connector - s = s .. S(" and ") - end - s = s .. group_names[g] - end - return s, #group_names - else + if specname:sub(1, 6) ~= "group:" then return nil, 0 end + local group_names = specname:sub(7):split(",") + return table.concat(group_names, S(" and ")), #group_names end unified_inventory.registered_group_items = { diff --git a/internal.lua b/internal.lua index fb1b232..7cd27f0 100644 --- a/internal.lua +++ b/internal.lua @@ -58,15 +58,20 @@ function unified_inventory.get_formspec(player, page) unified_inventory.current_page[player_name] = page local pagedef = unified_inventory.pages[page] + if not pagedef then + return "" -- Invalid page name + end + local formspec = { "size[14,10]", + pagedef.formspec_prepend and "" or "no_prepend[]", "background[-0.19,-0.25;14.4,10.75;ui_form_bg.png]" -- Background } - local n = 3 + local n = 4 if draw_lite_mode then formspec[1] = "size[11,7.7]" - formspec[2] = "background[-0.19,-0.2;11.4,8.4;ui_form_bg.png]" + formspec[3] = "background[-0.19,-0.2;11.4,8.4;ui_form_bg.png]" end if unified_inventory.is_creative(player_name) @@ -75,11 +80,6 @@ function unified_inventory.get_formspec(player, page) n = n+1 end - -- Current page - if not unified_inventory.pages[page] then - return "" -- Invalid page name - end - local perplayer_formspec = unified_inventory.get_per_player_formspec(player_name) local fsdata = pagedef.get_formspec(player, perplayer_formspec) diff --git a/register.lua b/register.lua index dd2b3cc..6b4454c 100644 --- a/register.lua +++ b/register.lua @@ -266,65 +266,77 @@ unified_inventory.register_page("craftguide", { local player_name = player:get_player_name() local player_privs = minetest.get_player_privs(player_name) - local formspec = "" - formspec = formspec.."background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]" - formspec = formspec.."label[0,"..formheadery..";" .. F(S("Crafting Guide")) .. "]" - formspec = formspec.."listcolors[#00000000;#00000000]" + local fs = { + "background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]", + "label[0,"..formheadery..";" .. F(S("Crafting Guide")) .. "]", + "listcolors[#00000000;#00000000]" + } local item_name = unified_inventory.current_item[player_name] - if not item_name then return {formspec=formspec} end + if not item_name then + return { formspec = table.concat(fs) } + end + local item_name_shown - if minetest.registered_items[item_name] and minetest.registered_items[item_name].description then - item_name_shown = string.format(S("%s (%s)"), minetest.registered_items[item_name].description, item_name) + if minetest.registered_items[item_name] + and minetest.registered_items[item_name].description then + item_name_shown = string.format(S("%s (%s)"), + minetest.registered_items[item_name].description, item_name) else item_name_shown = item_name end local dir = unified_inventory.current_craft_direction[player_name] - local rdir - if dir == "recipe" then rdir = "usage" end - if dir == "usage" then rdir = "recipe" end + local rdir = dir == "recipe" and "usage" or "recipe" + local crafts = unified_inventory.crafts_for[dir][item_name] local alternate = unified_inventory.alternate[player_name] local alternates, craft - if crafts ~= nil and #crafts > 0 then + if crafts and #crafts > 0 then alternates = #crafts craft = crafts[alternate] end - local has_creative = player_privs.give or player_privs.creative or - minetest.settings:get_bool("creative_mode") + local has_give = player_privs.give or unified_inventory.is_creative(player_name) - formspec = formspec.."background[0.5,"..(formspecy + 0.2)..";8,3;ui_craftguide_form.png]" - formspec = formspec.."textarea["..craftresultx..","..craftresulty - ..";10,1;;"..F(role_text[dir])..": "..item_name_shown..";]" - formspec = formspec..stack_image_button(0, formspecy, 1.1, 1.1, "item_button_" - .. rdir .. "_", ItemStack(item_name)) + fs[#fs + 1] = "background[0.5,"..(formspecy + 0.2)..";8,3;ui_craftguide_form.png]" + fs[#fs + 1] = string.format("textarea[%f,%f;10,1;;%s: %s;]", + craftresultx, craftresulty, F(role_text[dir]), item_name_shown) + fs[#fs + 1] = stack_image_button(0, formspecy, 1.1, 1.1, + "item_button_" .. rdir .. "_", ItemStack(item_name)) if not craft then - formspec = formspec.."label[5.5,"..(formspecy + 2.35)..";" - ..F(no_recipe_text[dir]).."]" + -- No craft recipes available for this item. + fs[#fs + 1] = "label[5.5,"..(formspecy + 2.35)..";" + .. F(no_recipe_text[dir]) .. "]" local no_pos = dir == "recipe" and 4.5 or 6.5 local item_pos = dir == "recipe" and 6.5 or 4.5 - formspec = formspec.."image["..no_pos..","..formspecy..";1.1,1.1;ui_no.png]" - formspec = formspec..stack_image_button(item_pos, formspecy, 1.1, 1.1, "item_button_" - ..other_dir[dir].."_", ItemStack(item_name)) - if has_creative then - formspec = formspec.."label[0,"..(formspecy + 2.10)..";" .. F(S("Give me:")) .. "]" - .."button[0, "..(formspecy + 2.7)..";0.6,0.5;craftguide_giveme_1;1]" - .."button[0.6,"..(formspecy + 2.7)..";0.7,0.5;craftguide_giveme_10;10]" - .."button[1.3,"..(formspecy + 2.7)..";0.8,0.5;craftguide_giveme_99;99]" + fs[#fs + 1] = "image["..no_pos..","..formspecy..";1.1,1.1;ui_no.png]" + fs[#fs + 1] = stack_image_button(item_pos, formspecy, 1.1, 1.1, + "item_button_" .. other_dir[dir] .. "_", ItemStack(item_name)) + if has_give then + fs[#fs + 1] = "label[0," .. (formspecy + 2.10) .. ";" .. F(S("Give me:")) .. "]" + .. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]" + .. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]" + .. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]" end - return {formspec = formspec} + return { formspec = table.concat(fs) } end local craft_type = unified_inventory.registered_craft_types[craft.type] or unified_inventory.craft_type_defaults(craft.type, {}) if craft_type.icon then - formspec = formspec..string.format(" image[%f,%f;%f,%f;%s]",5.7,(formspecy + 0.05),0.5,0.5,craft_type.icon) + fs[#fs + 1] = string.format("image[%f,%f;%f,%f;%s]", + 5.7, (formspecy + 0.05), 0.5, 0.5, craft_type.icon) end - formspec = formspec.."label[5.5,"..(formspecy + 1)..";" .. F(craft_type.description).."]" - formspec = formspec..stack_image_button(6.5, formspecy, 1.1, 1.1, "item_button_usage_", ItemStack(craft.output)) - local display_size = craft_type.dynamic_display_size and craft_type.dynamic_display_size(craft) or { width = craft_type.width, height = craft_type.height } - local craft_width = craft_type.get_shaped_craft_width and craft_type.get_shaped_craft_width(craft) or display_size.width + fs[#fs + 1] = "label[5.5,"..(formspecy + 1)..";" .. F(craft_type.description).."]" + fs[#fs + 1] = stack_image_button(6.5, formspecy, 1.1, 1.1, + "item_button_usage_", ItemStack(craft.output)) + + local display_size = craft_type.dynamic_display_size + and craft_type.dynamic_display_size(craft) + or { width = craft_type.width, height = craft_type.height } + local craft_width = craft_type.get_shaped_craft_width + and craft_type.get_shaped_craft_width(craft) + or display_size.width -- This keeps recipes aligned to the right, -- so that they're close to the arrow. @@ -359,63 +371,67 @@ unified_inventory.register_page("craftguide", { local xof = (fx-1) * of + of local yof = (y-1) * of + 1 if item then - formspec = formspec..stack_image_button( + fs[#fs + 1] = stack_image_button( xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h, "item_button_recipe_", ItemStack(item)) else -- Fake buttons just to make grid - formspec = formspec.."image_button[" - ..tostring(xoffset - xof)..","..tostring(formspecy - 1 + yof) - ..";"..bsize_w..","..bsize_h..";ui_blank_image.png;;]" + fs[#fs + 1] = string.format("image_button[%f,%f;%f,%f;ui_blank_image.png;;]", + xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h) end end end else -- Error - formspec = formspec.."label[" - ..tostring(2)..","..tostring(formspecy) - ..";"..F(S("This recipe is too\nlarge to be displayed.")).."]" + fs[#fs + 1] = string.format("label[2,%f;%s]", + formspecy, F(S("This recipe is too\nlarge to be displayed."))) end if craft_type.uses_crafting_grid and display_size.width <= 3 then - formspec = formspec.."label[0,"..(formspecy + 0.9)..";" .. F(S("To craft grid:")) .. "]" - .."button[0, "..(formspecy + 1.5)..";0.6,0.5;craftguide_craft_1;1]" - .."button[0.6,"..(formspecy + 1.5)..";0.7,0.5;craftguide_craft_10;10]" - .."button[1.3,"..(formspecy + 1.5)..";0.8,0.5;craftguide_craft_max;" .. F(S("All")) .. "]" + fs[#fs + 1] = "label[0," .. (formspecy + 0.9) .. ";" .. F(S("To craft grid:")) .. "]" + .. "button[0, " .. (formspecy + 1.5) .. ";0.6,0.5;craftguide_craft_1;1]" + .. "button[0.6," .. (formspecy + 1.5) .. ";0.7,0.5;craftguide_craft_10;10]" + .. "button[1.3," .. (formspecy + 1.5) .. ";0.8,0.5;craftguide_craft_max;" .. F(S("All")) .. "]" end - if has_creative then - formspec = formspec.."label[0,"..(formspecy + 2.1)..";" .. F(S("Give me:")) .. "]" - .."button[0, "..(formspecy + 2.7)..";0.6,0.5;craftguide_giveme_1;1]" - .."button[0.6,"..(formspecy + 2.7)..";0.7,0.5;craftguide_giveme_10;10]" - .."button[1.3,"..(formspecy + 2.7)..";0.8,0.5;craftguide_giveme_99;99]" + if has_give then + fs[#fs + 1] = "label[0," .. (formspecy + 2.1) .. ";" .. F(S("Give me:")) .. "]" + .. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]" + .. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]" + .. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]" end if alternates and alternates > 1 then - formspec = formspec.."label[5.5,"..(formspecy + 1.6)..";" - ..string.format(F(recipe_text[dir]), alternate, alternates).."]" - .."image_button[5.5,"..(formspecy + 2)..";1,1;ui_left_icon.png;alternate_prev;]" - .."image_button[6.5,"..(formspecy + 2)..";1,1;ui_right_icon.png;alternate;]" - .."tooltip[alternate_prev;"..F(prev_alt_text[dir]).."]" - .."tooltip[alternate;"..F(next_alt_text[dir]).."]" + fs[#fs + 1] = "label[5.5," .. (formspecy + 1.6) .. ";" + .. string.format(F(recipe_text[dir]), alternate, alternates) .. "]" + .. "image_button[5.5," .. (formspecy + 2) .. ";1,1;ui_left_icon.png;alternate_prev;]" + .. "image_button[6.5," .. (formspecy + 2) .. ";1,1;ui_right_icon.png;alternate;]" + .. "tooltip[alternate_prev;" .. F(prev_alt_text[dir]) .. "]" + .. "tooltip[alternate;" .. F(next_alt_text[dir]) .. "]" end - return {formspec = formspec} + return { formspec = table.concat(fs) } end, }) local function craftguide_giveme(player, formname, fields) + local player_name = player:get_player_name() + local player_privs = minetest.get_player_privs(player_name) + if not player_privs.give and + not unified_inventory.is_creative(player_name) then + minetest.log("action", "[unified_inventory] Denied give action to player " .. + player_name) + return + end + local amount for k, v in pairs(fields) do amount = k:match("craftguide_giveme_(.*)") if amount then break end end - if not amount then return end - amount = tonumber(amount) + amount = tonumber(amount) or 0 if amount == 0 then return end - local player_name = player:get_player_name() - local output = unified_inventory.current_item[player_name] if (not output) or (output == "") then return end @@ -424,78 +440,63 @@ local function craftguide_giveme(player, formname, fields) player_inv:add_item("main", {name = output, count = amount}) end --- tells if an item can be moved and returns an index if so -local function item_fits(player_inv, craft_item, needed_item) - local need_group = string.sub(needed_item, 1, 6) == "group:" - if need_group then - need_group = string.sub(needed_item, 7) +-- Takes any stack from "main" where the `amount` of `needed_item` may fit +-- into the given crafting stack (`craft_item`) +local function craftguide_move_stacks(inv, craft_item, needed_item, amount) + if craft_item:get_count() >= amount then + return end - if craft_item - and not craft_item:is_empty() then - local ciname = craft_item:get_name() - -- abort if the item there isn't usable - if ciname ~= needed_item - and not need_group then - return - end - - -- abort if no item fits onto it - if craft_item:get_count() >= craft_item:get_definition().stack_max then - return - end - - -- use the item there if it's in the right group and a group item is needed - if need_group then - if minetest.get_item_group(ciname, need_group) == 0 then + local get_item_group = minetest.get_item_group + local group = needed_item:match("^group:(.+)") + if group then + if not craft_item:is_empty() then + -- Source item must be the same to fill + if get_item_group(craft_item:get_name(), group) ~= 0 then + needed_item = craft_item:get_name() + else + -- TODO: Maybe swap unmatching "craft" items + -- !! Would conflict with recursive function call return end - needed_item = ciname - need_group = false - end - end - - if need_group then - -- search an item of the specific group - for i,item in pairs(player_inv:get_list("main")) do - if not item:is_empty() - and minetest.get_item_group(item:get_name(), need_group) > 0 then - return i + else + -- Take matching group from the inventory (biggest stack) + local main = inv:get_list("main") + local max_found = 0 + for i, stack in ipairs(main) do + if stack:get_count() > max_found and + get_item_group(stack:get_name(), group) ~= 0 then + needed_item = stack:get_name() + max_found = stack:get_count() + if max_found >= amount then + break + end + end end end - - -- no index found - return - end - - -- search an item with a the name needed_item - for i,item in pairs(player_inv:get_list("main")) do - if not item:is_empty() - and item:get_name() == needed_item then - return i + else + if not craft_item:is_empty() and + craft_item:get_name() ~= needed_item then + return -- Item must be identical end end - -- no index found -end - --- modifies the player inventory and returns the changed craft_item if possible -local function move_item(player_inv, craft_item, needed_item) - local stackid = item_fits(player_inv, craft_item, needed_item) - if not stackid then - return + needed_item = ItemStack(needed_item) + local to_take = math.min(amount, needed_item:get_stack_max()) + to_take = to_take - craft_item:get_count() + if to_take <= 0 then + return -- Nothing to do end - local wanted_stack = player_inv:get_stack("main", stackid) - local taken_item = wanted_stack:take_item() - player_inv:set_stack("main", stackid, wanted_stack) + needed_item:set_count(to_take) - if not craft_item - or craft_item:is_empty() then - return taken_item + local taken = inv:remove_item("main", needed_item) + local leftover = taken:add_item(craft_item) + if not leftover:is_empty() then + -- Somehow failed to add the existing "craft" item. Undo the action. + inv:add_item("main", leftover) + return taken end - - craft_item:add_item(taken_item) - return craft_item + return taken end local function craftguide_craft(player, formname, fields) @@ -505,15 +506,21 @@ local function craftguide_craft(player, formname, fields) if amount then break end end if not amount then return end + + amount = tonumber(amount) or 99 -- fallback for "all" + if amount <= 0 or amount > 99 then return end + local player_name = player:get_player_name() - local output = unified_inventory.current_item[player_name] - if (not output) or (output == "") then return end + local output = unified_inventory.current_item[player_name] or "" + if output == "" then return end local player_inv = player:get_inventory() + local craft_list = player_inv:get_list("craft") - local crafts = unified_inventory.crafts_for[unified_inventory.current_craft_direction[player_name]][output] - if (not crafts) or (#crafts == 0) then return end + local crafts = unified_inventory.crafts_for[ + unified_inventory.current_craft_direction[player_name]][output] or {} + if #crafts == 0 then return end local alternate = unified_inventory.alternate[player_name] @@ -521,24 +528,17 @@ local function craftguide_craft(player, formname, fields) if craft.width > 3 then return end local needed = craft.items - - local craft_list = player_inv:get_list("craft") - local width = craft.width if width == 0 then -- Shapeless recipe width = 3 end - amount = tonumber(amount) or 99 - --[[ - if amount == "max" then - amount = 99 -- Arbitrary; need better way to do this. - else - amount = tonumber(amount) - end--]] - - for iter = 1, amount do + -- To spread the items evenly + local STEPSIZE = math.ceil(math.sqrt(amount) / 5) * 5 + local current_count = 0 + repeat + current_count = math.min(current_count + STEPSIZE, amount) local index = 1 for y = 1, 3 do for x = 1, width do @@ -546,7 +546,9 @@ local function craftguide_craft(player, formname, fields) if needed_item then local craft_index = ((y - 1) * 3) + x local craft_item = craft_list[craft_index] - local newitem = move_item(player_inv, craft_item, needed_item) + local newitem = craftguide_move_stacks(player_inv, + craft_item, needed_item, current_count) + if newitem then craft_list[craft_index] = newitem end @@ -554,7 +556,7 @@ local function craftguide_craft(player, formname, fields) index = index + 1 end end - end + until current_count == amount player_inv:set_list("craft", craft_list) @@ -562,6 +564,10 @@ local function craftguide_craft(player, formname, fields) end minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "" then + return + end + for k, v in pairs(fields) do if k:match("craftguide_craft_") then craftguide_craft(player, formname, fields)