master #5

Merged
epCode merged 255 commits from VoxeLibre/VoxeLibre:master into master 2021-02-02 23:20:01 +01:00
7 changed files with 477 additions and 247 deletions
Showing only changes of commit be5a228bad - Show all commits

View File

@ -1,8 +1,8 @@
# MineClone 2 # MineClone 2
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils. An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
Developed by Wuzzy and contributors. Not developed or endorsed by Mojang AB. Developed by many people. Not developed or endorsed by Mojang AB.
Version: 0.69.1 Version: 0.70.0
### Gameplay ### Gameplay
You start in a randomly-generated world made entirely of cubes. You can explore You start in a randomly-generated world made entirely of cubes. You can explore
@ -195,7 +195,7 @@ Please report all bugs and missing Minecraft features here:
There are so many people to list (sorry). Check out the respective mod directories for details. This section is only a rough overview of the core authors of this game. There are so many people to list (sorry). Check out the respective mod directories for details. This section is only a rough overview of the core authors of this game.
### Coding ### Coding
* [Wuzzy](https://forum.minetest.net/memberlist.php?mode=viewprofile&u=3082): Main programmer of most mods * [Wuzzy](https://forum.minetest.net/memberlist.php?mode=viewprofile&u=3082): Main programmer of most mods (retired)
* davedevils: Creator of MineClone on which MineClone 2 is based on * davedevils: Creator of MineClone on which MineClone 2 is based on
* [ex-bart](https://github.com/ex-bart): Redstone comparators * [ex-bart](https://github.com/ex-bart): Redstone comparators
* [Rootyjr](https://github.com/Rootyjr): Fishing rod and bugfixes * [Rootyjr](https://github.com/Rootyjr): Fishing rod and bugfixes

View File

@ -65,7 +65,7 @@ minetest.register_globalstep(function(dtime)
if is_immortal or not enable_damage then if is_immortal or not enable_damage then
-- If damage is disabled, we can't kill players. -- If damage is disabled, we can't kill players.
-- So we just teleport the player back to spawn. -- So we just teleport the player back to spawn.
local spawn = mcl_spawn.get_spawn_pos(player) local spawn = mcl_spawn.get_player_spawn_pos(player)
player:set_pos(spawn) player:set_pos(spawn)
mcl_worlds.dimension_change(player, mcl_worlds.pos_to_dimension(spawn)) mcl_worlds.dimension_change(player, mcl_worlds.pos_to_dimension(spawn))
minetest.chat_send_player(player:get_player_name(), S("The void is off-limits to you!")) minetest.chat_send_player(player:get_player_name(), S("The void is off-limits to you!"))

View File

@ -284,6 +284,7 @@ if realtime then
mcl_observers.set_node = minetest.set_node mcl_observers.set_node = minetest.set_node
mcl_observers.swap_node = minetest.swap_node mcl_observers.swap_node = minetest.swap_node
mcl_observers.remove_node = minetest.remove_node mcl_observers.remove_node = minetest.remove_node
mcl_observers.bulk_set_node = minetest.bulk_set_node
minetest.add_node=function(pos,node) minetest.add_node=function(pos,node)
mcl_observers.add_node(pos,node) mcl_observers.add_node(pos,node)
@ -393,6 +394,35 @@ if realtime then
mcl_observers.observer_activate({x=pos.x,y=pos.y+1,z=pos.z}) mcl_observers.observer_activate({x=pos.x,y=pos.y+1,z=pos.z})
end end
end end
minetest.bulk_set_node=function(lst, node)
mcl_observers.bulk_set_node(lst, node)
for _, pos in pairs(lst) do
local n=minetest.get_node({x=pos.x+1,y=pos.y,z=pos.z})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_o" and minetest.facedir_to_dir(n.param2).x==-1 then
mcl_observers.observer_activate({x=pos.x+1,y=pos.y,z=pos.z})
end
n=minetest.get_node({x=pos.x-1,y=pos.y,z=pos.z})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_o" and minetest.facedir_to_dir(n.param2).x==1 then
mcl_observers.observer_activate({x=pos.x-1,y=pos.y,z=pos.z})
end
n=minetest.get_node({x=pos.x,y=pos.y,z=pos.z+1})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_o" and minetest.facedir_to_dir(n.param2).z==-1 then
mcl_observers.observer_activate({x=pos.x,y=pos.y,z=pos.z+1})
end
n=minetest.get_node({x=pos.x,y=pos.y,z=pos.z-1})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_o" and minetest.facedir_to_dir(n.param2).z==1 then
mcl_observers.observer_activate({x=pos.x,y=pos.y,z=pos.z-1})
end
n=minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_u" then
mcl_observers.observer_activate({x=pos.x,y=pos.y-1,z=pos.z})
end
n=minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z})
if n and n.name and string.sub(n.name,1,24)=="mcl_observers:observer_d" then
mcl_observers.observer_activate({x=pos.x,y=pos.y+1,z=pos.z})
end
end
end
else -- if realtime then ^^^ else: else -- if realtime then ^^^ else:
minetest.register_abm({ minetest.register_abm({

View File

@ -1,23 +1,23 @@
local S = minetest.get_translator("mcl_beds") local S = minetest.get_translator("mcl_beds")
local reverse = true local function destruct_bed(pos, oldnode)
local node = oldnode or minetest.get_node(pos)
local function destruct_bed(pos, is_top) if not node then return end
local node = minetest.get_node(pos)
local other
local dir = minetest.facedir_to_dir(node.param2) local dir = minetest.facedir_to_dir(node.param2)
if is_top then local pos2, node2
other = vector.subtract(pos, dir) if string.sub(node.name, -4) == "_top" then
else pos2 = vector.subtract(pos, dir)
other = vector.add(pos, dir) node2 = minetest.get_node(pos2)
if node2 and string.sub(node2.name, -7) == "_bottom" then
minetest.remove_node(pos2)
end
minetest.check_for_falling(pos)
elseif string.sub(node.name, -7) == "_bottom" then
pos2 = vector.add(pos, dir)
node2 = minetest.get_node(pos2)
if node2 and string.sub(node2.name, -4) == "_top" then
minetest.remove_node(pos2)
end end
if reverse then
reverse = not reverse
minetest.remove_node(other)
minetest.check_for_falling(other)
else
reverse = not reverse
end end
end end
@ -139,10 +139,7 @@ function mcl_beds.register_bed(name, def)
return itemstack return itemstack
end, end,
on_destruct = function(pos) after_destruct = destruct_bed,
destruct_bed(pos, false)
kick_player_after_destruct(pos)
end,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
mcl_beds.on_rightclick(pos, clicker, false) mcl_beds.on_rightclick(pos, clicker, false)
@ -205,7 +202,7 @@ function mcl_beds.register_bed(name, def)
_mcl_hardness = 0.2, _mcl_hardness = 0.2,
_mcl_blast_resistance = 1, _mcl_blast_resistance = 1,
sounds = def.sounds or default_sounds, sounds = def.sounds or default_sounds,
drop = name .. "_bottom", drop = "",
node_box = node_box_top, node_box = node_box_top,
selection_box = selection_box_top, selection_box = selection_box_top,
collision_box = collision_box_top, collision_box = collision_box_top,
@ -214,10 +211,7 @@ function mcl_beds.register_bed(name, def)
return itemstack return itemstack
end, end,
on_rotate = false, on_rotate = false,
on_destruct = function(pos) after_destruct = destruct_bed,
destruct_bed(pos, true)
kick_player_after_destruct(pos)
end,
}) })
minetest.register_alias(name, name .. "_bottom") minetest.register_alias(name, name .. "_bottom")

View File

@ -21,6 +21,21 @@ local destroy_portal = function(pos)
end end
end end
local ep_scheme = {
{ o={x=0, y=0, z=1}, p=3 },
{ o={x=0, y=0, z=2}, p=3 },
{ o={x=0, y=0, z=3}, p=3 },
{ o={x=1, y=0, z=4}, p=0 },
{ o={x=2, y=0, z=4}, p=0 },
{ o={x=3, y=0, z=4}, p=0 },
{ o={x=4, y=0, z=3}, p=1 },
{ o={x=4, y=0, z=2}, p=1 },
{ o={x=4, y=0, z=1}, p=1 },
{ o={x=3, y=0, z=0}, p=2 },
{ o={x=2, y=0, z=0}, p=2 },
{ o={x=1, y=0, z=0}, p=2 },
}
-- End portal -- End portal
minetest.register_node("mcl_portals:portal_end", { minetest.register_node("mcl_portals:portal_end", {
description = S("End Portal"), description = S("End Portal"),
@ -52,7 +67,7 @@ minetest.register_node("mcl_portals:portal_end", {
paramtype = "light", paramtype = "light",
sunlight_propagates = true, sunlight_propagates = true,
use_texture_alpha = true, use_texture_alpha = true,
walkable = true, walkable = false,
diggable = false, diggable = false,
pointable = false, pointable = false,
buildable_to = false, buildable_to = false,
@ -107,97 +122,19 @@ end
-- Check if pos is part of a valid end portal frame, filled with eyes of ender. -- Check if pos is part of a valid end portal frame, filled with eyes of ender.
local function check_end_portal_frame(pos) local function check_end_portal_frame(pos)
-- Check if pos has an end portal frame with eye of ender for i = 1, 12 do
local eframe = function(pos, param2) local pos0 = vector.subtract(pos, ep_scheme[i].o)
local node = minetest.get_node(pos) local portal = true
if node.name == "mcl_portals:end_portal_frame_eye" then for j = 1, 12 do
if param2 == nil or node.param2 == param2 then local p = vector.add(pos0, ep_scheme[j].o)
return true, node local node = minetest.get_node(p)
end if not node or node.name ~= "mcl_portals:end_portal_frame_eye" or node.param2 ~= ep_scheme[j].p then
end portal = false
return false
end
-- Step 1: Find a row of 3 end portal frames with eyes, all facing the same direction
local streak = 0
local streak_start, streak_end, streak_start_node, streak_end_node
local last_param2
local axes = { "x", "z" }
for a=1, #axes do
local axis = axes[a]
for b=pos[axis]-2, pos[axis]+2 do
local cpos = table.copy(pos)
cpos[axis] = b
local e, node = eframe(cpos, last_param2)
if e then
last_param2 = node.param2
streak = streak + 1
if streak == 1 then
streak_start = table.copy(pos)
streak_start[axis] = b
streak_start_node = node
elseif streak == 3 then
streak_end = table.copy(pos)
streak_end[axis] = b
streak_end_node = node
break break
end end
else
streak = 0
last_param2 = nil
end end
end if portal then
if streak_end then return true, {x=pos0.x+1, y=pos0.y, z=pos0.z+1}
break
end
streak = 0
last_param2 = nil
end
-- Has a row been found?
if streak_end then
-- Step 2: Using the known facedir, check the remaining spots in which we expect
-- “eyed” end portal frames.
local dir = minetest.facedir_to_dir(streak_start_node.param2)
if dir.x ~= 0 then
for i=1, 3 do
if not eframe({x=streak_start.x + i*dir.x, y=streak_start.y, z=streak_start.z - 1}) then
return false
end
if not eframe({x=streak_start.x + i*dir.x, y=streak_start.y, z=streak_end.z + 1}) then
return false
end
if not eframe({x=streak_start.x + 4*dir.x, y=streak_start.y, z=streak_start.z + i-1}) then
return false
end
end
-- All checks survived! We have a valid portal!
local k
if dir.x > 0 then
k = 1
else
k = -3
end
return true, { x = streak_start.x + k, y = streak_start.y, z = streak_start.z }
elseif dir.z ~= 0 then
for i=1, 3 do
if not eframe({x=streak_start.x - 1, y=streak_start.y, z=streak_start.z + i*dir.z}) then
return false
end
if not eframe({x=streak_end.x + 1, y=streak_start.y, z=streak_start.z + i*dir.z}) then
return false
end
if not eframe({x=streak_start.x + i-1, y=streak_start.y, z=streak_start.z + 4*dir.z}) then
return false
end
end
local k
if dir.z > 0 then
k = 1
else
k = -3
end
-- All checks survived! We have a valid portal!
return true, { x = streak_start.x, y = streak_start.y, z = streak_start.z + k }
end end
end end
return false return false
@ -222,34 +159,18 @@ local function end_portal_area(pos, destroy)
minetest.bulk_set_node(posses, {name=name}) minetest.bulk_set_node(posses, {name=name})
end end
minetest.register_abm({ function mcl_portals.end_teleport(obj, pos)
label = "End portal teleportation", if not obj then return end
nodenames = {"mcl_portals:portal_end"}, local pos = pos or obj:get_pos()
interval = 0.1, if not pos then return end
chance = 1,
action = function(pos, node)
for _,obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel
if obj:is_player() or lua_entity then
local dim = mcl_worlds.pos_to_dimension(pos) local dim = mcl_worlds.pos_to_dimension(pos)
local objpos = obj:get_pos()
if objpos == nil then
return
end
-- Check if object is actually in portal.
objpos.y = math.ceil(objpos.y)
if minetest.get_node(objpos).name ~= "mcl_portals:portal_end" then
return
end
local target local target
if dim == "end" then if dim == "end" then
-- End portal in the End: -- End portal in the End:
-- Teleport back to the player's spawn or world spawn in the Overworld. -- Teleport back to the player's spawn or world spawn in the Overworld.
if obj:is_player() then if obj:is_player() then
_, target = mcl_spawn.spawn(obj) target = mcl_spawn.get_player_spawn_pos(obj)
end end
target = target or mcl_spawn.get_world_spawn_pos(obj) target = target or mcl_spawn.get_world_spawn_pos(obj)
@ -295,9 +216,35 @@ minetest.register_abm({
mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target)) mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target))
minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}, true) minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}, true)
end end
end
function mcl_portals.end_portal_teleport(pos, node)
for _,obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel
if obj:is_player() or lua_entity then
local objpos = obj:get_pos()
if objpos == nil then
return
end
-- Check if object is actually in portal.
objpos.y = math.ceil(objpos.y)
if minetest.get_node(objpos).name ~= "mcl_portals:portal_end" then
return
end
mcl_portals.end_teleport(obj, objpos)
end end
end end
end, end
minetest.register_abm({
label = "End portal teleportation",
nodenames = {"mcl_portals:portal_end"},
interval = 0.1,
chance = 1,
action = mcl_portals.end_portal_teleport,
}) })
local rotate_frame, rotate_frame_eye local rotate_frame, rotate_frame_eye

View File

@ -164,3 +164,37 @@ minetest.register_globalstep(function(dtime)
end end
end end
end) end)
-- Don't change HP if the player falls in the water or through End Portal:
minetest.register_on_player_hpchange(function(player, hp_change, reason)
if reason and reason.type == "fall" and player then
local pos = player:get_pos()
local node = minetest.get_node(pos)
local velocity = player:get_velocity() or player:get_player_velocity() or {x=0,y=-10,z=0}
local v_axis_max = math.max(math.abs(velocity.x), math.abs(velocity.y), math.abs(velocity.z))
local step = {x = velocity.x / v_axis_max, y = velocity.y / v_axis_max, z = velocity.z / v_axis_max}
for i = 1, math.ceil(v_axis_max/5)+1 do -- trace at least 1/5 of the way per second
if not node or node.name == "ignore" then
minetest.get_voxel_manip():read_from_map(pos, pos)
node = minetest.get_node(pos)
end
if node then
if minetest.registered_nodes[node.name].walkable then
return hp_change
end
if minetest.get_item_group(node.name, "water") ~= 0 then
return 0
end
if node.name == "mcl_portals:portal_end" then
if mcl_portals and mcl_portals.end_teleport then
mcl_portals.end_teleport(player)
end
return 0
end
end
pos = vector.add(pos, step)
node = minetest.get_node(pos)
end
end
return hp_change
end, true)

View File

@ -2,7 +2,47 @@ mcl_spawn = {}
local S = minetest.get_translator("mcl_spawn") local S = minetest.get_translator("mcl_spawn")
local mg_name = minetest.get_mapgen_setting("mg_name") local mg_name = minetest.get_mapgen_setting("mg_name")
local storage = minetest.get_mod_storage()
-- Parameters
-------------
local respawn_search_interval = 30 -- seconds
local respawn_search_initial_delay = 30 -- seconds
local node_groups_white_list = {"group:soil"}
local biomes_white_list = {
"ColdTaiga",
"Taiga",
"MegaTaiga",
"MegaSpruceTaiga",
"Plains",
"SunflowerPlains",
"Forest",
"FlowerForest",
"BirchForest",
"BirchForestM",
"Jungle",
"JungleM",
"JungleEdge",
"JungleEdgeM",
"Savanna",
"SavannaM",
}
-- Resolution of search grid in nodes.
local res = 64
local half_res = 32 -- for emerge areas around the position
local alt_min = -10
local alt_max = 200
-- Number of points checked in the square search grid (edge * edge).
local checks = 128 * 128
-- Starting point for biome checks. This also sets the y co-ordinate for all
-- points checked, so the suitable biomes must be active at this y.
local start_pos = minetest.setting_get_pos("static_spawnpoint") or {x = 0, y = 8, z = 0}
-- Table of suitable biomes
local biome_ids = {}
-- Bed spawning offsets
local node_search_list = local node_search_list =
{ {
--[[1]] {x = 0, y = 0, z = -1}, -- --[[1]] {x = 0, y = 0, z = -1}, --
@ -19,41 +59,205 @@ local node_search_list =
--[[C]] {x = 0, y = 1, z = 1}, -- --[[C]] {x = 0, y = 1, z = 1}, --
} }
local cached_world_spawn -- End of parameters
--------------------
-- Initial variables
local success = storage:get_int("mcl_spawn_success")==1
local searched = (storage:get_int("mcl_spawn_searched")==1) or mg_name == "v6" or mg_name == "singlenode" or minetest.settings:get("static_spawnpoint")
local wsp = minetest.string_to_pos(storage:get_string("mcl_spawn_world_spawn_point")) or {} -- world spawn position
local check = storage:get_int("mcl_spawn_check") or 0
local cp = minetest.string_to_pos(storage:get_string("mcl_spawn_cp")) or {x=start_pos.x, y=start_pos.y, z=start_pos.z}
local edge_len = storage:get_int("mcl_spawn_edge_len") or 1
local edge_dist = storage:get_int("mcl_spawn_edge_dist") or 0
local dir_step = storage:get_int("mcl_spawn_dir_step") or 0
local dir_ind = storage:get_int("mcl_spawn_dir_ind") or 1
-- Get world 'mapgen_limit' and 'chunksize' to calculate 'spawn_limit'.
-- This accounts for how mapchunks are not generated if they or their shell exceed
-- 'mapgen_limit'.
local mapgen_limit = tonumber(minetest.get_mapgen_setting("mapgen_limit"))
local chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))
local spawn_limit = math.max(mapgen_limit - (chunksize + 1) * 16, 0)
--Functions
-----------
local function get_far_node(pos)
local node = minetest.get_node(pos)
if node.name ~= "ignore" then
return node
end
minetest.get_voxel_manip():read_from_map(pos, pos)
return minetest.get_node(pos)
end
local function good_for_respawn(pos, player)
local pos0 = {x = pos.x, y = pos.y - 1, z = pos.z}
local pos1 = {x = pos.x, y = pos.y, z = pos.z}
local pos2 = {x = pos.x, y = pos.y + 1, z = pos.z}
local node0 = get_far_node(pos0)
local node1 = get_far_node(pos1)
local node2 = get_far_node(pos2)
local nn0, nn1, nn2 = node0.name, node1.name, node2.name
if minetest.get_item_group(nn0, "destroys_items") ~=0
or minetest.get_item_group(nn1, "destroys_items") ~=0
or minetest.get_item_group(nn2, "destroys_items") ~=0
or minetest.get_item_group(nn0, "portal") ~=0
or minetest.get_item_group(nn1, "portal") ~=0
or minetest.get_item_group(nn2, "portal") ~=0
or minetest.is_protected(pos0, player or "")
or minetest.is_protected(pos1, player or "")
or minetest.is_protected(pos2, player or "")
or (not player and minetest.get_node_light(pos1, 0.5) < 8)
or (not player and minetest.get_node_light(pos2, 0.5) < 8)
then
return false
end
local def0 = minetest.registered_nodes[nn0]
local def1 = minetest.registered_nodes[nn1]
local def2 = minetest.registered_nodes[nn2]
return def0.walkable and (not def1.walkable) and (not def2.walkable) and
(def1.damage_per_second == nil or def2.damage_per_second <= 0) and
(def1.damage_per_second == nil or def2.damage_per_second <= 0)
end
local function can_find_tree(pos1)
local trees = minetest.find_nodes_in_area(vector.subtract(pos1,half_res), vector.add(pos1,half_res), {"group:tree"}, false)
for _, pos2 in ipairs(trees) do
if not minetest.is_protected(pos2, "") then
if pos2.x < pos1.x then
pos2.x = pos2.x + 1
elseif pos2.x > pos1.x then
pos2.x = pos2.x - 1
end
if pos2.z < pos1.z then
pos2.z = pos2.z + 1
elseif pos2.z > pos1.z then
pos2.z = pos2.z - 1
end
local way = minetest.find_path(pos1, pos2, res, 1, 3, "A*_noprefetch")
if way then
return true
end
end
end
return false
end
local function next_pos()
if edge_dist >= edge_len then
edge_dist = 1
dir_ind = (dir_ind % 4) + 1
dir_step = dir_step + 1
edge_len = math.floor(dir_step / 2) + 1
else
edge_dist = edge_dist + 1
end
if dir_ind==1 then
cp.z = cp.z + res
elseif dir_ind==2 then
cp.x = cp.x - res
elseif dir_ind==3 then
cp.z = cp.z - res
else
cp.x = cp.x + res
end
end
-- Spawn position search
local function next_biome()
while check <= checks do
local biome_data = minetest.get_biome_data(cp)
-- Sometimes biome_data is nil
local biome = biome_data and biome_data.biome
if biome then
minetest.log("verbose", "[mcl_spawn] Search white-listed biome at "..minetest.pos_to_string(cp)..": "..minetest.get_biome_name(biome))
for _, biome_id in ipairs(biome_ids) do
if biome == biome_id then
cp.y = minetest.get_spawn_level(cp.x, cp.z) or start_pos.y
if cp.y then
wsp = {x = cp.x, y = cp.y, z = cp.z}
return true
end
break
end
end
end
next_pos()
-- Check for position being outside world edge
if math.abs(cp.x) > spawn_limit or math.abs(cp.z) > spawn_limit then
check = checks + 1
return false
end
check = check + 1
end
return false
end
local function ecb_search_continue(blockpos, action, calls_remaining, param)
if calls_remaining <= 0 then
local pos1 = {x = wsp.x-half_res, y = alt_min, z = wsp.z-half_res}
local pos2 = {x = wsp.x+half_res, y = alt_max, z = wsp.z+half_res}
local nodes = minetest.find_nodes_in_area_under_air(pos1, pos2, node_groups_white_list)
minetest.log("verbose", "[mcl_spawn] Data emerge callback: "..minetest.pos_to_string(wsp).." - "..tostring(nodes and #nodes) .. " node(s) found under air")
if nodes then
for i=1, #nodes do
wsp = nodes[i]
if wsp then
wsp.y = wsp.y + 1
if good_for_respawn(wsp) and can_find_tree(wsp) then
minetest.log("action", "[mcl_spawn] Dynamic world spawn determined to be "..minetest.pos_to_string(wsp))
searched = true
success = true
return
end
end
end
end
next_pos()
mcl_spawn.search()
end
end
function mcl_spawn.search()
if not next_biome() or check > checks then
return false
end
check = check + 1
if not wsp.y then
wsp.y = 8
end
local pos1 = {x = wsp.x-half_res, y = alt_min, z = wsp.z-half_res}
local pos2 = {x = wsp.x+half_res, y = alt_max, z = wsp.z+half_res}
minetest.emerge_area(pos1, pos2, ecb_search_continue)
end
mcl_spawn.get_world_spawn_pos = function() mcl_spawn.get_world_spawn_pos = function()
local spawn if success then
spawn = minetest.setting_get_pos("static_spawnpoint") return wsp
if spawn then
return spawn
end
if cached_world_spawn then
return cached_world_spawn
end
-- 32 attempts to find a suitable spawn point
spawn = { x=math.random(-16, 16), y=8, z=math.random(-16, 16) }
for i=1, 32 do
local y = minetest.get_spawn_level(spawn.x, spawn.z)
if y then
spawn.y = y
cached_world_spawn = spawn
minetest.log("action", "[mcl_spawn] Dynamic world spawn determined to be "..minetest.pos_to_string(spawn))
return spawn
end
-- Random walk
spawn.x = spawn.x + math.random(-64, 64)
spawn.z = spawn.z + math.random(-64, 64)
end end
minetest.log("action", "[mcl_spawn] Failed to determine dynamic world spawn!") minetest.log("action", "[mcl_spawn] Failed to determine dynamic world spawn!")
-- Use dummy position if nothing found return start_pos
return { x=math.random(-16, 16), y=8, z=math.random(-16, 16) }
end end
-- Returns a spawn position of player. -- Returns a spawn position of player.
-- If player is nil or not a player, a world spawn point is returned. -- If player is nil or not a player, a world spawn point is returned.
-- The second return value is true if returned spawn point is player-chosen, -- The second return value is true if returned spawn point is player-chosen,
-- false otherwise. -- false otherwise.
mcl_spawn.get_spawn_pos = function(player) mcl_spawn.get_bed_spawn_pos = function(player)
local spawn, custom_spawn = nil, false local spawn, custom_spawn = nil, false
if player ~= nil and player:is_player() then if player ~= nil and player:is_player() then
local attr = player:get_meta():get_string("mcl_beds:spawn") local attr = player:get_meta():get_string("mcl_beds:spawn")
@ -101,29 +305,8 @@ mcl_spawn.set_spawn_pos = function(player, pos, message)
return spawn_changed return spawn_changed
end end
local function get_far_node(pos) mcl_spawn.get_player_spawn_pos = function(player)
local node = minetest.get_node(pos) local pos, custom_spawn = mcl_spawn.get_bed_spawn_pos(player)
if node.name ~= "ignore" then
return node
end
minetest.get_voxel_manip():read_from_map(pos, pos)
return minetest.get_node(pos)
end
local function good_for_respawn(pos)
local node0 = get_far_node({x = pos.x, y = pos.y - 1, z = pos.z})
local node1 = get_far_node({x = pos.x, y = pos.y, z = pos.z})
local node2 = get_far_node({x = pos.x, y = pos.y + 1, z = pos.z})
local def0 = minetest.registered_nodes[node0.name]
local def1 = minetest.registered_nodes[node1.name]
local def2 = minetest.registered_nodes[node2.name]
return def0.walkable and (not def1.walkable) and (not def2.walkable) and
(def1.damage_per_second == nil or def2.damage_per_second <= 0) and
(def1.damage_per_second == nil or def2.damage_per_second <= 0)
end
mcl_spawn.spawn = function(player)
local pos, custom_spawn = mcl_spawn.get_spawn_pos(player)
if pos and custom_spawn then if pos and custom_spawn then
-- Check if bed is still there -- Check if bed is still there
local node_bed = get_far_node(pos) local node_bed = get_far_node(pos)
@ -134,7 +317,7 @@ mcl_spawn.spawn = function(player)
player:get_meta():set_string("mcl_beds:spawn", "") player:get_meta():set_string("mcl_beds:spawn", "")
end end
minetest.chat_send_player(player:get_player_name(), S("Your spawn bed was missing or blocked.")) minetest.chat_send_player(player:get_player_name(), S("Your spawn bed was missing or blocked."))
return false return mcl_spawn.get_world_spawn_pos(), false
end end
-- Find spawning position on/near the bed free of solid or damaging blocks iterating a square spiral 15x15: -- Find spawning position on/near the bed free of solid or damaging blocks iterating a square spiral 15x15:
@ -151,17 +334,59 @@ mcl_spawn.spawn = function(player)
else -- dir.x == 1 else -- dir.x == 1
offset = {x = -o.z, y = o.y, z = o.x} offset = {x = -o.z, y = o.y, z = o.x}
end end
local spawn_pos = vector.add(pos, offset) local player_spawn_pos = vector.add(pos, offset)
if good_for_respawn(spawn_pos) then if good_for_respawn(player_spawn_pos, player:get_player_name()) then
player:set_pos(spawn_pos) return player_spawn_pos, true
return true, spawn_pos
end end
end end
-- We here if we didn't find suitable place for respawn
end
return mcl_spawn.get_world_spawn_pos(), false
end
-- We here if we didn't find suitable place for respawn: mcl_spawn.spawn = function(player)
return false local pos, in_bed = mcl_spawn.get_player_spawn_pos(player)
end player:set_pos(pos)
return in_bed or success
end end
-- Respawn player at specified respawn position -- Respawn player at specified respawn position
minetest.register_on_respawnplayer(mcl_spawn.spawn) minetest.register_on_respawnplayer(mcl_spawn.spawn)
function mcl_spawn.shadow_worker()
if #biome_ids < 1 then
for _, biome_name in pairs(biomes_white_list) do
table.insert(biome_ids, minetest.get_biome_id(biome_name))
end
end
if not searched then
searched = true
mcl_spawn.search()
minetest.log("action", "[mcl_spawn] Started world spawn point search")
end
if success and ((not good_for_respawn(wsp)) or (not can_find_tree(wsp))) then
success = false
minetest.log("action", "[mcl_spawn] World spawn position isn't safe anymore: "..minetest.pos_to_string(wsp))
mcl_spawn.search()
end
minetest.after(respawn_search_interval, mcl_spawn.shadow_worker)
end
minetest.after(respawn_search_initial_delay, function()
mcl_spawn.shadow_worker()
minetest.register_on_shutdown(function()
storage:set_int("mcl_spawn_success", success and 1 or 0)
if wsp and wsp.x then
storage:set_string("mcl_spawn_world_spawn_point", minetest.pos_to_string(wsp))
end
storage:set_int("mcl_spawn_searched", searched and 1 or 0)
storage:set_int("mcl_spawn_check", check)
storage:set_string("mcl_spawn_cp", minetest.pos_to_string(cp))
storage:set_int("mcl_spawn_edge_len", edge_len)
storage:set_int("mcl_spawn_edge_dist", edge_dist)
storage:set_int("mcl_spawn_dir_step", dir_step)
storage:set_int("mcl_spawn_dir_ind", dir_ind)
end)
end)