516 lines
18 KiB
Lua
516 lines
18 KiB
Lua
|
---Close one or all open quivers in players inventory
|
||
|
---@param player ObjectRef
|
||
|
---@param quiver_id? string If `nil` then all open quivers will be closed
|
||
|
---@returns nil
|
||
|
function x_bows.quiver.close_quiver(player, quiver_id)
|
||
|
local player_inv = player:get_inventory()
|
||
|
|
||
|
---find matching quiver item in players inventory with the open formspec name
|
||
|
if player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then
|
||
|
local inv_list = player_inv:get_list('main')
|
||
|
|
||
|
for i, st in ipairs(inv_list) do
|
||
|
local st_meta = st:get_meta()
|
||
|
|
||
|
if not st:is_empty() and st:get_name() == 'x_bows:quiver_open' then
|
||
|
if quiver_id and st_meta:get_string('quiver_id') == quiver_id then
|
||
|
local replace_item = x_bows.quiver.get_replacement_item(st, 'x_bows:quiver')
|
||
|
player_inv:set_stack('main', i, replace_item)
|
||
|
break
|
||
|
else
|
||
|
local replace_item = x_bows.quiver.get_replacement_item(st, 'x_bows:quiver')
|
||
|
player_inv:set_stack('main', i, replace_item)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta.
|
||
|
---@param from_stack ItemStack transfer data from this item
|
||
|
---@param to_item_name string transfer data to this item
|
||
|
---@return ItemStack ItemStack replacement item
|
||
|
function x_bows.quiver.get_replacement_item(from_stack, to_item_name)
|
||
|
---@type ItemStack
|
||
|
local replace_item = ItemStack({
|
||
|
name = to_item_name,
|
||
|
count = from_stack:get_count(),
|
||
|
wear = from_stack:get_wear()
|
||
|
})
|
||
|
local replace_item_meta = replace_item:get_meta()
|
||
|
local from_stack_meta = from_stack:get_meta()
|
||
|
|
||
|
replace_item_meta:set_string('quiver_items', from_stack_meta:get_string('quiver_items'))
|
||
|
replace_item_meta:set_string('quiver_id', from_stack_meta:get_string('quiver_id'))
|
||
|
replace_item_meta:set_string('description', from_stack_meta:get_string('description'))
|
||
|
|
||
|
return replace_item
|
||
|
end
|
||
|
|
||
|
---Gets arrow from quiver
|
||
|
---@param player ObjectRef
|
||
|
---@return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil}
|
||
|
function x_bows.quiver.get_itemstack_arrow_from_quiver(player)
|
||
|
local player_inv = player:get_inventory()
|
||
|
---@type ItemStack|nil
|
||
|
local found_arrow_stack
|
||
|
local prev_detached_inv_list = {}
|
||
|
local quiver_id
|
||
|
local quiver_name
|
||
|
|
||
|
---find matching quiver item in players inventory with the open formspec name
|
||
|
if player_inv and player_inv:contains_item('main', 'x_bows:quiver') then
|
||
|
local inv_list = player_inv:get_list('main')
|
||
|
|
||
|
for i, st in ipairs(inv_list) do
|
||
|
if not st:is_empty() and st:get_name() == 'x_bows:quiver' then
|
||
|
local st_meta = st:get_meta()
|
||
|
local player_name = player:get_player_name()
|
||
|
quiver_id = st_meta:get_string('quiver_id')
|
||
|
|
||
|
local detached_inv = x_bows.quiver.get_or_create_detached_inv(
|
||
|
quiver_id,
|
||
|
player_name,
|
||
|
st_meta:get_string('quiver_items')
|
||
|
)
|
||
|
|
||
|
if not detached_inv:is_empty('main') then
|
||
|
local detached_inv_list = detached_inv:get_list('main')
|
||
|
|
||
|
---find arrows inside quiver inventory
|
||
|
for j, qst in ipairs(detached_inv_list) do
|
||
|
---save copy of inv list before we take the item
|
||
|
table.insert(prev_detached_inv_list, detached_inv:get_stack('main', j))
|
||
|
|
||
|
if not qst:is_empty() and not found_arrow_stack then
|
||
|
quiver_name = st:get_name()
|
||
|
found_arrow_stack = qst:take_item()
|
||
|
|
||
|
if not x_bows.is_creative(player_name) then
|
||
|
detached_inv:set_list('main', detached_inv_list)
|
||
|
x_bows.quiver.save(detached_inv, player, true)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if found_arrow_stack then
|
||
|
---show HUD - quiver inventory
|
||
|
x_bows.quiver.udate_or_create_hud(player, prev_detached_inv_list)
|
||
|
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return {
|
||
|
found_arrow_stack = found_arrow_stack,
|
||
|
quiver_id = quiver_id,
|
||
|
quiver_name = quiver_name
|
||
|
}
|
||
|
end
|
||
|
|
||
|
---Remove all added HUDs
|
||
|
---@param player ObjectRef
|
||
|
function x_bows.quiver.remove_hud(player)
|
||
|
local player_name = player:get_player_name()
|
||
|
|
||
|
if x_bows.quiver.hud_item_ids[player_name] then
|
||
|
for _, v in pairs(x_bows.quiver.hud_item_ids[player_name]) do
|
||
|
if type(v) == 'table' then
|
||
|
for _, v2 in pairs(v) do
|
||
|
player:hud_remove(v2)
|
||
|
end
|
||
|
else
|
||
|
player:hud_remove(v)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
x_bows.quiver.hud_item_ids[player_name] = {
|
||
|
arrow_inv_img = {},
|
||
|
stack_count = {}
|
||
|
}
|
||
|
else
|
||
|
x_bows.quiver.hud_item_ids[player_name] = {
|
||
|
arrow_inv_img = {},
|
||
|
stack_count = {}
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---Update or create HUD
|
||
|
---@todo implement hud_change?
|
||
|
---@param player ObjectRef
|
||
|
---@param inv_list ItemStack[]
|
||
|
---@return nil
|
||
|
function x_bows.quiver.udate_or_create_hud(player, inv_list)
|
||
|
local player_name = player:get_player_name()
|
||
|
local selected_bg_added = false
|
||
|
|
||
|
if x_bows.quiver.after_job[player_name] then
|
||
|
for _, v in pairs(x_bows.quiver.after_job[player_name]) do
|
||
|
v:cancel()
|
||
|
end
|
||
|
|
||
|
x_bows.quiver.after_job[player_name] = {}
|
||
|
else
|
||
|
x_bows.quiver.after_job[player_name] = {}
|
||
|
end
|
||
|
|
||
|
x_bows.quiver.remove_hud(player)
|
||
|
|
||
|
---title image
|
||
|
x_bows.quiver.hud_item_ids[player_name].title_image = player:hud_add({
|
||
|
hud_elem_type = 'image',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -120, y = -140},
|
||
|
text = 'x_bows_quiver.png',
|
||
|
scale = {x = 4, y = 4},
|
||
|
alignment = 0,
|
||
|
})
|
||
|
|
||
|
---title copy
|
||
|
local quiver_def = minetest.registered_items['x_bows:quiver']
|
||
|
x_bows.quiver.hud_item_ids[player_name].title_copy = player:hud_add({
|
||
|
hud_elem_type = 'text',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -120, y = -75},
|
||
|
text = quiver_def.short_description,
|
||
|
alignment = 0,
|
||
|
scale = {x = 100, y = 30},
|
||
|
number = 0xFFFFFF,
|
||
|
})
|
||
|
|
||
|
---hotbar bg
|
||
|
x_bows.quiver.hud_item_ids[player_name].hotbar_bg = player:hud_add({
|
||
|
hud_elem_type = 'image',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -238, y = 0},
|
||
|
text = 'x_bows_quiver_hotbar.png',
|
||
|
scale = {x = 1, y = 1},
|
||
|
alignment = {x = 1, y = 0 },
|
||
|
})
|
||
|
|
||
|
for j, qst in ipairs(inv_list) do
|
||
|
if not qst:is_empty() then
|
||
|
local found_arrow_stack_def = minetest.registered_items[qst:get_name()]
|
||
|
|
||
|
if not selected_bg_added then
|
||
|
selected_bg_added = true
|
||
|
|
||
|
---ui selected bg
|
||
|
x_bows.quiver.hud_item_ids[player_name].hotbar_selected = player:hud_add({
|
||
|
hud_elem_type = 'image',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -308 + (j * 74), y = 2},
|
||
|
text = 'x_bows_quiver_hotbar_selected.png',
|
||
|
scale = {x = 1, y = 1},
|
||
|
alignment = {x = 1, y = 0 },
|
||
|
})
|
||
|
end
|
||
|
|
||
|
if found_arrow_stack_def then
|
||
|
---arrow inventory image
|
||
|
table.insert(x_bows.quiver.hud_item_ids[player_name].arrow_inv_img, player:hud_add({
|
||
|
hud_elem_type = 'image',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -300 + (j * 74), y = 0},
|
||
|
text = found_arrow_stack_def.inventory_image,
|
||
|
scale = {x = 4, y = 4},
|
||
|
alignment = {x = 1, y = 0 },
|
||
|
}))
|
||
|
|
||
|
---stack count
|
||
|
table.insert(x_bows.quiver.hud_item_ids[player_name].stack_count, player:hud_add({
|
||
|
hud_elem_type = 'text',
|
||
|
position = {x = 1, y = 0.5},
|
||
|
offset = {x = -244 + (j * 74), y = 23},
|
||
|
text = qst:get_count(),
|
||
|
alignment = -1,
|
||
|
scale = {x = 50, y = 10},
|
||
|
number = 0xFFFFFF,
|
||
|
}))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---@param v_player ObjectRef
|
||
|
table.insert(x_bows.quiver.after_job[player_name], minetest.after(10, function(v_player)
|
||
|
x_bows.quiver.remove_hud(v_player)
|
||
|
end, player))
|
||
|
end
|
||
|
|
||
|
---Get existing detached inventory or create new one
|
||
|
---@param quiver_id string
|
||
|
---@param player_name string
|
||
|
---@param quiver_items? string
|
||
|
---@return InvRef
|
||
|
function x_bows.quiver.get_or_create_detached_inv(quiver_id, player_name, quiver_items)
|
||
|
local detached_inv
|
||
|
|
||
|
if quiver_id ~= '' then
|
||
|
detached_inv = minetest.get_inventory({type='detached', name=quiver_id})
|
||
|
end
|
||
|
|
||
|
if not detached_inv then
|
||
|
detached_inv = minetest.create_detached_inventory(quiver_id, {
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param from_list string
|
||
|
---@param from_index number
|
||
|
---@param to_list string
|
||
|
---@param to_index number
|
||
|
---@param count number
|
||
|
---@param player ObjectRef
|
||
|
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||
|
if x_bows.quiver.quiver_can_allow(inv, player) then
|
||
|
return count
|
||
|
else
|
||
|
return 0
|
||
|
end
|
||
|
end,
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param listname string listname of the inventory, e.g. `'main'`
|
||
|
---@param index number
|
||
|
---@param stack ItemStack
|
||
|
---@param player ObjectRef
|
||
|
allow_put = function(inv, listname, index, stack, player)
|
||
|
if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 and x_bows.quiver.quiver_can_allow(inv, player) then
|
||
|
return stack:get_count()
|
||
|
else
|
||
|
return 0
|
||
|
end
|
||
|
end,
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param listname string listname of the inventory, e.g. `'main'`
|
||
|
---@param index number
|
||
|
---@param stack ItemStack
|
||
|
---@param player ObjectRef
|
||
|
allow_take = function(inv, listname, index, stack, player)
|
||
|
if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 and x_bows.quiver.quiver_can_allow(inv, player) then
|
||
|
return stack:get_count()
|
||
|
else
|
||
|
return 0
|
||
|
end
|
||
|
end,
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param from_list string
|
||
|
---@param from_index number
|
||
|
---@param to_list string
|
||
|
---@param to_index number
|
||
|
---@param count number
|
||
|
---@param player ObjectRef
|
||
|
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||
|
x_bows.quiver.save(inv, player)
|
||
|
end,
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param listname string listname of the inventory, e.g. `'main'`
|
||
|
---@param index number index where was item put
|
||
|
---@param stack ItemStack stack of item what was put
|
||
|
---@param player ObjectRef
|
||
|
on_put = function(inv, listname, index, stack, player)
|
||
|
x_bows.quiver.save(inv, player)
|
||
|
end,
|
||
|
---@param inv InvRef detached inventory
|
||
|
---@param listname string listname of the inventory, e.g. `'main'`
|
||
|
---@param index number
|
||
|
---@param stack ItemStack
|
||
|
---@param player ObjectRef
|
||
|
on_take = function(inv, listname, index, stack, player)
|
||
|
x_bows.quiver.save(inv, player)
|
||
|
end,
|
||
|
}, player_name)
|
||
|
|
||
|
detached_inv:set_size('main', 3 * 1)
|
||
|
end
|
||
|
|
||
|
---populate items in inventory
|
||
|
if quiver_items and quiver_items ~= '' then
|
||
|
x_bows.quiver.set_string_to_inv(detached_inv, quiver_items)
|
||
|
end
|
||
|
|
||
|
return detached_inv
|
||
|
end
|
||
|
|
||
|
---create UUID
|
||
|
---@return string
|
||
|
local function uuid()
|
||
|
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||
|
|
||
|
---@diagnostic disable-next-line: redundant-return-value
|
||
|
return string.gsub(template, '[xy]', function (c)
|
||
|
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
|
||
|
return string.format('%x', v)
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
---create formspec
|
||
|
---@param name string name of the form
|
||
|
---@return string
|
||
|
local function get_formspec(name)
|
||
|
local width = 3
|
||
|
local height = 1
|
||
|
local list_w = 8
|
||
|
local list_pos_x = (list_w - width) / 2
|
||
|
|
||
|
local formspec =
|
||
|
'size['..list_w..',6]' ..
|
||
|
'list[detached:'..name..';main;'..list_pos_x..',0.3;'..width..',1;]'..
|
||
|
'list[current_player;main;0,'..(height + 0.85)..';'..list_w..',1;]'..
|
||
|
'list[current_player;main;0,'..(height + 2.08)..';'..list_w..',3;8]'..
|
||
|
'listring[detached:'..name..';main]'..
|
||
|
'listring[current_player;main]'..
|
||
|
default.get_hotbar_bg(0, height + 0.85)
|
||
|
|
||
|
--update formspec
|
||
|
local inv = minetest.get_inventory({type='detached', name=name})
|
||
|
local invlist = inv:get_list(name)
|
||
|
|
||
|
---inventory slots overlay
|
||
|
local px, py = list_pos_x, 0.3
|
||
|
|
||
|
for i = 1, 3 do
|
||
|
if not invlist or invlist[i]:is_empty() then
|
||
|
formspec = formspec ..
|
||
|
'image[' .. px .. ',' .. py .. ';1,1;x_bows_arrow_slot.png]'
|
||
|
end
|
||
|
|
||
|
px = px + 1
|
||
|
end
|
||
|
|
||
|
return formspec
|
||
|
end
|
||
|
|
||
|
---convert inventory of itemstacks to serialized string
|
||
|
---@param inv InvRef
|
||
|
---@return {['inv_string']: string, ['content_description']: string}
|
||
|
local function get_string_from_inv(inv)
|
||
|
local inv_list = inv:get_list('main')
|
||
|
local t = {}
|
||
|
local content_description = ''
|
||
|
|
||
|
for i, st in ipairs(inv_list) do
|
||
|
if not st:is_empty() then
|
||
|
table.insert(t, st:to_table())
|
||
|
content_description = content_description .. '\n' ..st:get_short_description()..' '..st:get_count()
|
||
|
else
|
||
|
table.insert(t, {is_empty = true})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return {
|
||
|
inv_string = minetest.serialize(t),
|
||
|
content_description = content_description == '' and '\nEmpty' or content_description
|
||
|
}
|
||
|
end
|
||
|
|
||
|
---set items from serialized string to inventory
|
||
|
---@param inv InvRef inventory to add items to
|
||
|
---@param str string previously stringified inventory of itemstacks
|
||
|
function x_bows.quiver.set_string_to_inv(inv, str)
|
||
|
local t = minetest.deserialize(str)
|
||
|
|
||
|
for i, item in ipairs(t) do
|
||
|
if not item.is_empty then
|
||
|
inv:set_stack('main', i, ItemStack(item))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---save quiver inventory to itemstack meta
|
||
|
---@param inv InvRef
|
||
|
---@param player ObjectRef
|
||
|
---@param quiver_is_closed? boolean default `false`
|
||
|
function x_bows.quiver.save(inv, player, quiver_is_closed)
|
||
|
local player_inv = player:get_inventory()
|
||
|
local inv_loc = inv:get_location()
|
||
|
local quiver_item_name = quiver_is_closed and 'x_bows:quiver' or 'x_bows:quiver_open'
|
||
|
|
||
|
---find matching quiver item in players inventory with the open formspec name
|
||
|
if player_inv and player_inv:contains_item('main', quiver_item_name) then
|
||
|
local inv_list = player_inv:get_list('main')
|
||
|
|
||
|
for i, st in ipairs(inv_list) do
|
||
|
local st_meta = st:get_meta()
|
||
|
|
||
|
if not st:is_empty() and st:get_name() == quiver_item_name and st_meta:get_string('quiver_id') == inv_loc.name then
|
||
|
---save inventory items in quiver item meta
|
||
|
local string_from_inventory_result = get_string_from_inv(inv)
|
||
|
|
||
|
st_meta:set_string('quiver_items', string_from_inventory_result.inv_string)
|
||
|
|
||
|
---update description
|
||
|
local new_description = st:get_short_description()..'\n'..string_from_inventory_result.content_description..'\n'
|
||
|
|
||
|
st_meta:set_string('description', new_description)
|
||
|
player_inv:set_stack('main', i, st)
|
||
|
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---check if we are allowing actions in the correct quiver inventory
|
||
|
---@param inv InvRef
|
||
|
---@param player ObjectRef
|
||
|
---@return boolean
|
||
|
function x_bows.quiver.quiver_can_allow(inv, player)
|
||
|
local player_inv = player:get_inventory()
|
||
|
local inv_loc = inv:get_location()
|
||
|
|
||
|
---find matching quiver item in players inventory with the open formspec name
|
||
|
if player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then
|
||
|
local inv_list = player_inv:get_list('main')
|
||
|
|
||
|
for i, st in ipairs(inv_list) do
|
||
|
local st_meta = st:get_meta()
|
||
|
|
||
|
if not st:is_empty() and st:get_name() == 'x_bows:quiver_open' and st_meta:get_string('quiver_id') == inv_loc.name then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
---Open quiver
|
||
|
---@param itemstack ItemStack
|
||
|
---@param user ObjectRef
|
||
|
---@return ItemStack
|
||
|
function x_bows.open_quiver(itemstack, user)
|
||
|
local itemstack_meta = itemstack:get_meta()
|
||
|
local pname = user:get_player_name()
|
||
|
local quiver_id = itemstack_meta:get_string('quiver_id')
|
||
|
|
||
|
---create inventory id and save it
|
||
|
if quiver_id == '' then
|
||
|
quiver_id = itemstack:get_name()..'_'..uuid()
|
||
|
itemstack_meta:set_string('quiver_id', quiver_id)
|
||
|
end
|
||
|
|
||
|
local quiver_items = itemstack_meta:get_string('quiver_items')
|
||
|
|
||
|
x_bows.quiver.get_or_create_detached_inv(quiver_id, pname, quiver_items)
|
||
|
|
||
|
---show open variation of quiver
|
||
|
local replace_item = x_bows.quiver.get_replacement_item(itemstack, 'x_bows:quiver_open')
|
||
|
|
||
|
itemstack:replace(replace_item)
|
||
|
|
||
|
minetest.sound_play('x_bows_quiver', {
|
||
|
to_player = user:get_player_name(),
|
||
|
gain = 0.1
|
||
|
})
|
||
|
|
||
|
minetest.show_formspec(pname, quiver_id, get_formspec(quiver_id))
|
||
|
return itemstack
|
||
|
end
|
||
|
|
||
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||
|
if player and fields.quit then
|
||
|
x_bows.quiver.close_quiver(player, formname)
|
||
|
end
|
||
|
end)
|