-- LUALOCALS < --------------------------------------------------------- local ItemStack, math, minetest, nodecore, string, type, vector = ItemStack, math, minetest, nodecore, string, type, vector local math_random, string_format = math.random, string.format -- LUALOCALS > --------------------------------------------------------- local modname = minetest.get_current_modname() local api = _G[modname] local modstore = api.store local storekey = "falling" local cache do local s = modstore:get_string(storekey) cache = s and s ~= "" and minetest.deserialize(s) or {} end local function getfallingthings(ipos) local poskey = minetest.pos_to_string(ipos) local found = cache[poskey] or {list = {}} -- Convert legacy separate item/node lists to combined list if not found.list then found.list = {} end if found.item then local s = found.item local l = found.list for i = 1, #s do l[#l + 1] = {i = s[i]} end found.item = nil end if found.node then local s = found.node local l = found.list for i = 1, #s do l[#l + 1] = s[i] end found.node = nil end return found, function() cache[poskey] = found return modstore:set_string(storekey, minetest.serialize(cache)) end end local function fallingfor(self) local pos = self.object:get_pos() if not (pos and api.in_sky_realm(pos)) then return end local vel = self.object:get_velocity() if (not vel) or vel.y > -30 then return end local oy = pos.y while true do local node = minetest.get_node(pos) if node.name == "ignore" then break end if not api.air[node.name] then return end pos.y = pos.y - 1 end pos.y = oy return api.island_near(pos) or api.island_near(pos, {x = 0, y = 1, z = 0}) end local function itemeffects(pos, thing) api.teleportsound(pos) if type(thing) == "string" then thing = ItemStack(thing):get_name() thing = thing and minetest.registered_items[thing] end return api.particleburst(pos, vector.new(0.25, 0.25, 0.25), thing) end local function poof(self, def) if not def then return self.object:remove() end local pos = self.object:get_pos() itemeffects(pos, def) return self.object:remove() end local function itemshortdesc(stack) stack = ItemStack(stack) local copy = ItemStack(stack:get_name()) copy:set_count(stack:get_count()) return copy:to_string() end nodecore.register_item_entity_step(function(self) local ipos = fallingfor(self) if not ipos then return end local pt, save = getfallingthings(ipos) pt = pt.list local stack = ItemStack(self.itemstring) local iname = stack:get_name() for i = 1, #pt do local pts = ItemStack(pt[i]) stack = pts:add_item(stack) pt[i] = {i = pts:to_string()} if stack:is_empty() then break end end if not stack:is_empty() then pt[#pt + 1] = {i = stack:to_string()} end save() nodecore.log("action", string_format("skyrealm %s dropped item %q at %s total %d", api.islandhash(ipos), itemshortdesc(stack), minetest.pos_to_string(self.object:get_pos(), 0), #pt)) local def = minetest.registered_items[iname] return poof(self, def) end) nodecore.register_falling_node_step(function(self) local ipos = fallingfor(self) if not ipos then return end local pt, save = getfallingthings(ipos) pt = pt.list pt[#pt + 1] = { n = self.node, m = self.meta } save() nodecore.log("action", string_format("skyrealm %s dropped node %q at %s total %d", api.islandhash(ipos), self.node.name, minetest.pos_to_string(self.object:get_pos(), 0), #pt)) local def = minetest.registered_items[self.node.name] return poof(self, def) end) local function findspot_start(pos) if nodecore.near_unloaded(pos, nil, 16) then return end for rel in nodecore.settlescan() do local p = vector.add(pos, rel) if nodecore.buildable_to(p) then return p end end end local function findspot_trace(pos) pos = findspot_start(pos) if not pos then return end local tpos = vector.add(pos, { x = math_random() * 10 - 5, y = math_random() * 10 + 5, z = math_random() * 10 - 5, }) for hit in minetest.raycast(pos, tpos, false, false) do if hit.above and nodecore.buildable_to(hit.above) then return hit.above end end return tpos end local function findspot(pos) pos = findspot_trace(pos) if not pos then return end local below = vector.offset(pos, 0, -1, 0) if not nodecore.buildable_to(below) then return pos end for _ = 1, 16 do pos.y = pos.y - 1 below.y = below.y - 1 if not nodecore.buildable_to(below) then return pos end end end local function returncore(pos, ipos, list) if #list < 1 then return end pos = findspot(pos) if not pos then return end local idx = math_random(1, #list) local pick = list[idx] if not pick then return end if idx ~= #list then list[idx] = list[#list] end list[#list] = nil itemeffects(pos, pick.i or pick.n.name) nodecore.log("action", string_format( "skyrealm %s %s arrived at %s left %d", api.islandhash(ipos), pick.i and string_format("item %q", pick.i) or string_format("node %q", pick.n.name), minetest.pos_to_string(pos), #list)) if pick.i then nodecore.item_eject(pos, pick.i) else nodecore.set_loud(pos, pick.n) minetest.get_meta(pos):from_table(pick.m) end nodecore.fallcheck(pos) return true end local pending = {} local function return_falling_delayed(key, pos, ipos) local data, save = getfallingthings(ipos) if not returncore(pos, ipos, data.list) then pending[key] = nil else save() pending[key] = minetest.after(math_random() / 4, return_falling_delayed, key, pos, ipos) end end function api.return_falling(pos, ipos) local key = minetest.hash_node_position(vector.round(pos)) pending[key] = pending[key] or minetest.after(0, return_falling_delayed, key, pos, ipos) end