187 lines
5.6 KiB
Lua
187 lines
5.6 KiB
Lua
local SPAWNRADIUS = 21
|
|
local MAX_SPAWN_HEIGHT = 2
|
|
|
|
-- Cache spawnable blocks
|
|
local spawnable_blocks = { ignore = false }
|
|
local spawnable_blocks_in = { air = true, ignore = false }
|
|
|
|
-- Returns true if the node can be spawned into
|
|
local can_spawn_in = function(nodename)
|
|
-- Check cache
|
|
if spawnable_blocks_in[nodename] ~= nil then
|
|
return spawnable_blocks_in[nodename]
|
|
end
|
|
spawnable_blocks_in[nodename] = false
|
|
if minetest.get_item_group(nodename, "spawn_allowed_in") > 0 then
|
|
spawnable_blocks_in[nodename] = true
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
-- Returns true if the node with the given nodename can be spawned on
|
|
local can_spawn_on = function(nodename)
|
|
-- Check cache
|
|
if spawnable_blocks[nodename] ~= nil then
|
|
return spawnable_blocks[nodename]
|
|
end
|
|
spawnable_blocks[nodename] = false
|
|
local def = minetest.registered_nodes[nodename]
|
|
if not def then
|
|
return false
|
|
end
|
|
if def.walkable then
|
|
local no_spawn = minetest.get_item_group(nodename, "no_spawn_allowed_on") == 1
|
|
if no_spawn then
|
|
return false
|
|
end
|
|
if def.damage_per_second > 0 then
|
|
return false
|
|
end
|
|
local slab = minetest.get_item_group(nodename, "slab") > 0
|
|
local stair = minetest.get_item_group(nodename, "stair") > 0
|
|
if slab or stair then
|
|
spawnable_blocks[nodename] = true
|
|
return true
|
|
end
|
|
if def.collision_box then
|
|
if def.collision_box.type == "regular" then
|
|
spawnable_blocks[nodename] = true
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
spawnable_blocks[nodename] = true
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Returns true if player can spawn at pos1.
|
|
-- Checks if pos1 and the pos above pos1 are air,
|
|
-- and if the pos below pos1 can be stood on.
|
|
local is_valid_spawn_pos = function(pos1)
|
|
local pos2 = vector.add(pos1, vector.new(0,1,0)) -- above
|
|
local pos0 = vector.add(pos1, vector.new(0,-1,0)) -- floor
|
|
|
|
local node1 = minetest.get_node(pos1)
|
|
local node2 = minetest.get_node(pos2)
|
|
local node0 = minetest.get_node(pos0)
|
|
if node0.name == "ignore" then
|
|
return false
|
|
end
|
|
if can_spawn_in(node1.name) and can_spawn_in(node2.name) and can_spawn_on(node0.name) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Checks pos as well as a number of positions above pos whether they are spawnable
|
|
local check_spawn_at_and_above_pos = function(pos)
|
|
for h = 0, MAX_SPAWN_HEIGHT do
|
|
local spawn = vector.new(pos.x, pos.y+h, pos.z)
|
|
local valid = is_valid_spawn_pos(spawn)
|
|
if valid then
|
|
return spawn
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local neighbors = {
|
|
vector.new(-1,0,-1),
|
|
vector.new(0,0,-1),
|
|
vector.new(1,0,1),
|
|
vector.new(-1,0,0),
|
|
vector.new(1,0,0),
|
|
vector.new(-1,0,1),
|
|
vector.new(0,0,1),
|
|
vector.new(1,0,1),
|
|
}
|
|
|
|
-- Given a position pos, searches around pos to find
|
|
-- a safe position to spawn in. Might not succeeed in
|
|
-- very difficult terrain. Returns spawn position on success
|
|
-- and nil on failure
|
|
local find_spawn_nearby = function(pos)
|
|
local npos = check_spawn_at_and_above_pos(pos)
|
|
if npos then
|
|
return npos
|
|
end
|
|
local nneighbors = table.copy(neighbors)
|
|
while #nneighbors > 0 do
|
|
local n = math.random(1, #nneighbors)
|
|
local neighbor = nneighbors[n]
|
|
npos = vector.add(pos, neighbor)
|
|
local y = minetest.get_spawn_level(npos.x,npos.z)
|
|
if y then
|
|
y = y - 1
|
|
npos.y = y
|
|
npos = check_spawn_at_and_above_pos(npos)
|
|
if npos then
|
|
return npos
|
|
end
|
|
end
|
|
table.remove(nneighbors, n)
|
|
end
|
|
for r=2, SPAWNRADIUS do
|
|
for j=1, 20 do
|
|
local x = pos.x + math.random(-r, r)
|
|
local z = pos.z + math.random(-r, r)
|
|
local y = minetest.get_spawn_level(x,z)
|
|
if y then
|
|
y = y - 1
|
|
local spawn = vector.new(x, y, z)
|
|
spawn = check_spawn_at_and_above_pos(spawn)
|
|
if spawn then
|
|
return spawn
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check for alternative spawn position if the map emerge was successful
|
|
local function emerge_callback(blockpos, action, calls_remaining, param)
|
|
if calls_remaining == 0 and (action == minetest.EMERGE_FROM_MEMORY or action == minetest.EMERGE_FROM_DISK or action == minetest.EMERGE_GENERATED) then
|
|
if not param.player or not param.player:is_player() then
|
|
return
|
|
end
|
|
local emerge_timer2 = minetest.get_us_time()
|
|
local time = emerge_timer2 - param.timer
|
|
minetest.log("info", "[rp_safespawn] Time needed to emerge map for spawning: "..time.." µs")
|
|
|
|
local ppos_cb = param.ppos
|
|
if not is_valid_spawn_pos(ppos_cb) then
|
|
minetest.log("action", "[rp_safespawn] Player spawn for "..param.player:get_player_name().." was indeed bad. Searching for new spawn ...")
|
|
local newspawn = find_spawn_nearby(ppos_cb)
|
|
if newspawn then
|
|
minetest.log("action", "[rp_safespawn] Spawning "..param.player:get_player_name().." at new position: "..minetest.pos_to_string(newspawn, 0))
|
|
param.player:set_pos(newspawn)
|
|
else
|
|
minetest.log("action", "[rp_safespawn] Failed to find new spawn position for "..param.player:get_player_name()..". Not moving player.")
|
|
end
|
|
else
|
|
minetest.log("action", "[rp_safespawn] Player spawn for "..param.player:get_player_name().." was OK. Not moving player.")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check if the player is at a valid position. If not, emerged the map around player
|
|
-- and checks again.
|
|
local function spawngen(player)
|
|
local ppos = player:get_pos()
|
|
local node = minetest.get_node(ppos)
|
|
local timer = minetest.get_us_time()
|
|
if not is_valid_spawn_pos(ppos) then
|
|
minetest.log("action", "[rp_safespawn] Player "..player:get_player_name().." spawns at bad or unloaded position "..minetest.pos_to_string(ppos)..". Emerging map to check spawn ...")
|
|
local min = vector.add(ppos, vector.new(-5,-5,-5))
|
|
local max = vector.add(ppos, vector.new(5,5,5))
|
|
minetest.emerge_area(min, max, emerge_callback, {ppos=ppos,timer=timer,player=player})
|
|
end
|
|
end
|
|
|
|
minetest.register_on_newplayer(spawngen)
|