Rework armor stand inventories (#165)

This change makes armor stands only have one inventory list instead of four, thereby allowing the user to shift-click items back and forth. There is no visual difference in the formspec, and each piece of armor still has its own dedicated slot. An LBM is used to update the inventories of previous armor stands.
This commit is contained in:
Bradley Pierce
2025-09-06 02:06:39 -04:00
committed by GitHub
parent dbe0a9276a
commit 8f775a64f0

View File

@@ -6,27 +6,27 @@ local armor_stand_formspec = "size[8,7]" ..
default.gui_bg_img .. default.gui_bg_img ..
default.gui_slots .. default.gui_slots ..
default.get_hotbar_bg(0,3) .. default.get_hotbar_bg(0,3) ..
"list[current_name;armor_head;3,0.5;1,1;]" .. "list[current_name;main;3,0.5;2,1;]" ..
"list[current_name;armor_torso;4,0.5;1,1;]" .. "list[current_name;main;3,1.5;2,1;2]" ..
"list[current_name;armor_legs;3,1.5;1,1;]" ..
"list[current_name;armor_feet;4,1.5;1,1;]" ..
"image[3,0.5;1,1;3d_armor_stand_head.png]" .. "image[3,0.5;1,1;3d_armor_stand_head.png]" ..
"image[4,0.5;1,1;3d_armor_stand_torso.png]" .. "image[4,0.5;1,1;3d_armor_stand_torso.png]" ..
"image[3,1.5;1,1;3d_armor_stand_legs.png]" .. "image[3,1.5;1,1;3d_armor_stand_legs.png]" ..
"image[4,1.5;1,1;3d_armor_stand_feet.png]" .. "image[4,1.5;1,1;3d_armor_stand_feet.png]" ..
"list[current_player;main;0,3;8,1;]" .. "list[current_player;main;0,3;8,1;]" ..
"list[current_player;main;0,4.25;8,3;8]" "list[current_player;main;0,4.25;8,3;8]" ..
"listring[current_name;main]" ..
"listring[current_player;main]"
local elements = {"head", "torso", "legs", "feet"} local elements = {"head", "torso", "legs", "feet"}
local function drop_armor(pos) local function drop_armor(pos)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local inv = meta:get_inventory() local inv = meta:get_inventory()
for _, element in pairs(elements) do for i = 1, 4 do
local stack = inv:get_stack("armor_"..element, 1) local stack = inv:get_stack("main", i)
if stack and stack:get_count() > 0 then if stack and stack:get_count() > 0 then
armor.drop_armor(pos, stack) armor.drop_armor(pos, stack)
inv:set_stack("armor_"..element, 1, nil) inv:set_stack("main", i, nil)
end end
end end
end end
@@ -68,8 +68,8 @@ local function update_entity(pos)
local inv = meta:get_inventory() local inv = meta:get_inventory()
local yaw = 0 local yaw = 0
if inv then if inv then
for _, element in pairs(elements) do for i, element in ipairs(elements) do
local stack = inv:get_stack("armor_"..element, 1) local stack = inv:get_stack("main", i)
if stack:get_count() == 1 then if stack:get_count() == 1 then
local item = stack:get_name() or "" local item = stack:get_name() or ""
local def = stack:get_definition() or {} local def = stack:get_definition() or {}
@@ -149,236 +149,139 @@ minetest.register_node("3d_armor_stand:top", {
tiles = {"blank.png"}, tiles = {"blank.png"},
}) })
minetest.register_node("3d_armor_stand:armor_stand", { local function register_armor_stand(def)
description = S("Armor Stand"), local function owns_armor_stand(pos, meta, player)
drawtype = "mesh", if def.name == "locked_armor_stand" and not has_locked_armor_stand_privilege(meta, player) then
mesh = "3d_armor_stand.obj", return false
tiles = {"3d_armor_stand.png"},
use_texture_alpha = "clip",
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {
{-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
},
},
groups = {choppy=2, oddly_breakable_by_hand=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", armor_stand_formspec)
meta:set_string("infotext", S("Armor Stand"))
local inv = meta:get_inventory()
for _, element in pairs(elements) do
inv:set_size("armor_"..element, 1)
end end
end, local has_access = minetest.is_player(player) and not minetest.is_protected(pos, player:get_player_name())
can_dig = function(pos, player) if def.name == "shared_armor_stand" and not has_access then
local meta = minetest.get_meta(pos) return false
local inv = meta:get_inventory()
for _, element in pairs(elements) do
if not inv:is_empty("armor_"..element) then
return false
end
end end
return true return true
end, end
after_place_node = function(pos, placer)
minetest.add_entity(pos, "3d_armor_stand:armor_entity") minetest.register_node("3d_armor_stand:" .. def.name, {
add_hidden_node(pos, placer) description = def.description,
end, drawtype = "mesh",
allow_metadata_inventory_put = function(pos, listname, index, stack) mesh = "3d_armor_stand.obj",
local def = stack:get_definition() or {} tiles = {def.texture},
local groups = def.groups or {} use_texture_alpha = "clip",
if groups[listname] then paramtype = "light",
paramtype2 = "facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {
{-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
},
},
groups = {choppy=2, oddly_breakable_by_hand=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", armor_stand_formspec)
meta:set_string("infotext", def.description)
if def.name == "locked_armor_stand" then
meta:set_string("owner", "")
end
local inv = meta:get_inventory()
inv:set_size("main", 4)
end,
can_dig = function(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if not inv:is_empty("main") then
return false
end
return true
end,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
minetest.add_entity(pos, "3d_armor_stand:armor_entity")
if def.name == "locked_armor_stand" then
meta:set_string("owner", placer:get_player_name() or "")
meta:set_string("infotext", S("Armor Stand (owned by @1)", meta:get_string("owner")))
elseif def.name == "shared_armor_stand" then
meta:set_string("infotext", def.description)
end
add_hidden_node(pos, placer)
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
if not owns_armor_stand(pos, meta, player) then
return 0
end
local inv = meta:get_inventory()
local stack_def = stack:get_definition() or {}
local groups = stack_def.groups or {}
for i, element in ipairs(elements) do
if groups["armor_"..element] and inv:get_stack(listname, i):is_empty() then
return 1
end
end
return 0
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
if not owns_armor_stand(pos, meta, player) then
return 0
end
return 1 return 1
end end,
return 0 allow_metadata_inventory_move = function(pos)
end, return 0
allow_metadata_inventory_move = function(pos) end,
return 0 on_metadata_inventory_put = function(pos, listname, index, stack)
end, local meta = minetest.get_meta(pos)
on_metadata_inventory_put = function(pos) local inv = meta:get_inventory()
update_entity(pos) local stack_def = stack:get_definition() or {}
end, local groups = stack_def.groups or {}
on_metadata_inventory_take = function(pos) for i, element in ipairs(elements) do
update_entity(pos) if groups["armor_"..element] then
end, inv:set_stack(listname, i, stack)
after_destruct = function(pos) if index ~= i then
update_entity(pos) inv:set_stack(listname, index, nil)
remove_hidden_node(pos) end
end, break
end
end
update_entity(pos)
end,
on_metadata_inventory_take = function(pos)
update_entity(pos)
end,
after_destruct = function(pos)
update_entity(pos)
remove_hidden_node(pos)
end,
on_blast = def.on_blast
})
end
register_armor_stand({
name = "armor_stand",
description = S("Armor Stand"),
texture = "3d_armor_stand.png",
on_blast = function(pos) on_blast = function(pos)
drop_armor(pos) drop_armor(pos)
armor.drop_armor(pos, "3d_armor_stand:armor_stand") armor.drop_armor(pos, "3d_armor_stand:armor_stand")
minetest.remove_node(pos) minetest.remove_node(pos)
end, end
}) })
minetest.register_node("3d_armor_stand:locked_armor_stand", { register_armor_stand({
name = "locked_armor_stand",
description = S("Locked Armor Stand"), description = S("Locked Armor Stand"),
drawtype = "mesh", texture = "3d_armor_stand_locked.png"
mesh = "3d_armor_stand.obj",
tiles = {"3d_armor_stand_locked.png"},
use_texture_alpha = "clip",
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {
{-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
},
},
groups = {choppy=2, oddly_breakable_by_hand=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", armor_stand_formspec)
meta:set_string("infotext", S("Armor Stand"))
meta:set_string("owner", "")
local inv = meta:get_inventory()
for _, element in pairs(elements) do
inv:set_size("armor_"..element, 1)
end
end,
can_dig = function(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
for _, element in pairs(elements) do
if not inv:is_empty("armor_"..element) then
return false
end
end
return true
end,
after_place_node = function(pos, placer)
minetest.add_entity(pos, "3d_armor_stand:armor_entity")
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name() or "")
meta:set_string("infotext", S("Armor Stand (owned by @1)", meta:get_string("owner")))
add_hidden_node(pos, placer)
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
if not has_locked_armor_stand_privilege(meta, player) then
return 0
end
local def = stack:get_definition() or {}
local groups = def.groups or {}
if groups[listname] then
return 1
end
return 0
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
if not has_locked_armor_stand_privilege(meta, player) then
return 0
end
return stack:get_count()
end,
allow_metadata_inventory_move = function(pos)
return 0
end,
on_metadata_inventory_put = function(pos)
update_entity(pos)
end,
on_metadata_inventory_take = function(pos)
update_entity(pos)
end,
after_destruct = function(pos)
update_entity(pos)
remove_hidden_node(pos)
end,
on_blast = function(pos)
-- Not affected by TNT
end,
}) })
minetest.register_node("3d_armor_stand:shared_armor_stand", { register_armor_stand({
name = "shared_armor_stand",
description = S("Shared Armor Stand"), description = S("Shared Armor Stand"),
drawtype = "mesh", texture = "3d_armor_stand_shared.png"
mesh = "3d_armor_stand.obj",
tiles = {"3d_armor_stand_shared.png"},
use_texture_alpha = "clip",
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {
{-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
},
},
groups = {choppy=2, oddly_breakable_by_hand=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", armor_stand_formspec)
meta:set_string("infotext", S("Shared Armor Stand"))
local inv = meta:get_inventory()
for _, element in pairs(elements) do
inv:set_size("armor_"..element, 1)
end
end,
can_dig = function(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
for _, element in pairs(elements) do
if not inv:is_empty("armor_"..element) then
return false
end
end
return true
end,
after_place_node = function(pos, placer)
minetest.add_entity(pos, "3d_armor_stand:armor_entity")
local meta = minetest.get_meta(pos)
meta:set_string("infotext", S("Shared Armor Stand"))
add_hidden_node(pos, placer)
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if not minetest.is_player(player) or minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local def = stack:get_definition() or {}
local groups = def.groups or {}
if groups[listname] then
return 1
end
return 0
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
if not minetest.is_player(player) or minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end,
allow_metadata_inventory_move = function(pos)
return 0
end,
on_metadata_inventory_put = function(pos)
update_entity(pos)
end,
on_metadata_inventory_take = function(pos)
update_entity(pos)
end,
after_destruct = function(pos)
update_entity(pos)
remove_hidden_node(pos)
end,
on_blast = function(pos)
-- Not affected by TNT
end,
}) })
minetest.register_entity("3d_armor_stand:armor_entity", { minetest.register_entity("3d_armor_stand:armor_entity", {
@@ -421,6 +324,32 @@ minetest.register_abm({
end end
}) })
minetest.register_lbm({
label = "Update armor stand inventories",
name = "3d_armor_stand:update_inventories",
nodenames = {"3d_armor_stand:locked_armor_stand", "3d_armor_stand:shared_armor_stand", "3d_armor_stand:armor_stand"},
run_at_every_load = false,
action = function(pos, node)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local lists = inv:get_lists()
for _, element in pairs(elements) do
if not lists["armor_"..element] then
-- Abort to avoid item loss in case env_meta.txt is corrupted/deleted
return
end
end
inv:set_lists({main = {
lists.armor_head[1],
lists.armor_torso[1],
lists.armor_legs[1],
lists.armor_feet[1]
}})
meta:set_string("formspec", armor_stand_formspec)
update_entity(pos)
end
})
minetest.register_craft({ minetest.register_craft({
output = "3d_armor_stand:armor_stand", output = "3d_armor_stand:armor_stand",
recipe = { recipe = {