[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.
This commit is contained in:
luk3yx 2022-11-06 12:20:15 +13:00
parent c79c4adf39
commit c370b8051a
2 changed files with 220 additions and 9 deletions

View File

@ -1,4 +1,4 @@
name = exchange_shop
description = An improved exchange shop
depends = default
depends = default, flow
optional_depends = mesecons_mvps

227
shop.lua
View File

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