Aaron Suen 9c9eb4b85a Guard against dummy objects in 5.3+
Some time in the 5.3 dev stream (docs updated at
217f3a42), object refs started being invalidated
immediately on calling obj:remove(), such that
obj:get_pos() starts to return nil instead of the object's
last known position.

This can cause some crashes in NodeCore, where we
assume that our object is still valid (or usable as if it
were still valid) even though we're looping through
handlers and any one of them may have remove()d the
object before other handlers get a chance to fire.

Instead, just watch for unexpected nil returns from
functions we expect would never return nil (e.g.
get_pos or get_properties) and return if we hit one.
We can assume all other calls will be non-nil after that
one, as long as we stay in the same function flow.
2020-05-18 18:36:06 -04:00

149 lines
4.3 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, math, minetest, nodecore, pairs, type, vector
= ItemStack, math, minetest, nodecore, pairs, type, vector
local math_floor, math_pi, math_random, math_sqrt
= math.floor, math.pi, math.random, math.sqrt
-- 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
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 = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
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.textures[1] = stack:get_name()
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 nil
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)
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 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_settle_check(on_settle, isnode)
return function(self)
local pos = 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 self.settle_oldpos and vector.distance(self.settle_oldpos, pos) < 1/16 then
local csize = self.collidesize or 0.5
pos.x = pos.x + (math_random() * 2 - 1) * csize
pos.z = pos.z + (math_random() * 2 - 1) * csize
else
self.settle_oldpos = pos
end
local yvel = self.object:get_velocity().y
local coll = (isnode or self.not_rising and yvel == 0)
and collides({x = pos.x, y = pos.y - 0.75, z = pos.z})
self.not_rising = yvel <= 0
if not coll then
if self.setvel then
self.object:set_velocity(self.vel)
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
pos.y = pos.y + 1
for _, obj in pairs(nodecore.get_objects_at_pos(pos)) do
obj = obj.get_luaentity and obj:get_luaentity()
if obj and obj.settle_check then
obj:settle_check()
end
end
return nodecore.fallcheck(pos)
end
end