nodecore-cd2025/mods/nc_api/util_stack.lua
Aaron Suen d414c1f3b0 Move node itemstacks from meta inv to field
Node inventories are sent to clients, while metadata
fields are automatically set private by the server
and not sent to clients.  Not using node meta
inventories may help reduce the amount of data
sent to clients, especially in storage-heavy areas
where we were duplicating the work of sending the
inventory data not used on the client, as well as the
visible item entities.

Automatically convert old-format data to new upon
reading.

Also, needed to provide some APIs for sane access
to data in serialized format, and another way to
signal that a node can accept a stack other than
the size of inventory[solo]
2021-04-10 09:00:39 -04:00

234 lines
6.9 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, math, minetest, nodecore, pairs, string, vector
= ItemStack, math, minetest, nodecore, pairs, string, vector
local math_cos, math_floor, math_pi, math_random, math_sin,
string_format
= math.cos, math.floor, math.pi, math.random, math.sin,
string.format
-- LUALOCALS > ---------------------------------------------------------
local function shortdesc(stack, noqty)
stack = ItemStack(stack)
if noqty and stack:get_count() > 1 then stack:set_count(1) end
local pre = stack:to_string()
stack:get_meta():from_table({})
local desc = stack:to_string()
if pre == desc then return desc end
return string_format("%s @%d", desc, #pre - #desc)
end
nodecore.stack_shortdesc = shortdesc
local function family(stack)
stack = ItemStack(stack)
if stack:is_empty() then return "" end
local name = stack:get_name()
local def = minetest.registered_items[name]
if def and def.stackfamily then
stack:set_name(def.stackfamily)
end
if stack:get_count() > 1 then
stack:set_count(1)
end
return stack:to_string()
end
nodecore.stack_family = family
function nodecore.stack_merge(dest, src)
if dest:is_empty() then return dest:add_item(src) end
if family(src) ~= family(dest) then
return dest:add_item(src)
end
local o = src:get_name()
src:set_name(dest:get_name())
src = dest:add_item(src)
if not src:is_empty() then
src:set_name(o)
end
return src
end
local metakey = "ncitem"
function nodecore.stack_get(pos)
local meta = minetest.get_meta(pos)
local str = meta:get_string(metakey)
if str and str ~= "" then return ItemStack(str) end
local inv = meta:get_inventory()
local stack = inv:get_stack("solo", 1)
if stack:is_empty() then return stack end
meta:set_string(metakey, stack:to_string())
inv:set_size("solo", 0)
return stack
end
function nodecore.stack_get_serial(metatable)
local str = metatable
str = str and str.fields
str = str and str[metakey]
if str and str ~= "" then return ItemStack(str) end
local inv = metatable.inventory
inv = inv and inv.solo
inv = inv and inv[1]
if inv and inv ~= "" then return ItemStack(inv) end
end
local function update(pos, ...)
nodecore.visinv_update_ents(pos)
return ...
end
function nodecore.stack_set(pos, stack, player)
if player then
nodecore.log("action", string_format("%s sets stack %q at %s",
player:get_player_name(), shortdesc(stack), minetest.pos_to_string(pos)))
end
return update(pos, minetest.get_meta(pos):set_string(metakey,
ItemStack(stack):to_string()))
end
function nodecore.stack_add(pos, stack, player)
local node = minetest.get_node(pos)
local def = minetest.registered_items[node.name] or {}
if not def.can_have_itemstack then return end
if def.stack_allow then
local ret = def.stack_allow(pos, node, stack)
if ret == false then return stack end
if ret and ret ~= true then return ret end
end
stack = ItemStack(stack)
local donate = stack:get_count()
local item = nodecore.stack_get(pos)
local exist = item:get_count()
local left = nodecore.stack_merge(item, stack)
nodecore.stack_set(pos, item)
local remain = left:get_count()
if donate ~= remain then
if player then
nodecore.log("action", string_format(
"%s adds stack %q %d + %d = %d + %d at %s",
player:get_player_name(), shortdesc(stack, true),
exist, donate, exist + donate - remain, remain,
minetest.pos_to_string(pos)))
end
nodecore.stack_sounds(pos, "place")
end
return update(pos, left)
end
function nodecore.stack_giveto(pos, player)
local stack = nodecore.stack_get(pos)
local qty = stack:get_count()
if qty < 1 then return true end
local left = player:get_inventory():add_item("main", stack)
local remain = left:get_count()
if remain == qty then return stack:is_empty() end
nodecore.log("action", string_format(
"%s takes stack %q %d - %d = %d at %s",
player:get_player_name(), shortdesc(stack, true),
qty, qty - remain, remain, minetest.pos_to_string(pos)))
nodecore.stack_sounds(pos, "dug")
nodecore.stack_set(pos, left)
return stack:is_empty()
end
function nodecore.item_eject(pos, stack, speed, qty, vel)
stack = ItemStack(stack)
speed = speed or 0
vel = vel or {x = 0, y = 0, z = 0}
if speed == 0 and vel.x == 0 and vel.y == 0 and vel.z == 0
and nodecore.place_stack and minetest.get_node(pos).name == "air" then
stack:set_count(stack:get_count() * (qty or 1))
return nodecore.place_stack(pos, stack)
end
for _ = 1, (qty or 1) do
local v = {x = vel.x, y = vel.y, z = vel.z}
if speed > 0 then
local inc = math_random() * math_pi / 3
local y = math_sin(inc)
local xz = math_cos(inc)
local theta = math_random() * math_pi * 2
local x = math_sin(theta) * xz
local z = math_cos(theta) * xz
v = {
x = v.x + x * speed,
y = v.y + y * speed,
z = v.z + z * speed
}
end
local p = {x = pos.x, y = pos.y + 0.25, z = pos.z}
local obj = minetest.add_item(p, stack)
if obj then obj:set_velocity(v) end
end
end
do
local stddirs = {}
for _, v in pairs(nodecore.dirs()) do
if v.y <= 0 then stddirs[#stddirs + 1] = v end
end
function nodecore.item_disperse(pos, name, qty, outdirs)
if qty < 1 then return end
local dirs = {}
for _, d in pairs(outdirs or stddirs) do
local p = vector.add(pos, d)
if nodecore.buildable_to(p) then
dirs[#dirs + 1] = {pos = p, qty = 0}
end
end
if #dirs < 1 then
return nodecore.item_eject(pos, name .. " " .. qty)
end
for _ = 1, qty do
local p = dirs[math_random(1, #dirs)]
p.qty = p.qty + 1
end
for _, v in pairs(dirs) do
if v.qty > 0 then
nodecore.item_eject(v.pos, name .. " " .. v.qty)
end
end
end
end
local function item_lose(player, listname, slot, speed)
local inv = player:get_inventory()
local stack = inv:get_stack(listname, slot)
if stack:is_empty() or nodecore.item_is_virtual(stack) then return end
local pos = player:get_pos()
pos.y = pos.y + player:get_properties().eye_height
local def = stack:get_definition() or {}
if def.on_drop
and def.on_drop ~= minetest.item_drop
and def.on_drop ~= minetest.nodedef_default.on_drop
and def.on_drop ~= minetest.craftitemdef_default.on_drop
and def.on_drop ~= minetest.tooldef_default.on_drop
and def.on_drop ~= minetest.noneitemdef_default.on_drop then
nodecore.log("action", string_format("%s loses item %q at %s by on_drop",
player:get_player_name(), shortdesc(stack),
minetest.pos_to_string(pos, 0)))
stack = def.on_drop(stack, player, pos)
return inv:set_stack(listname, slot, stack)
end
nodecore.log("action", string_format("%s loses item %q at %s by eject(%d)",
player:get_player_name(), shortdesc(stack),
minetest.pos_to_string(pos, 0), math_floor(speed + 0.5)))
nodecore.item_eject(pos, stack, speed)
return inv:set_stack(listname, slot, "")
end
nodecore.item_lose = item_lose
function nodecore.inventory_dump(player)
for listname, list in pairs(player:get_inventory():get_lists()) do
if listname ~= "hand" then
for slot in pairs(list) do
item_lose(player, listname, slot, 0.001)
end
end
end
end