Items and nodes are listed equally in a single array. This makes it a lot easier to fairly select random things from the complete set to return, instead of the de facto stratified random choice with equal weighting between items and nodes regardless of how many of each there is. The stratified weighting wasn't a problem before, when our expectation was that all items and all nodes would be returned instantly. Now that we're spacing things out, order matters more.
207 lines
5.6 KiB
Lua
207 lines
5.6 KiB
Lua
-- 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
|