From 00c05a8a897dc7fe99d48f61870f154d1e27d1b7 Mon Sep 17 00:00:00 2001 From: upsilon Date: Sat, 17 Aug 2019 20:55:43 +0200 Subject: [PATCH] Change starting algorithm: replace countdown with number-of-players based algorithm --- README.md | 6 -- mods/hg_map/map.lua | 2 +- mods/hg_map/map_placer.lua | 4 -- mods/hg_match/countdown.lua | 57 ------------------ mods/hg_match/depends.txt | 1 + mods/hg_match/init.lua | 41 ++++--------- mods/hg_match/start.lua | 93 +++++++++++++++++++++++++++++ mods/hg_player/depends.txt | 1 - mods/hg_player/hud.lua | 27 +++------ mods/hg_player/init.lua | 22 ++++--- mods/hg_player/ranking_formspec.lua | 4 +- 11 files changed, 130 insertions(+), 128 deletions(-) delete mode 100644 mods/hg_match/countdown.lua create mode 100644 mods/hg_match/start.lua diff --git a/README.md b/README.md index b0246ff..ad98ccb 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,3 @@ is the maximum between 3 and the number of players out of the number of maps are 10 players waiting). The maximum waiting time is 5 minutes, if there are not enough players (still more than 3) but 5 minutes were waited, a new game will start anyway. --> - -The game computes a metric named the last-20 minutes average time to get 30 -players. The countdown is initially set to this value (with a maximum of 5 -minutes, which will be typically used unless there are more than 6 players a -minute). Every time a new players connects, the countdown is decreased by -5% of its initial value, unless the countdown is below 20% of its initial value. diff --git a/mods/hg_map/map.lua b/mods/hg_map/map.lua index 70e8f1d..1f2d84a 100644 --- a/mods/hg_map/map.lua +++ b/mods/hg_map/map.lua @@ -130,7 +130,7 @@ end function hg_map.get_map(spawnpoints_min) local selectable_maps = {} for _, map in ipairs(hg_map.maps) do - if not map.in_use and map.ready and map.max_players >= spawnpoints_min then + if map.ready and map.max_players >= spawnpoints_min then table.insert(selectable_maps, map) end end diff --git a/mods/hg_map/map_placer.lua b/mods/hg_map/map_placer.lua index d5658b2..cbe2d27 100644 --- a/mods/hg_map/map_placer.lua +++ b/mods/hg_map/map_placer.lua @@ -156,10 +156,6 @@ local function coroutine_body() minetest.log("action", string.format("[hg_map] Finished loading of map id %d.", map.id)) map.ready = true - if hg_match.waiting_map_flag then - hg_match.waiting_map_flag = false - hg_match.new_game() - end else -- There is no need for a map. coroutine.yield() diff --git a/mods/hg_match/countdown.lua b/mods/hg_match/countdown.lua deleted file mode 100644 index ae47630..0000000 --- a/mods/hg_match/countdown.lua +++ /dev/null @@ -1,57 +0,0 @@ -hg_match.initial_countdown = 0 -hg_match.wait_countdown = 0 -hg_match.new_players_20_min = 0 - -function hg_match.init_countdown() - -- Average time to get 20 players based on the last 20 minutes activity. - -- Maximum 5 minutes, minimum 30 seconds - local minutes_per_player = (20 * 60) / hg_match.new_players_20_min - local v = math.max(math.min(20 * minutes_per_player, 300), 30) - hg_match.initial_countdown = v - hg_match.wait_countdown = v - - hg_match.call_registered_on_countdown_update(v, v) -end - -function hg_match.update_countdown() - if #minetest.get_connected_players() == 0 then - -- Do not run the countdown when no player is connected, - -- and leave it in a ready state - hg_match.waiting_player_flag = false - hg_match.waiting_map_flag = false - hg_match.init_countdown() - end - - if hg_match.waiting_player_flag or hg_match.waiting_map_flag then - return - end - - hg_match.wait_countdown = hg_match.wait_countdown - 1 - - hg_match.call_registered_on_countdown_update(hg_match.initial_countdown, hg_match.wait_countdown) - - if hg_match.wait_countdown == 0 then - hg_match.init_countdown() - hg_match.new_game() - end -end - -local timer = 0 -minetest.register_globalstep(function(dtime) - timer = timer + dtime - if timer < 1 then - return - end - - timer = timer - 1 - hg_match.update_countdown() -end) - -hg_match.register_on_new_player(function(name) - hg_match.new_players_20_min = hg_match.new_players_20_min + 1 - minetest.after(20 * 60, function() - hg_match.new_players_20_min = hg_match.new_players_20_min - 1 - end) -end) - -hg_match.init_countdown() diff --git a/mods/hg_match/depends.txt b/mods/hg_match/depends.txt index f4abb1c..991d750 100644 --- a/mods/hg_match/depends.txt +++ b/mods/hg_match/depends.txt @@ -1 +1,2 @@ hg_map +hg_player diff --git a/mods/hg_match/init.lua b/mods/hg_match/init.lua index 172a547..00ff8d8 100644 --- a/mods/hg_match/init.lua +++ b/mods/hg_match/init.lua @@ -5,8 +5,6 @@ end hg_match = { players_waiting = {}, max_players = 0, - waiting_player_flag = false, - waiting_map_flag = false, } function debug_msg(msg) @@ -15,7 +13,7 @@ function debug_msg(msg) end end -for _, v in ipairs({"new_game", "end_game", "countdown_update", "new_player", "killed_player"}) do +for _, v in ipairs({"new_game", "end_game", "new_player", "killed_player"}) do hg_match["registered_on_" .. v] = {} hg_match["register_on_" .. v] = function(f) assert(type(f) == "function") @@ -53,10 +51,6 @@ function hg_match.new_game() -- Check if there are enough players if #hg_match.players_waiting < 2 then debug_msg("Not enough players") - - hg_match.waiting_player_flag = true - hg_match.call_registered_on_countdown_update(hg_match.initial_countdown, 0, - "The game will start as soon as at least 2 players are ready.") return false end @@ -64,26 +58,25 @@ function hg_match.new_game() local map = hg_map.get_map(#hg_match.players_waiting) if not map then debug_msg("No map is ready") - hg_match.waiting_map_flag = true - hg_match.call_registered_on_countdown_update(hg_match.initial_countdown, 0, - "The game will start as soon as a map is ready.") return false end local players = table.copy(hg_match.players_waiting) - hg_match.players_waiting = {} map.in_use = true - map.players = players + map.ready = false map.dead_players = {} map.build_countdown = 5*60 map.scores = {} for _, name in ipairs(players) do + table.remove(hg_match.players_waiting, 1) + table.insert(map.players, name) map.scores[name] = {} end - minetest.log("action", string.format("[hg_match] Starting a new game with %d players on map %d (%s)!", #players, map.id, map.name)) - hg_match.call_registered_on_new_game(map, players) + minetest.log("action", string.format("[hg_match] Starting a new game with %d players on map %d (%s)!", #map.players, map.id, map.name)) + hg_player.new_game(map) + hg_match.call_registered_on_new_game(map, map.players) return true end @@ -94,7 +87,6 @@ function hg_match.end_game(map) minetest.log("action", string.format("[hg_match] End of game on map %d.", map.id)) map.in_use = false - map.ready = false for i, name in ipairs(map.players) do table.remove(map.players, i) table.insert(map.dead_players, name) @@ -102,12 +94,16 @@ function hg_match.end_game(map) hg_match.new_player(name) end + hg_match.last_game_time = os.time() + + hg_player.end_game(map) hg_match.call_registered_on_end_game(map) end function hg_match.new_player(name) debug_msg("Called new_player with params " .. name) + hg_player.new_player(name) hg_match.call_registered_on_new_player(name) if hg_match.is_waiting(name) then @@ -115,19 +111,6 @@ function hg_match.new_player(name) end table.insert(hg_match.players_waiting, name) - - if hg_match.waiting_player_flag then - debug_msg("Starting a game because the waiting_player_flag is set") - hg_match.waiting_player_flag = false - minetest.after(2, hg_match.new_game) - return - end - - if #hg_match.players_waiting >= hg_match.max_players then - debug_msg("Starting a game because no map is able to handle more players") - hg_match.new_game() - return - end end function hg_match.remove_player(name) @@ -173,4 +156,4 @@ function hg_match.killed_player(name, killer_name) end end -dofile(minetest.get_modpath("hg_match") .. "/countdown.lua") +dofile(minetest.get_modpath("hg_match") .. "/start.lua") diff --git a/mods/hg_match/start.lua b/mods/hg_match/start.lua new file mode 100644 index 0000000..002ff39 --- /dev/null +++ b/mods/hg_match/start.lua @@ -0,0 +1,93 @@ +--[[ +The code is this file is responsible for deciding when to start a game. It checks +every second whether to do so. +]] + +function hg_match.starter_step() + if hg_match.start_delay then + -- A start is scheduled after some delay + hg_match.start_delay = hg_match.start_delay - 1 + if hg_match.start_delay == 0 then + hg_match.start_delay = nil + else + hg_player.update_text_hud_all(hg_match.players_waiting, string.format("The game will start in %d seconds...", hg_match.start_delay)) + return + end + end + + -- If there are less than two players, it's certain that we can't start a game. + local n_players = #minetest.get_connected_players() + if n_players < 2 then + hg_player.update_text_hud_all(hg_match.players_waiting, "The game will start as soon as another player is ready...") + return + end + + -- Read the maximum number of players to wait to start a game. + local max_players_waited = tonumber(minetest.settings:get("hg.max_players_waited")) or hg_match.max_players + if max_players_waited > hg_match.max_players then + minetest.log("warning", string.format("hg.max_players_waited set to %d, but no map can handle more than %d players.", + max_players_waited, hg_match.max_players)) + max_players_waited = hg_match.max_players + elseif max_players_waited < 2 then + minetest.log("warning", string.format("hg.max_players_waited must be at least 2, set to %d.", + max_players_waited)) + max_players_waited = 2 + end + -- Compute the number of players to wait based on the number of connected players + local players_waited + if n_players <= 2 then + -- We need at least two players + players_waited = 2 + elseif n_players <= 5 then + -- Up to 5 players, wait all the players + players_waited = n_players + else + -- Wait for the ceil of the square root of the number of players + players_waited = math.ceil(math.sqrt(n_players)) + end + -- But never more than max_players_waited. + if players_waited > max_players_waited then + players_waited = max_players_waited + end + + if #hg_match.players_waiting < players_waited then + -- Not enough players + hg_player.update_text_hud_all(hg_match.players_waiting, string.format("The game will start as soon as %d players are ready (it shouldn't take long)...", + players_waited)) + return + end + + -- Also check that there is a map ready + local map_ready = false + for _, map in ipairs(hg_map.maps) do + if map.ready then + map_ready = true + break + end + end + if not map_ready then + hg_player.update_text_hud_all(hg_match.players_waiting, "The game will start as soon as a map is ready...") + return + end + + -- We can now start a game. + if hg_match.last_game_time and os.difftime(os.time(), hg_match.last_game_time) < 15 then + -- If a game ended less than 15 seconds ago, wait 15 seconds before starting + -- the next game to let players respawn, say each other Good Game, and rest + -- for a bit. + hg_match.start_delay = 15 - os.difftime(os.time(), hg_match.last_game_time) + else + hg_match.new_game() + end +end + +local timer = 0 +minetest.register_globalstep(function(dtime) + timer = timer + dtime + if timer < 1 then + return + end + + timer = timer - 1 + hg_match.starter_step() +end) diff --git a/mods/hg_player/depends.txt b/mods/hg_player/depends.txt index f602977..a3c68df 100644 --- a/mods/hg_player/depends.txt +++ b/mods/hg_player/depends.txt @@ -1,3 +1,2 @@ -hg_match hg_map hg_hunger diff --git a/mods/hg_player/hud.lua b/mods/hg_player/hud.lua index 256a527..0274981 100644 --- a/mods/hg_player/hud.lua +++ b/mods/hg_player/hud.lua @@ -1,8 +1,5 @@ -- Waiting HUD - -hg_player = { - text_huds = {}, -} +hg_player.text_huds = {} function hg_player.update_text_hud(player, text) local name = player:get_player_name() @@ -31,6 +28,12 @@ function hg_player.update_text_hud(player, text) end end +function hg_player.update_text_hud_all(names, text) + for _, name in ipairs(names) do + hg_player.update_text_hud(minetest.get_player_by_name(name), text) + end +end + function hg_player.remove_text_hud(player) local name = player:get_player_name() local id = hg_player.text_huds[name] @@ -42,20 +45,4 @@ end minetest.register_on_leaveplayer(function(player) hg_player.remove_text_hud(player) - hg_match.remove_player(player:get_player_name()) -end) - -hg_match.register_on_countdown_update(function(initial, current, error) - local str = "" - if error then - str = error - else - str = string.format("Next game starts in: %d min %d s", - math.floor(current / 60), current % 60) - end - - for _, name in ipairs(hg_match.players_waiting) do - local player = minetest.get_player_by_name(name) - hg_player.update_text_hud(player, str) - end end) diff --git a/mods/hg_player/init.lua b/mods/hg_player/init.lua index 7218135..b97b393 100644 --- a/mods/hg_player/init.lua +++ b/mods/hg_player/init.lua @@ -2,6 +2,8 @@ if minetest.is_singleplayer() then return end +hg_player = {} + dofile(minetest.get_modpath("hg_player") .. "/hud.lua") dofile(minetest.get_modpath("hg_player") .. "/pvp.lua") dofile(minetest.get_modpath("hg_player") .. "/ranking_formspec.lua") @@ -26,7 +28,7 @@ local function clear_inventory(player) end end -hg_match.register_on_new_player(function(name) +function hg_player.new_player(name) -- Revoke interact local privs = minetest.get_player_privs(name) privs.interact = nil @@ -49,7 +51,7 @@ hg_match.register_on_new_player(function(name) player:set_clouds({ height = hg_map.spawn.maxp.y }) -end) +end minetest.register_on_joinplayer(function(player) local name = player:get_player_name() @@ -59,6 +61,10 @@ minetest.register_on_joinplayer(function(player) hg_match.new_player(name) end) +minetest.register_on_leaveplayer(function(player) + hg_match.remove_player(player:get_player_name()) +end) + minetest.register_on_dieplayer(function(player) local name = player:get_player_name() @@ -74,6 +80,7 @@ end) minetest.register_on_respawnplayer(function(player) local name = player:get_player_name() + local formspec = hg_player.respawn_ranking_formspec[name] if formspec then minetest.after(1, function() @@ -87,9 +94,9 @@ minetest.register_on_respawnplayer(function(player) return true end) -hg_match.register_on_new_game(function(map, players) +function hg_player.new_game(map) local spawnpoints = table.copy(map.hg_nodes.spawnpoint) - for _, name in ipairs(players) do + for _, name in ipairs(map.players) do local player = minetest.get_player_by_name(name) -- Grant interact @@ -123,12 +130,13 @@ hg_match.register_on_new_game(function(map, players) to_player = name, }) end -end) +end -hg_match.register_on_end_game(function(map) +function hg_player.end_game(map) for _, name in ipairs(map.dead_players) do minetest.sound_play({name = "hg_player_match_start"}, { to_player = name, }) end -end) + hg_player.show_ranking_formspec(map) +end diff --git a/mods/hg_player/ranking_formspec.lua b/mods/hg_player/ranking_formspec.lua index 0f4fd61..1779a67 100644 --- a/mods/hg_player/ranking_formspec.lua +++ b/mods/hg_player/ranking_formspec.lua @@ -1,6 +1,6 @@ hg_player.respawn_ranking_formspec = {} -local function show_ranking_formspec(map) +function hg_player.show_ranking_formspec(map) local kill_map = {} local kill_number = {} for killer, v in pairs(map.scores) do @@ -30,5 +30,3 @@ local function show_ranking_formspec(map) hg_player.respawn_ranking_formspec[map.dead_players[#map.dead_players-1]] = formspec end - -hg_match.register_on_end_game(show_ranking_formspec)