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 })