+ Reset system now async!

+ Added tests for the map reset system
+ Fixed get_arena_by_pos()
+ Code cleaning and optimizations
- Debug messages translations
master
Giov4 2020-11-02 18:13:45 +01:00
parent f3615d3e8f
commit ad3f9b5261
12 changed files with 321 additions and 95 deletions

View File

@ -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"

View File

@ -15,8 +15,8 @@ an arena can have.
2) Saving the map area using:
/skywars pos1 <arena name>
/skywars pos2 <arena name>
/skywars min_pos <arena name>
/skywars max_pos <arena name>
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

View File

@ -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

View File

@ -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], "_", " ")

View File

@ -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

106
_tests/map_reset.lua Normal file
View File

@ -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"},
})

View File

@ -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 <kit name>
- removeitem <kit name> <item>
- removeitem hand <kit name>
- arenakit add <arena> <kit name>
- arenakit remove <arena> <kit name>
- arenakit add <arena name> <kit name>
- arenakit remove <arena name> <kit name>
- getkits
- resetkit <kit name>
- getitems <kit name>
@ -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 <arena name>: tests the reset system, make sure your map is properly reset
before using it, 'cause it will clear the maps table first
- test asyncspeed <arena name>: 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 }
})

View File

@ -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")

View File

@ -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!
Nobody must be in the editor!=Nessuno deve essere nell'editor!

View File

@ -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!=
Nobody must be in the editor!=

BIN
textures/test_node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

@ -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