Kimapr 41482b830e Metadata in item entity visuals
Send item metadata in item entities using the `wield_item` property,
replacing the deprecated since at least 5.1 `textures[1]` which does not
allow metadata in the itemstring. Only metadata fields known to affect
visuals are sent to the client, count and wear are reset.

This allows item entities to display color and (since 5.8) texture
overrides properly.
2024-01-10 15:08:21 +05:00

295 lines
8.2 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, math, minetest, nodecore, pairs, table, type, vector
= ItemStack, math, minetest, nodecore, pairs, table, type, vector
local math_floor, math_pi, math_random, math_sqrt, table_insert,
table_remove
= math.floor, math.pi, math.random, math.sqrt, table.insert,
table.remove
-- LUALOCALS > ---------------------------------------------------------
function minetest.spawn_falling_node(pos, node, meta)
node = node or minetest.get_node(pos)
if node.name == "air" or node.name == "ignore" then
return false
end
local obj = minetest.add_entity(pos, "__builtin:falling_node")
if obj then
obj:get_luaentity():set_node(node, meta or minetest.get_meta(pos):to_table())
minetest.remove_node(pos)
return obj
end
return false
end
local visible_stackmeta = {
inventory_image = true,
inventory_overlay = true,
wield_image = true,
wield_overlay = true,
wield_scale = true,
color = true,
palette_index = true,
}
local function stack_tostring_clean(stack)
-- Do not send metadata that does not affect stack visuals to the client
stack = ItemStack(stack)
local meta = stack:get_meta():to_table()
for key, _ in pairs(meta.fields) do
if not visible_stackmeta[key] then
meta.fields[key] = nil
end
end
stack:get_meta():from_table(meta)
stack:set_count(1)
stack:set_wear(0)
return stack:to_string()
end
function nodecore.stackentprops(stack, yaw, rotate, ss)
local props = {
hp_max = 1,
physical = false,
collide_with_objects = false,
collisionbox = {0, 0, 0, 0, 0, 0},
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
is_visible = false,
static_save = ss and true or false
}
local scale = 0
yaw = yaw or 0
if stack then
if type(stack) == "string" then stack = ItemStack(stack) end
props.is_visible = not stack:is_empty()
props.wield_item = stack_tostring_clean(stack)
local ratio = stack:get_count() / stack:get_stack_max()
if ratio > 1 then ratio = 1 end
scale = math_sqrt(ratio) * 0.15 + 0.25
props.visual_size = {x = scale, y = scale}
props.automatic_rotate = rotate
and rotate * 2 / math_sqrt(math_sqrt(ratio)) or 0
local def = minetest.registered_items[stack:get_name()]
props.glow = def and (def.glow or def.light_source)
if ratio == 1 then ratio = 1 - (stack:get_wear() / 65536) end
if ratio ~= 1 then yaw = yaw + 1/8 + 3/8 * (1 - ratio) end
yaw = yaw - 2 * math_floor(yaw / 2)
end
return props, scale, yaw * math_pi / 2
end
function nodecore.entity_staticdata_helpers(savedprops)
return function(self, data)
data = data and minetest.deserialize(data) or {}
for k in pairs(savedprops) do self[k] = data[k] end
end,
function(self)
local data = {}
for k in pairs(savedprops) do data[k] = self[k] end
return minetest.serialize(data)
end
end
local area_unloaded = {}
local function collides(pos, ignorewalkable)
if pos.y < nodecore.map_limit_min then return {name = "ignore"} end
local node = minetest.get_node_or_nil(pos)
if not node then return area_unloaded end
local def = minetest.registered_nodes[node.name]
if not def then return node end
if (def.walkable and not ignorewalkable)
or ((def.groups and def.groups.support_falling or 0) > 0)
then return node end
end
local oldcheck = minetest.check_single_for_falling
function minetest.check_single_for_falling(...)
local oldget = minetest.get_node_or_nil
function minetest.get_node_or_nil(pos, ...)
if pos.y < nodecore.map_limit_min then return end
return oldget(pos, ...)
end
local function helper(...)
minetest.get_node_or_nil = oldget
return ...
end
return helper(oldcheck(...))
end
function nodecore.entity_update_maxy(self, pos, vel)
pos = pos or self.object:get_pos()
if not pos then return end
if (not self.maxy) or pos.y > self.maxy then
self.maxy = pos.y
return
end
vel = vel or self.object:get_velocity()
if vel.y > 0 then self.maxy = pos.y end
end
local hash_node_position = minetest.hash_node_position
local round = vector.round
local function yqinsert(yq, ent)
local key = ent.maxy
local grp = yq.ents[key]
if not grp then
grp = {}
yq.ents[key] = grp
local min = 1
local max = #yq.idx
while max > min do
local try = math_floor((min + max) / 2)
if key > yq.idx[try] then
min = try + 1
else
max = try
end
end
table_insert(yq.idx, min, key)
end
grp[#grp + 1] = ent
end
local function yqinsertall(yq, bypos, pos)
local key = hash_node_position(pos)
local list = bypos[key]
if not list then return end
bypos[key] = nil
for e in pairs(list) do
yqinsert(yq, e)
end
end
local entity_settle_recursing
function nodecore.entity_settle_recurse(pos)
if entity_settle_recursing then return end
entity_settle_recursing = true
pos = round(pos)
local blocked = {[hash_node_position(pos)] = true}
local bypos = {}
for _, ent in pairs(minetest.luaentities) do
if ent.settle_check and ent.maxy then
local p = ent.object:get_pos()
if p then
local hash = hash_node_position(round(pos))
local set = bypos[hash]
if not set then
set = {}
bypos[hash] = set
end
set[ent] = true
end
end
end
local queue = {idx = {}, ents = {}}
yqinsertall(queue, bypos, pos)
pos.y = pos.y + 1
yqinsertall(queue, bypos, pos)
while #queue.idx > 0 do
local key = queue.idx[1]
local ents = queue.ents[key]
local ent = ents[#ents]
if #ents == 1 then
queue.ents[key] = nil
table_remove(queue.idx, 1)
else
ents[#ents] = nil
end
local p = round(ent.object:get_pos())
local maxy = math_floor(ent.maxy + 0.5)
while true do
local c = blocked[hash_node_position(p)]
if p.y >= maxy or not (c and c ~= area_unloaded) then
ent.object:set_pos(p)
break
end
p.y = p.y + 1
end
ent:settle_check(0)
if collides(p) then
blocked[hash_node_position(p)] = true
yqinsertall(queue, bypos, p)
p.y = p.y + 1
yqinsertall(queue, bypos, p)
end
end
entity_settle_recursing = nil
end
local function groundpos(moveresult)
if not (moveresult and moveresult.touching_ground) then return end
local collisions = moveresult.collisions
if not collisions then return end
local first = collisions[1]
if not (first and first.type == "node") then return end
local pos = first.nodepos
if not pos then return end
return {x = pos.x, y = pos.y + 0.55, z = pos.z}
end
function nodecore.entity_settle_check(on_settle, isnode)
return function(self, dtime, moveresult)
local pos = groundpos(moveresult) or self.object:get_pos()
if not pos then return end
if pos.y < nodecore.map_limit_min then
pos.y = nodecore.map_limit_min
self.object:set_pos(pos)
local vel = self.object:get_velocity()
vel.y = 0
self.object:set_velocity(vel)
end
-- If stuck in place, we might be stuck on a corner/edge overhanging
-- air; jitter the x/z position to try to settle on the node that's
-- caught us. We might also be stuck for some other reason; in that
-- case, keep increasing the check distance over time, and try to
-- tunnel out into a place we can settle.
if self.settle_oldpos and vector.distance(self.settle_oldpos, pos)
< 1/4 * dtime then
self.settle_stucktime = (self.settle_stucktime or 0) + dtime
local stuck = self.settle_stucktime / 5 - 2
if stuck < 0 then stuck = 0 end
if stuck > 64 then stuck = 64 end
local csize = self.collidesize or 0.5
pos.x = pos.x + (math_random() * 2 - 1) * (csize + stuck)
pos.z = pos.z + (math_random() * 2 - 1) * (csize + stuck)
pos.y = pos.y - (math_random() - 0.25) * stuck
else
self.settle_stucktime = 0
self.settle_oldpos = pos
end
local yvel = self.object:get_velocity().y
local coll = collides({x = pos.x, y = pos.y - 0.75, z = pos.z},
(not isnode) and not (self.not_rising and yvel == 0))
self.not_rising = yvel <= 0
if not coll then
if self.setvel then
if self.vel then self.object:set_velocity(self.vel) end
self.setvel = nil
end
self.vel = self.object:get_velocity()
return nodecore.grav_air_accel_ent(self.object)
end
if coll == area_unloaded then
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.setvel = true
return
end
pos = vector.round(pos)
if not on_settle(self, pos, collides) then return end
nodecore.entity_settle_recurse(pos)
return nodecore.fallcheck(pos)
end
end