feat: Add set_as_inventory_for API function (#2)

Adds the `Form:set_as_inventory_for(player, [ctx])` API.

Also includes:

 • `Form:unset_as_inventory_for(player)`
 • Extends private `show_form` to account for inv edge cases
 • Extends private `on_fs_input` to account for inv edge cases
 • Adds several abstraction functions
 • Add feature to `on_leaveplayer` to delete the inv formspec state as well
 • Add a few unit tests for related features
This commit is contained in:
Lazerbeak12345 2023-03-25 20:44:31 -06:00 committed by GitHub
parent 45e61e34c9
commit bba30cc267
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 153 additions and 22 deletions

View File

@ -399,3 +399,38 @@ end)
```
![Screenshot](https://user-images.githubusercontent.com/3182651/212222545-baee3669-15cd-410d-a638-c63b65a8811b.png)
### Using a form as an inventory
A form can be set as the player inventory. Flow internally generates the
formspec and passes it to `player:set_inventory_formspec()`. This will
completely replace your inventory and isn't compatible with inventory mods like
sfinv.
```lua
local example_inventory = flow.make_gui(function (player, context)
return gui.Label{ label = "Inventory goes here!" }
end)
minetest.register_on_joinplayer(function(player)
example_inventory:set_as_inventory_for(player)
end)
```
Like with the `show_hud` function, `update*` functions don't do anything, so to
update it, call `set_as_inventory_for` again with the new context. If the
context is not provided, it will reuse the existing context.
```lua
example_inventory:set_as_inventory_for(player, new_context)
```
While the form will of course be cleared when the player leaves, if you'd like
to unset the inventory manually, call `:unset_as_inventory_for(player)`,
analogue to `close_hud`:
```lua
example_inventory:unset_as_inventory_for(player)
```
This will set the inventory formspec string to `""` and stop flow from
processing inventory formspec input.

View File

@ -720,10 +720,12 @@ function Form:_render(player, ctx, formspec_version, id1)
"Changing the value of ctx.form is not supported!")
ctx.form = orig_form
-- The numbering of automatically named elements is continued from previous
-- iterations of the form to work around race conditions
if not id1 or id1 > 1e6 then id1 = 0 end
local tree = render_ast(box)
local callbacks, saved_fields, id2 = parse_callbacks(
tree, orig_form, id1 or 0
)
local callbacks, saved_fields, id2 = parse_callbacks(tree, orig_form, id1)
local redraw_if_changed = {}
for var in pairs(used_ctx_vars) do
@ -744,8 +746,7 @@ function Form:_render(player, ctx, formspec_version, id1)
}
end
local open_formspecs = {}
local function show_form(self, player, formname, ctx, auto_name_id)
local function prepare_form(self, player, formname, ctx, auto_name_id)
local name = player:get_player_name()
-- local t = DEBUG_MODE and minetest.get_us_time()
local info = minetest.get_player_information(name)
@ -757,10 +758,19 @@ local function show_form(self, player, formname, ctx, auto_name_id)
-- local t3 = DEBUG_MODE and minetest.get_us_time()
form_info.formname = formname
open_formspecs[name] = form_info
-- if DEBUG_MODE then
-- print(t3 - t, t2 - t, t3 - t2)
-- end
return fs, form_info
end
local open_formspecs = {}
local function show_form(self, player, formname, ctx, auto_name_id)
local name = player:get_player_name()
local fs, form_info = prepare_form(self, player, formname, ctx,
auto_name_id)
open_formspecs[name] = form_info
minetest.show_formspec(name, formname, fs)
end
@ -786,6 +796,22 @@ function Form:show_hud(player, ctx)
hud_fs.show_hud(player, self, tree)
end
local open_inv_formspecs = {}
function Form:set_as_inventory_for(player, ctx)
local name = player:get_player_name()
local old_form_info = open_inv_formspecs[name]
if not ctx and old_form_info and old_form_info.self == self then
ctx = old_form_info.ctx
end
-- Formname of "" is inventory
local fs, form_info = prepare_form(self, player, "", ctx or {},
old_form_info and old_form_info.auto_name_id)
open_inv_formspecs[name] = form_info
player:set_inventory_formspec(fs)
end
function Form:close(player)
local name = player:get_player_name()
local form_info = open_formspecs[name]
@ -799,15 +825,20 @@ function Form:close_hud(player)
hud_fs.close_hud(player, self)
end
local function update_form(self, player, form_info)
-- The numbering of automatically named elements is continued from previous
-- iterations of the form to work around race conditions
local auto_name_id
if form_info.auto_name_id < 1e6 then
auto_name_id = form_info.auto_name_id
function Form:unset_as_inventory_for(player)
local name = player:get_player_name()
local form_info = open_inv_formspecs[name]
if form_info and form_info.self == self then
open_inv_formspecs[name] = nil
player:set_inventory_formspec("")
end
end
show_form(self, player, form_info.formname, form_info.ctx, auto_name_id)
-- This function may eventually call minetest.update_formspec if/when it gets
-- added (https://github.com/minetest/minetest/issues/13142)
local function update_form(self, player, form_info)
show_form(self, player, form_info.formname, form_info.ctx,
form_info.auto_name_id)
end
function Form:update(player)
@ -835,7 +866,8 @@ end
local function on_fs_input(player, formname, fields)
local name = player:get_player_name()
local form_info = open_formspecs[name]
local form_infos = formname == "" and open_inv_formspecs or open_formspecs
local form_info = form_infos[name]
if not form_info or formname ~= form_info.formname then return end
local callbacks = form_info.callbacks
@ -848,12 +880,13 @@ local function on_fs_input(player, formname, fields)
for field, transformer in pairs(form_info.saved_fields) do
if fields[field] then
local new_value = transformer(fields[field])
if redraw_if_changed[field] and ctx_form[field] ~= new_value then
if DEBUG_MODE then
print('Modified:', dump(field), dump(ctx_form[field]),
'->', dump(new_value))
if ctx_form[field] ~= new_value then
if redraw_if_changed[field] then
redraw_fs = true
elseif formname == "" then
-- Update the inventory when the player closes it next
form_info.ctx_form_modified = true
end
redraw_fs = true
end
ctx_form[field] = new_value
end
@ -866,9 +899,14 @@ local function on_fs_input(player, formname, fields)
end
end
if open_formspecs[name] ~= form_info then return true end
if form_infos[name] ~= form_info then return true end
if fields.quit then
if formname == "" then
-- Special case for inventory forms
if redraw_fs or (fields.quit and form_info.ctx_form_modified) then
form_info.self:set_as_inventory_for(player)
end
elseif fields.quit then
open_formspecs[name] = nil
elseif redraw_fs then
update_form(form_info.self, player, form_info)
@ -877,7 +915,9 @@ local function on_fs_input(player, formname, fields)
end
local function on_leaveplayer(player)
open_formspecs[player:get_player_name()] = nil
local name = player:get_player_name()
open_formspecs[name] = nil
open_inv_formspecs[name] = nil
end
if DEBUG_MODE then

View File

@ -22,6 +22,27 @@ local function dummy() end
minetest.register_on_leaveplayer = dummy
minetest.get_modpath = dummy
minetest.is_singleplayer = dummy
minetest.get_player_information = dummy
minetest.show_formspec = dummy
-- Stub minetest player api
local function stub_player(name)
assert(type(name) == "string")
local self = {}
function self:get_player_name()
return name
end
function self:get_inventory_formspec()
return ""
end
function self:set_inventory_formspec(formspec)
assert(formspec ~= nil)
function self:get_inventory_formspec()
return formspec
end
end
return self
end
table.indexof = table.indexof or function(list, value)
for i, item in ipairs(list) do
@ -258,4 +279,39 @@ describe("Flow", function()
style[test;prop=value]
]])
end)
it("registers inventory formspecs", function ()
local stupid_simple_inv_expected =
"formspec_version[5]" ..
"size[10.35,5.35]" ..
"list[current_player;main;0.3,0.3;8,4]"
local stupid_simple_inv = flow.make_gui(function (p, c)
return gui.List{
inventory_location = "current_player",
list_name = "main",
w = 8,
h = 4,
}
end)
local player = stub_player("test_player")
assert(player:get_inventory_formspec() == "")
stupid_simple_inv:set_as_inventory_for(player)
assert(player:get_inventory_formspec() == stupid_simple_inv_expected)
end)
it("can still show a form when an inventory formspec is shown", function ()
local expected_one = "formspec_version[5]size[1.6,1.6]box[0.3,0.3;1,1;]"
local one = flow.make_gui(function (p, c)
return gui.Box{ w = 1, h = 1 }
end)
local blue = flow.make_gui(function (p, c)
return gui.Box{ w = 1, h = 4, color = "blue" }
end)
local player = stub_player("test_player")
assert(player:get_inventory_formspec() == "")
one:set_as_inventory_for(player)
assert(player:get_inventory_formspec() == expected_one)
blue:show(player)
assert(player:get_inventory_formspec() == expected_one)
end)
end)