diff --git a/SETTINGS.lua b/SETTINGS.lua index 7372abb..7f392a0 100644 --- a/SETTINGS.lua +++ b/SETTINGS.lua @@ -35,7 +35,7 @@ skywars_settings.player_speed = 1.5 -- true = on/false = off (case sensitive). skywars_settings.fall_damage_disabled = true --- The name of the permission to allow players to break blocks +-- The name of the permission to allow players to break nodes -- (if there's none just set it to ""). skywars_settings.build_permission = "build" diff --git a/TUTORIAL.txt b/TUTORIAL.txt index 450b592..2f5409a 100644 --- a/TUTORIAL.txt +++ b/TUTORIAL.txt @@ -15,8 +15,8 @@ an arena can have. 2) Saving the map area using: -/skywars pos1 -/skywars pos2 +/skywars min_pos +/skywars max_pos In order to kill players that go out of the map and to properly recognize the arena you have to define a map area; to do so, simply specify its diff --git a/_arena_lib/arena_callbacks.lua b/_arena_lib/arena_callbacks.lua index d30d730..13a9531 100644 --- a/_arena_lib/arena_callbacks.lua +++ b/_arena_lib/arena_callbacks.lua @@ -1,6 +1,6 @@ local function add_privs() end local function remove_privs() end -local function create_glass_cage()end +local function create_glass_cage() end minetest.register_on_joinplayer(function(player) @@ -10,7 +10,7 @@ end) arena_lib.on_load("skywars", function(arena) - skywars.load_map_mapblocks(arena) + skywars.load_mapblocks(arena) skywars.reset_map(arena) for pl_name in pairs(arena.players) do @@ -65,6 +65,8 @@ arena_lib.on_end("skywars", function(arena, players) skywars.remove_armor(player) skywars.block_enderpearl(player, arena) end + + skywars.reset_map(arena) end) @@ -130,7 +132,7 @@ arena_lib.on_enable("skywars", function(arena, pl_name) elseif arena.chests[1] == nil then skywars.print_error(pl_name, skywars.T("You didn't set the chests!")) return false - elseif arena.pos1.x == nil or arena.pos2.x == nil then + elseif arena.min_pos.x == nil or arena.max_pos.x == nil then skywars.print_error(pl_name, skywars.T("You didn't set the map corners!")) return false end @@ -159,7 +161,7 @@ function add_privs(pl_name) local privs = minetest.get_player_privs(pl_name) local player = minetest.get_player_by_name(pl_name) - -- preventing players with noclip to fall when placing blocks + -- preventing players with noclip to fall when placing nodes if privs.noclip then player:get_meta():set_string("sw_can_noclip", "true") privs.noclip = nil @@ -200,7 +202,7 @@ end function create_glass_cage(player) minetest.after(0.1, function() local pl_pos = player:get_pos() - local glass_blocks = { + local glass_nodes = { {x = 0, y = -1, z = 0}, {x = 0, y = -2, z = 0}, {x = 1, y = 1, z = 0}, @@ -213,7 +215,7 @@ function create_glass_cage(player) player:set_physics_override({gravity=0, jump=0}) player:add_player_velocity(vector.multiply(player:get_player_velocity(), -1)) - for _, relative_pos in pairs(glass_blocks) do + for _, relative_pos in pairs(glass_nodes) do local node_pos = vector.round(vector.add(pl_pos, relative_pos)) if minetest.get_node(node_pos).name == "air" then minetest.add_node(node_pos, {name="default:glass"}) @@ -224,5 +226,8 @@ function create_glass_cage(player) minetest.after(1, function() player:set_pos(pl_pos) end) + minetest.after(2, function() + player:set_pos(pl_pos) + end) end) end \ No newline at end of file diff --git a/_kits/formspec.lua b/_kits/formspec.lua index a6122e9..a59aab3 100644 --- a/_kits/formspec.lua +++ b/_kits/formspec.lua @@ -18,7 +18,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local arena = arena_lib.get_arena_by_player(pl_name) local kits = skywars.load_table("kits") - -- if the pressed button name is equal to one of the kits in the arena then select it + -- If the pressed button name is equal to one of the kits in the arena then select it. for i = 1, #arena.kits do local kit_name = arena.kits[i] if fields[kit_name] then @@ -60,7 +60,7 @@ function create_formspec(arena) local offset_y = 0 local kits = skywars.load_table("kits") - -- generates the formspec buttons + -- Generates the formspec buttons. for i=1, #arena.kits do local kit_name = arena.kits[i] local kit = kits[kit_name] @@ -77,7 +77,7 @@ function create_formspec(arena) end if kit.items and kit.items[1] then - -- if offset_x has reached its maximum amount then reset it and increase offset_y + -- If offset_x has reached its maximum amount then reset it and increase offset_y. if offset_x == distance_x * (buttons_per_row-1) then offset_y = offset_y + distance_y offset_x = 0 @@ -85,11 +85,11 @@ function create_formspec(arena) offset_x = offset_x + distance_x end - -- generating the kit description (a list of all the items in the kit) + -- Generating the kit description (a list of all the items in the kit). for j = 1, #kit.items do local item_name = kit.items[j].name - -- if the string is "mod:item_name" it becomes "item name" + -- If the string is "mod:item_name" it becomes "item name". if string.match(item_name, ":") then local split_name = string.split(item_name, ":") item_name = string.gsub(split_name[2], "_", " ") diff --git a/_map_handler/map_manager.lua b/_map_handler/map_manager.lua index 3b7a62b..9089e46 100644 --- a/_map_handler/map_manager.lua +++ b/_map_handler/map_manager.lua @@ -1,40 +1,19 @@ local function save_block() end +local function delete_drops() end +local function async_reset_map() end - -function skywars.load_map_mapblocks(arena) - minetest.load_area(arena.pos1, arena.pos2) - minetest.emerge_area(arena.pos1, arena.pos2) +function skywars.load_mapblocks(arena) + minetest.load_area(arena.min_pos, arena.max_pos) + minetest.emerge_area(arena.min_pos, arena.max_pos) end -function skywars.reset_map(arena) - local maps = skywars.load_table("maps") - local pos1, pos2 = reorder_positions(arena.pos1, arena.pos2) - local distance_from_center = vector.distance(pos1, pos2) / 2 - local map_center = {x = (pos1.x+pos2.x) / 2, y = (pos1.y+pos2.y) / 2, z = (pos1.z+pos2.z) / 2} +function skywars.reset_map(arena, debug, debug_data) + if not arena.enabled or arena.is_resetting then return end - -- deleting drops - for i, obj in pairs(minetest.get_objects_inside_radius(map_center, distance_from_center)) do - if not obj:is_player() then - local props = obj:get_properties() - local entity_texture = props.textures[1] - if props.automatic_rotate > 0 and ItemStack(entity_texture):is_known() then - obj:remove() - end - end - end - - if not maps or maps == "" or not maps[arena.name] or not maps[arena.name].blocks then - return - end - - for serialized_pos, node in pairs(maps[arena.name].blocks) do - local pos = minetest.deserialize(serialized_pos) - minetest.add_node(pos, node) - end - maps[arena.name].blocks = {} - skywars.overwrite_table("maps", maps) + delete_drops(arena) + async_reset_map(arena, debug, debug_data) end @@ -43,7 +22,7 @@ function skywars.kill_players_out_map(arena) for pl_name in pairs(arena.players) do local player = minetest.get_player_by_name(pl_name) local pl_pos = player:get_pos() - local map_area = VoxelArea:new{MinEdge = arena.pos1, MaxEdge = arena.pos2} + local map_area = VoxelArea:new{MinEdge = arena.min_pos, MaxEdge = arena.max_pos} if map_area:contains(pl_pos.x, pl_pos.y, pl_pos.z) == false then player:set_hp(0) @@ -81,13 +60,15 @@ end) --- minetest.set_node override +-- minetest.set_node override. local set_node = minetest.set_node function minetest.set_node(pos, node) local arena = skywars.get_arena_by_pos(pos) local oldnode = minetest.get_node(pos) - if arena and arena.enabled then save_block(arena, pos, oldnode) end + if arena and arena.enabled then + save_block(arena, pos, oldnode) + end return set_node(pos, node) end @@ -107,11 +88,101 @@ function save_block(arena, pos, node) if not arena then return end if not maps then maps = {} end if not maps[arena.name] then maps[arena.name] = {} end - if not maps[arena.name].blocks then maps[arena.name].blocks = {} end + if not maps[arena.name].nodes then maps[arena.name].nodes = {} end - -- if this block has not been changed yet then save it - if maps[arena.name].blocks[serialized_pos] == nil then - maps[arena.name].blocks[serialized_pos] = node + -- If this block has not been changed yet then save it. + if maps[arena.name].nodes[serialized_pos] == nil then + maps[arena.name].nodes[serialized_pos] = node skywars.overwrite_table("maps", maps) end +end + + + +function delete_drops(arena) + local min_pos, max_pos = skywars.reorder_positions(arena.min_pos, arena.max_pos) + local distance_from_center = vector.distance(min_pos, max_pos) / 2 + local map_center = { + x = (min_pos.x + max_pos.x) / 2, + y = (min_pos.y + max_pos.y) / 2, + z = (min_pos.z + max_pos.z) / 2 + } + + for i, obj in pairs(minetest.get_objects_inside_radius(map_center, distance_from_center)) do + if not obj:is_player() then + local props = obj:get_properties() + local entity_texture = props.textures[1] + if props.automatic_rotate > 0 and ItemStack(entity_texture):is_known() then + obj:remove() + end + end + end +end + + + +function async_reset_map(arena, debug, recursive_data) + recursive_data = recursive_data or {} + + -- When the function gets called again it uses the same maps table. + local original_maps = recursive_data.original_maps or skywars.load_table("maps") + if not original_maps[arena.name] or not original_maps[arena.name].nodes then + return + end + + -- The indexes are useful to count the reset nodes. + debug = debug or false + local current_index = 1 + local original_map_nodes = original_maps[arena.name].nodes + local last_index = recursive_data.last_index or 0 + local nodes_per_tick = recursive_data.nodes_per_tick or 30 + local current_cycle = recursive_data.current_cycle or 1 + local initial_time = recursive_data.initial_time or minetest.get_us_time() + local nodes_to_reset = nodes_per_tick * current_cycle + + -- Resets a node if it hasn't been reset yet and, if it reset more than "nodes_per_tick" + -- nodes, it invokes this function again after one step. + arena.is_resetting = true + for serialized_pos, node in pairs(original_map_nodes) do + if current_index > last_index then + local pos = minetest.deserialize(serialized_pos) + minetest.add_node(pos, node) + end + if current_index >= nodes_to_reset then + minetest.after(0, function() + async_reset_map(arena, debug, { + last_index = current_index, + nodes_per_tick = nodes_per_tick, + original_maps = original_maps, + current_cycle = current_cycle+1, + initial_time = initial_time + }) + end) + return + end + + current_index = current_index + 1 + end + arena.is_resetting = false + + -- Removing the original map nodes from the actual map to preserve eventual changes made + -- to the latter during the reset. + local actual_maps = skywars.load_table("maps") + if not actual_maps[arena.name] or not actual_maps[arena.name].nodes then + return + end + local actual_map_nodes = actual_maps[arena.name].nodes + + for serialized_pos, node in pairs(actual_map_nodes) do + if original_map_nodes[serialized_pos] then + actual_map_nodes[serialized_pos] = nil + end + end + + skywars.overwrite_table("maps", actual_maps) + + if debug then + local duration = minetest.get_us_time() - initial_time + minetest.log("[Skywars Reset Debug] The reset took " .. duration/1000000 .. " seconds!") + end end \ No newline at end of file diff --git a/_tests/map_reset.lua b/_tests/map_reset.lua new file mode 100644 index 0000000..4075184 --- /dev/null +++ b/_tests/map_reset.lua @@ -0,0 +1,106 @@ +local function test_not_async_reset() end +local function test_async_reset() end +local function place_nodes_at_arena_edges() end +local function get_nodes_at_arena_edges() end + + +function skywars.map_reset_test(arena) + return test_not_async_reset(arena) and test_async_reset(arena) +end + + + +function skywars.test_async_speed(arena) + skywars.reorder_positions(arena.min_pos, arena.max_pos) + skywars.load_mapblocks(arena) + + local max_pos = arena.max_pos + local min_pos = arena.min_pos + local area_size = 10 + max_pos = vector.add(min_pos, area_size) + + for x = 1, max_pos.x - min_pos.x do + for y = 1, max_pos.y - min_pos.y do + for z = 1, max_pos.z - min_pos.z do + local node_pos = { + x = min_pos.x+x, + y = min_pos.y+y, + z = min_pos.z+z + } + minetest.set_node(node_pos, {name="skywars:test_node"}) + end + end + end + + minetest.after(1, function() skywars.reset_map(arena, true) end) +end + + + +function test_not_async_reset(arena) + place_nodes_at_arena_edges(arena) + + skywars.reset_map(arena) + local node1, node2 = get_nodes_at_arena_edges(arena) + + local did_nodes_reset = (node1.name ~= "skywars:test_node" and node2.name ~= "skywars:test_node") + if not did_nodes_reset then + minetest.log("[Skywars Test] Reset system doesn't work") + return false + end + + return true +end + + + +function test_async_reset(arena) + place_nodes_at_arena_edges(arena) + + skywars.reset_map(arena, true, {nodes_per_tick = 1}) + local node1, node2 = get_nodes_at_arena_edges(arena) + + local did_just_one_node_reset = (node1.name ~= node2.name) + if not did_just_one_node_reset then + minetest.log("[Skywars Test] Async reset system doesn't work") + return false + end + + return true +end + + + +function place_nodes_at_arena_edges(arena) + skywars.reorder_positions(arena.min_pos, arena.max_pos) + skywars.load_mapblocks(arena) + + local node1, node2 = get_nodes_at_arena_edges(arena) + + if node1.name == "skywars:test_node" then minetest.remove_node(arena.min_pos) end + if node2.name == "skywars:test_node" then minetest.remove_node(arena.max_pos) end + skywars.overwrite_table("maps", {}) + + minetest.set_node(arena.min_pos, {name="skywars:test_node"}) + minetest.set_node(arena.max_pos, {name="skywars:test_node"}) + + node1 = minetest.get_node(arena.min_pos) + node2 = minetest.get_node(arena.max_pos) +end + + + +function get_nodes_at_arena_edges(arena) + local node1 = minetest.get_node(arena.min_pos) + local node2 = minetest.get_node(arena.max_pos) + + return node1, node2 +end + + + +minetest.register_node("skywars:test_node", { + description = "Skywars test block, don't use it!", + groups = {crumbly=1, soil=1}, + tiles = {"test_node.png"}, +}) \ No newline at end of file diff --git a/commands.lua b/commands.lua index 435e2b4..0304436 100644 --- a/commands.lua +++ b/commands.lua @@ -748,8 +748,8 @@ ChatCmdBuilder.new("skywars", function(cmd) return end - arena.pos1 = player:get_pos() - arena_lib.change_arena_property(sender, "skywars", arena.name, "pos1", arena.pos1) + arena.min_pos = player:get_pos() + arena_lib.change_arena_property(sender, "skywars", arena.name, "min_pos", arena.min_pos) skywars.print_msg(sender, skywars.T("Position saved!")) end) @@ -765,24 +765,14 @@ ChatCmdBuilder.new("skywars", function(cmd) return end - arena.pos2 = player:get_pos() - arena_lib.change_arena_property(sender, "skywars", arena.name, "pos2", arena.pos2) + arena.max_pos = player:get_pos() + arena_lib.change_arena_property(sender, "skywars", arena.name, "max_pos", arena.max_pos) skywars.print_msg(sender, skywars.T("Position saved!")) end) - cmd:sub("getpos", - function(sender) - local pos = minetest.get_player_by_name(sender):get_pos() - local readable_pos = "[X Y Z] " .. minetest.pos_to_string(pos, 1) - - skywars.print_msg(sender, readable_pos) - end) - - - cmd:sub("reset :arena", function(sender, arena_name) local player = minetest.get_player_by_name(sender) @@ -802,10 +792,60 @@ ChatCmdBuilder.new("skywars", function(cmd) + -------------------- + -- ! DEBUG CMDS ! -- + -------------------- + cmd:sub("clearmapstable", function(sender) skywars.overwrite_table("maps", {}) - skywars.print_msg(sender, skywars.T("Maps table reset!")) + skywars.print_msg(sender, "Maps table reset!") + end) + + + + cmd:sub("getpos", + function(sender) + local pos = minetest.get_player_by_name(sender):get_pos() + local readable_pos = "[X Y Z] " .. minetest.pos_to_string(pos, 1) + + skywars.print_msg(sender, readable_pos) + end) + + + + cmd:sub("test reset :arena", + function(sender, arena_name) + local player = minetest.get_player_by_name(sender) + local arena, arena_name = get_valid_arena(arena_name, sender) + + if not arena then return end + + if arena.enabled then + local result = skywars.map_reset_test(arena) + if result then skywars.print_msg(sender, "Reset system working!") + else skywars.print_error(sender, "Reset system doesn't work!") end + else + skywars.print_error(sender, skywars.T("@1 must be enabled!", arena_name)) + end + end) + + + + cmd:sub("test asyncspeed :arena", + function(sender, arena_name) + local player = minetest.get_player_by_name(sender) + local arena, arena_name = get_valid_arena(arena_name, sender) + + if not arena then return end + skywars.print_msg(sender, "Placing 1000 nodes, the server may lag...") + + if arena.enabled then + skywars.test_async_speed(arena) + skywars.print_msg(sender, "Nodes placed at " .. minetest.pos_to_string(arena.min_pos, 0) .. "!") + else + skywars.print_error(sender, skywars.T("@1 must be enabled!", arena_name)) + end end) end, { @@ -850,8 +890,8 @@ end, { - additem hand - removeitem - removeitem hand - - arenakit add - - arenakit remove + - arenakit add + - arenakit remove - getkits - resetkit - getitems @@ -860,8 +900,12 @@ end, { Debug (don't use them if you don't know what you're doing): - - clearmapstable: clears the changed blocks table of each map without resetting them + - clearmapstable: clears the changed nodes table of each map without resetting them - getpos + - test reset : tests the reset system, make sure your map is properly reset + before using it, 'cause it will clear the maps table first + - test asyncspeed : places a 10x10 area full of nodes, useful to test the + async reset system speed (read the server logs to know the reset speed) ]], privs = { skywars_admin = true } }) diff --git a/init.lua b/init.lua index 9479572..5218bc5 100644 --- a/init.lua +++ b/init.lua @@ -21,10 +21,11 @@ arena_lib.register_minigame("skywars", { }, properties = { chests = {}, - treasures = {}, -- items to put in the chests - pos1 = {}, - pos2 = {}, - kits = {} + treasures = {}, -- Items to put in the chests. + min_pos = {}, + max_pos = {}, + kits = {}, + is_resetting = false }, time_mode = 2, disabled_damage_types = disabled_damage_types_ @@ -34,6 +35,7 @@ arena_lib.register_minigame("skywars", { dofile(minetest.get_modpath("skywars") .. "/chatcmdbuilder.lua") dofile(minetest.get_modpath("skywars") .. "/utils.lua") +dofile(minetest.get_modpath("skywars") .. "/_tests/map_reset.lua") dofile(minetest.get_modpath("skywars") .. "/_compatible_mods/enderpearl/init_enderpearl.lua") dofile(minetest.get_modpath("skywars") .. "/_compatible_mods/3d_armor/init_3d_armor.lua") dofile(minetest.get_modpath("skywars") .. "/_storage/storage_manager.lua") diff --git a/locale/skywars.it.tr b/locale/skywars.it.tr index 6230eb7..b9d1129 100644 --- a/locale/skywars.it.tr +++ b/locale/skywars.it.tr @@ -41,5 +41,4 @@ x@1 @2 added to @3!=x@1 @2 aggiunto a @3! @1 kits have been copied to @2!=I kit di @1 sono stati copiati in @2! Time is out, the match is over!=Tempo terminato, la partita รจ finita! Nobody=Nessuno -Nobody must be in the editor!=Nessuno deve essere nell'editor! -Maps table reset!=Tabella mappe resettata! \ No newline at end of file +Nobody must be in the editor!=Nessuno deve essere nell'editor! \ No newline at end of file diff --git a/locale/template.txt b/locale/template.txt index 26ee5b0..e65f933 100644 --- a/locale/template.txt +++ b/locale/template.txt @@ -41,5 +41,4 @@ x@1 @2 added to @3!= @1 kits have been copied to @2!= Time is out, the match is over!= Nobody= -Nobody must be in the editor!= -Maps table reset!= \ No newline at end of file +Nobody must be in the editor!= \ No newline at end of file diff --git a/textures/test_node.png b/textures/test_node.png new file mode 100644 index 0000000..636674b Binary files /dev/null and b/textures/test_node.png differ diff --git a/utils.lua b/utils.lua index 9185177..762e91a 100644 --- a/utils.lua +++ b/utils.lua @@ -12,10 +12,10 @@ end function skywars.get_arena_by_pos(pos) for i, arena in pairs(arena_lib.mods["skywars"].arenas) do - if arena.pos1.x == nil or arena.pos2.x == nil then goto continue end + if arena.min_pos.x == nil or arena.max_pos.x == nil then goto continue end - reorder_positions(arena.pos1, arena.pos2) - local map_area = VoxelArea:new{MinEdge = arena.pos1, MaxEdge = arena.pos2} + skywars.reorder_positions(arena.min_pos, arena.max_pos) + local map_area = VoxelArea:new{MinEdge = arena.min_pos, MaxEdge = arena.max_pos} if map_area:contains(pos.x, pos.y, pos.z) then return arena @@ -27,27 +27,27 @@ end --- reordering the corners positions so that pos1 is smaller than pos2 -function reorder_positions(pos1, pos2) +-- reordering the corners positions so that min_pos is smaller than max_pos +function skywars.reorder_positions(min_pos, max_pos) local temp - if pos1.z > pos2.z then - temp = pos1.z - pos1.z = pos2.z - pos2.z = temp + if min_pos.z > max_pos.z then + temp = min_pos.z + min_pos.z = max_pos.z + max_pos.z = temp end - if pos1.y > pos2.y then - temp = pos1.y - pos1.y = pos2.y - pos2.y = temp + if min_pos.y > max_pos.y then + temp = min_pos.y + min_pos.y = max_pos.y + max_pos.y = temp end - if pos1.x > pos2.x then - temp = pos1.x - pos1.x = pos2.x - pos2.x = temp + if min_pos.x > max_pos.x then + temp = min_pos.x + min_pos.x = max_pos.x + max_pos.x = temp end - return pos1, pos2 + return min_pos, max_pos end \ No newline at end of file