forked from MineClone5/MineClone5
Merge branch 'master' of https://git.minetest.land/Wuzzy/MineClone2
This commit is contained in:
commit
be5a228bad
|
@ -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
|
||||||
|
|
|
@ -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!"))
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue