From c370b8051a6cb56e8de9249a0f57427352e67163 Mon Sep 17 00:00:00 2001 From: luk3yx Date: Sun, 6 Nov 2022 12:20:15 +1300 Subject: [PATCH] [WIP] Add item picker WARNING: Do not revert back to the previous commits after this one! Doing so will give away items for free (as players will be able to take out any items that they prevously selected with the item picker) If you want to be able to revert then maybe it'd be better to make the existing code store a copy of the item in its own inventory first (like mese tubes in pipeworks). The GUI currently uses flow because I'm lazy, if more performance is needed then I can probably convert it to a formspec. --- mod.conf | 2 +- shop.lua | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 220 insertions(+), 9 deletions(-) diff --git a/mod.conf b/mod.conf index 3f03070..3eb6577 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name = exchange_shop description = An improved exchange shop -depends = default +depends = default, flow optional_depends = mesecons_mvps diff --git a/shop.lua b/shop.lua index 5b3069d..0f939ca 100644 --- a/shop.lua +++ b/shop.lua @@ -9,6 +9,7 @@ local S = exchange_shop.S local shop_positions = {} local tconcat = table.concat +local lower = utf8.lower local function get_exchange_shop_formspec(mode, pos, meta) local name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z @@ -67,7 +68,20 @@ local function get_exchange_shop_formspec(mode, pos, meta) main_image .. "list[current_player;main;0," .. inv_pos .. ";9,4;]" - return formspec + return formspec + end + + local function make_slots_btns(x, y, w, h, list, label) + local fs = {make_slots(x, y, w, h, list, label)} + local i = 0 + for y2 = 1, h do + for x2 = 1, w do + i = i + 1 + fs[#fs + 1] = ("image_button[%s,%s;1.2,1.2;;%s_%s;;false;false]"):format(x + x2 - 1.1, y + y2 - 1.1, list, i) + end + end + print(dump(fs)) + return tconcat(fs) end if mode == "owner_custm" or mode == "owner_stock" then @@ -75,12 +89,12 @@ local function get_exchange_shop_formspec(mode, pos, meta) -- owner local formspec = ( - "size[10,10]" .. + "formspec_version[3]size[10,10]real_coordinates[false]" .. "item_image[0,-0.1;1,1;".. exchange_shop.shopname .. "]" .. "label[0.9,0.1;" .. S("Exchange Shop") .. "]" .. default.gui_close_btn("9.3,-0.1") .. - make_slots(0.1, 2, 2, 2, "cust_ow", S("You need:")) .. - make_slots(2.6, 2, 2, 2, "cust_og", S("You give:")) .. + make_slots_btns(0.1, 2, 2, 2, "cust_ow", S("You need:")) .. + make_slots_btns(2.6, 2, 2, 2, "cust_og", S("You give:")) .. "label[5,0.4;" .. S("Current stock:") .. "]" ) @@ -140,6 +154,178 @@ local function get_exchange_shop_formspec(mode, pos, meta) end +local function shop_valid(pos, player) + return minetest.get_node(pos).name == exchange_shop.shopname and + not minetest.is_protected(pos, player:get_player_name()) +end + +-- TODO: Maybe not use flow +local function go_back(player, ctx) + if shop_valid(ctx.pos, player) then + local name = player:get_player_name() + shop_positions[name] = ctx.pos + minetest.show_formspec(name, "exchange_shop:shop_formspec", + get_exchange_shop_formspec("owner_custm", ctx.pos)) + end +end + +local items_cache = {} +minetest.after(0, function() + for item, def in pairs(minetest.registered_items) do + if (not def.groups or (def.groups.not_in_creative_inventory ~= 1 and + def.groups.stairs ~= 1)) and def.description ~= "" then + items_cache[#items_cache + 1] = item + end + end + + table.sort(items_cache) +end) + +local function matches_search(query, description, lang) + return query == "" or + lower(minetest.get_translated_string(lang, description)):find(query, 1, true) +end + +local gui = flow.widgets +local item_picker = flow.make_gui(function(player, ctx) + local rows = {name='items', w = 11.8, h = 5.8, custom_scrollbar = { + w = 0.9, + scrollbar_bg = "inventory_creative_scrollbar_bg.png", + slider = "inventory_creative_slider.png", + arrow_up = "inventory_creative_arrow_up.png", + arrow_down = "inventory_creative_arrow_down.png", + }} + + local query = ctx.form.Dsearch and lower(ctx.form.Dsearch) or "" + + -- Reset items scrollbar + if ctx.query ~= query then + ctx.form["_scrollbar-items"] = 0 + ctx.query = query + end + + local name = player:get_player_name() + local info = minetest.get_player_information(name) + local lang = info and info.lang_code or "" + + local row = {} + for _, item in ipairs(items_cache) do + local description = minetest.registered_items[item].description + if matches_search(query, description, lang) then + if #row >= 10 then + rows[#rows + 1] = gui.HBox(row) + row = {} + end + row[#row + 1] = gui.ItemImageButton{ + w = 1, h = 1, + item_name = item, + on_event = function(p, c) + if c.item ~= item or c.form.amount ~= "1" then + c.item = item + c.desc = description or item + c.form.amount = "1" + return true + end + end, + } + end + end + if #rows > 0 or #row > 0 then + rows[#rows + 1] = gui.HBox(row) + else + rows[#rows + 1] = gui.Label{label = S("No items found.")} + end + + local item_preview + + return gui.VBox{ + gui.HBox{ + gui.Style{selectors = {"back"}, props = {border = false}}, + gui.ItemImageButton{ + item_name = exchange_shop.shopname, w = 1, h = 1, + on_event = go_back, name = "back", + }, + gui.Label{label = S("Select item"), align_h = "left", expand = true}, + + -- Search box + gui.HBox{ + align_v = "centre", + bgimg = "inventory_search_bg9.png", + bgimg_middle = 25, + spacing = 0, + gui.Spacer{expand = false, padding = 0.06}, + gui.Style{selectors = {"Dsearch"}, props = {border = false, bgcolor = "transparent"}}, + gui.Field{name = "Dsearch", w = 3, h = 0.7}, + gui.ImageButton{ + w = 0.7, h = 0.7, drawborder = false, padding = 0.05, + texture_name = "inventory_search.png", + }, + gui.ImageButton{ + w = 0.7, h = 0.7, drawborder = false, padding = 0.05, + texture_name = "inventory_search_clear.png", + on_event = function(_, c) + if c.form.Dsearch ~= "" then + c.form.Dsearch = "" + return true + end + end + }, + } + }, + gui.StyleType{ + selectors = {"item_image_button"}, + props = { + bgimg = "formspec_cell.png", + bgimg_hovered = "formspec_cell.png^[brighten", + border = false, + } + }, + gui.ScrollableVBox(rows), + gui.HBox{ + gui.ItemImage{w = 1, h = 1, item_name = ctx.item}, + gui.Label{ + label = ctx.desc and ctx.item ~= "" and + S("Selected item: @1", ctx.desc) or + S("No item selected") + }, + }, + gui.HBox{ + gui.Field{name = "amount", label = S("Amount"), default = "1"}, + gui.HBox{ + expand = true, align_h = "end", align_v = "end", + gui.Button{ + label = S("Clear"), + w = 3.5, + on_event = function(p, c) + c.item = "" + c.form.amount = "0" + return true + end, + }, + gui.Button{ + label = S("Save"), + w = 3.5, + on_event = function(p, c) + if shop_valid(c.pos, p) then + local item = ItemStack(c.item) + local amount = tonumber(c.form.amount) + if amount and amount == amount and amount >= 1 then + item:set_count(math.min(amount, item:get_stack_max())) + end + shop_positions[name] = c.pos + local meta = minetest.get_meta(c.pos) + meta:get_inventory():set_stack(c.list, c.idx, item) + minetest.show_formspec(name, "exchange_shop:shop_formspec", + get_exchange_shop_formspec("owner_custm", c.pos, meta)) + end + end, + }, + }, + }, + } +end) + + minetest.register_on_player_receive_fields(function(sender, formname, fields) if formname ~= "exchange_shop:shop_formspec" then return @@ -183,6 +369,23 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields) end minetest.show_formspec(player_name, "exchange_shop:shop_formspec", get_exchange_shop_formspec(mode, pos, meta)) + else + for field in pairs(fields) do + local list, idx = field:match("(cust_o[wg])_([1-4])") + if list then + idx = tonumber(idx) + local stack = minetest.get_meta(pos):get_inventory():get_stack(list, idx) + item_picker:show(sender, { + pos = pos, + list = list, + idx = idx, + item = stack:get_name(), + desc = stack:get_short_description(), + form = {amount = stack:get_count()} + }) + return + end + end end end) @@ -243,7 +446,11 @@ minetest.register_node(exchange_shop.shopname, { get_exchange_shop_formspec(mode, pos, meta)) end, - allow_metadata_inventory_move = function(pos, _, _, _, _, count, player) + allow_metadata_inventory_move = function(pos, from_list, to_list, _, _, count, player) + if from_list:sub(1, 6) == "cust_o" or to_list:sub(1, 6) == "cust_o" then + return 0 + end + local player_name = player:get_player_name() return not minetest.is_protected(pos, player_name) and count or 0 end, @@ -257,15 +464,19 @@ minetest.register_node(exchange_shop.shopname, { return 0 end if not minetest.is_protected(pos, player_name) - and listname ~= "custm_ej" then + and listname ~= "custm_ej" and listname:sub(1, 6) ~= "cust_o" then return stack:get_count() end return 0 end, - allow_metadata_inventory_take = function(pos, _, _, stack, player) + allow_metadata_inventory_take = function(pos, listname, _, stack, player) local player_name = player:get_player_name() - return not minetest.is_protected(pos, player_name) and stack:get_count() or 0 + if minetest.is_protected(pos, player_name) or + listname:sub(1, 6) == "cust_o" then + return 0 + end + return stack:get_count() end })