From 826afe24a1fbeda3daf682ab7be5eefb1e295317 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Fri, 10 Mar 2023 14:26:44 +0330 Subject: [PATCH 01/12] add indentation --- mods/ctf/ctf_rankings/default.lua | 143 +++++++++--------------------- 1 file changed, 40 insertions(+), 103 deletions(-) diff --git a/mods/ctf/ctf_rankings/default.lua b/mods/ctf/ctf_rankings/default.lua index 57b9403855..e4ebb6eed0 100644 --- a/mods/ctf/ctf_rankings/default.lua +++ b/mods/ctf/ctf_rankings/default.lua @@ -1,120 +1,57 @@ -return function(prefix, top, sorting_finished) +return function(top) + local modstorage = assert(core.get_mod_storage(), "Can only init rankings at runtime!") -local modstorage = assert(minetest.get_mod_storage(), "Can only init rankings at runtime!") - --- If callback isn't passed then coroutine will never yield -local function op_all(operation, callback) - if not callback then - minetest.log("warning", "op_all() called without callback, it will block the server step until it finishes") - end - - local TARGET_INTERVAL = 0.1 - local interval = 0.05 - local time = minetest.get_us_time() - local times = 0 - local keys = modstorage:to_table()["fields"] - local c = coroutine.wrap(function() - for k, v in pairs(keys) do - times = times + 1 - operation(k, v) - - if callback and ((minetest.get_us_time()-time) / 1e6) >= interval then - coroutine.yield() - end - end - - return "done" - end) - - local function rep() - if ((minetest.get_us_time()-time) / 1e6) > TARGET_INTERVAL then - interval = interval - 0.01 - else - interval = interval + 0.01 - end - time = minetest.get_us_time() - - if c() ~= "done" then - minetest.after(0, rep) - elseif callback then - callback() + for k, v in pairs(modstorage:to_table()["fields"]) do + local rank = core.parse_json(v) + if rank ~= nil and rank.score then + top:set(k, rank.score) end end - rep() -end - -local timer = minetest.get_us_time() -op_all(function(noprefix_key, value) - local rank = minetest.parse_json(value) + return { + backend = "default", + top = top, + modstorage = modstorage, - if rank ~= nil and rank.score then - top:set(noprefix_key, rank.score) - end -end, -function() - minetest.log( - "action", - "Sorted rankings by score '"..prefix:sub(1, -2).."'. Took "..((minetest.get_us_time()-timer) / 1e6) - ) - sorting_finished() -end) + get = function(self, pname) + pname = PlayerName(pname) -return { - backend = "default", - top = top, - modstorage = modstorage, + local rank_str = self.modstorage:get_string(pname) - prefix = "", - - op_all = op_all, - - get = function(self, pname) - pname = PlayerName(pname) - - local rank_str = self.modstorage:get_string(pname) + if not rank_str or rank_str == "" then + return false + end - if not rank_str or rank_str == "" then - return false - end + return core.parse_json(rank_str) + end, + set = function(self, pname, newrankings, erase_unset) + pname = PlayerName(pname) - return minetest.parse_json(rank_str) - end, - set = function(self, pname, newrankings, erase_unset) - pname = PlayerName(pname) + if not erase_unset then + local rank = self:get(pname) + if rank then + for k, v in pairs(newrankings) do + rank[k] = v + end - if not erase_unset then - local rank = self:get(pname) - if rank then - for k, v in pairs(newrankings) do - rank[k] = v + newrankings = rank end - - newrankings = rank end - end - - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, minetest.write_json(newrankings)) - end, - add = function(self, pname, amounts) - pname = PlayerName(pname) - - local newrankings = self:get(pname) or {} - for k, v in pairs(amounts) do - newrankings[k] = (newrankings[k] or 0) + v - end + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end, + add = function(self, pname, amounts) + pname = PlayerName(pname) - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, minetest.write_json(newrankings)) - end, - del = function(self, pname) - pname = PlayerName(pname) + local newrankings = self:get(pname) or {} - self.top:set(pname, 0) - self.modstorage:set_string(pname) - end, -} + for k, v in pairs(amounts) do + newrankings[k] = (newrankings[k] or 0) + v + end + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end + } end From 27bfbb957486fb53eef512eae4ee47eefb7bf5e0 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Fri, 10 Mar 2023 15:32:56 +0330 Subject: [PATCH 02/12] Add player contributed bounties to the game --- mods/ctf/ctf_map/maps | 2 +- mods/ctf/ctf_modebase/bounties.lua | 188 ++++++++++++++++++++-- mods/ctf/ctf_modebase/recent_rankings.lua | 6 + mods/ctf/ctf_rankings/default.lua | 82 +++++----- 4 files changed, 221 insertions(+), 57 deletions(-) diff --git a/mods/ctf/ctf_map/maps b/mods/ctf/ctf_map/maps index e831d074cf..3dad69c045 160000 --- a/mods/ctf/ctf_map/maps +++ b/mods/ctf/ctf_map/maps @@ -1 +1 @@ -Subproject commit e831d074cfd388d06f961227706c00023e8ba2ff +Subproject commit 3dad69c045fdadf59e38a09d8217f78cc7790118 diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 1ebae4f4ec..1b7b6e4c79 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -3,6 +3,37 @@ local timer = nil local bounties = {} ctf_modebase.bounties = {} +-- ^ This is for game's own bounties +ctf_modebase.contributed_bounties = { +--[[ + ["player_name"] = { + total = score, + contributors = {["player1"] = amount, ["player2"] = amount, ...} + }, a total bounty of `total` score is on `player_name` player contributed + by those in the list(`player1`, `player2`, ...) + ... +--]] +} +-- ^ This is for player contributed bounties + +local function get_contributors(name) + local bounty = ctf_modebase.contributed_bounties[name] + if not bounty then + return "" + else + local list = "" + local first = true + for contributor, score in pairs(bounty.contributors) do + if first then + list = list .. contributor + first = false + else + list = "," .. list .. contributor + end + end + return list + end +end local function get_reward_str(rewards) local ret = "" @@ -21,7 +52,7 @@ local function set(pname, pteam, rewards) -- -- bounty_kills(int) which is usually 1 -- -- score(int) which is the amount of score given to the one -- -- -- who claims the bounty - local bounty_message = minetest.colorize(CHAT_COLOR, string.format( + local bounty_message = core.colorize(CHAT_COLOR, string.format( "[Bounty] %s. Rewards: %s", pname, get_reward_str(rewards) )) @@ -36,23 +67,52 @@ local function set(pname, pteam, rewards) end local function remove(pname, pteam) - minetest.chat_send_all(minetest.colorize(CHAT_COLOR, string.format("[Bounty] %s is no longer bountied", pname))) + core.chat_send_all(core.colorize(CHAT_COLOR, string.format("[Bounty] %s is no longer bountied", pname))) bounties[pteam] = nil end function ctf_modebase.bounties.claim(player, killer) local pteam = ctf_teams.get(player) - - if not (pteam and bounties[pteam] and bounties[pteam].name == player) then + local is_bounty = not (pteam and bounties[pteam] and bounties[pteam].name == player) + if is_bounty and not ctf_modebase.contributed_bounties[player] then + -- checking if there is bounty on this player return end local rewards = bounties[pteam].rewards local bounty_kill_text = string.format("[Bounty] %s killed %s and got %s", killer, player, get_reward_str(rewards)) - minetest.chat_send_all(minetest.colorize(CHAT_COLOR, bounty_kill_text)) + core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) ctf_modebase.announce(bounty_kill_text) + local rewards = { bounty_kills = 0, score = 0 } + if bounties[pteam] and bounties[pteam].rewards then + rewards = bounties[pteam].rewards + local bounty_kill_text = + string.format( + "[Bounty] %s killed %s and got %s from the game!", + killer, + player, + get_reward_str(rewards) + ) + core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) + ctf_modebase.announce(bounty_kill_text) - bounties[pteam] = nil + bounties[pteam] = nil + end + if ctf_modebase.contributed_bounties[player] then + local score = ctf_modebase.contributed_bounties[player].total + rewards.score = rewards.score + score + local bounty_kill_text = + string.format( + "[Player bounty] %s killed %s and got %d from %s!", + killer, + player, + score, + get_contributors(player) + ) + core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) + ctf_modebase.announce(bounty_kill_text) + ctf_modebase.contributed_bounties[player] = nil + end return rewards end @@ -94,7 +154,7 @@ function ctf_modebase.bounties.reassign() end function ctf_modebase.bounties.reassign_timer() - timer = minetest.after(math.random(180, 360), function() + timer = core.after(math.random(180, 360), function() ctf_modebase.bounties.reassign() ctf_modebase.bounties.reassign_timer() end) @@ -134,7 +194,7 @@ ctf_teams.register_on_allocplayer(function(player, new_team, old_team) end if #output > 0 then - minetest.chat_send_player(pname, table.concat(output, "\n")) + core.chat_send_player(pname, table.concat(output, "\n")) end end) @@ -145,14 +205,14 @@ ctf_core.register_chatcommand_alias("list_bounties", "lb", { local output = {} local x = 0 for tname, bounty in pairs(bounties) do - local player = minetest.get_player_by_name(bounty.name) + local player = core.get_player_by_name(bounty.name) if player and pteam ~= tname then local label = string.format( "label[%d,0.1;%s: %s score]", x, bounty.name, - minetest.colorize("cyan", bounty.rewards.score) + core.colorize("cyan", bounty.rewards.score) ) table.insert(output, label) @@ -166,19 +226,40 @@ ctf_core.register_chatcommand_alias("list_bounties", "lb", { x = x + 4.5 end end + for pname, bounty in pairs(ctf_modebase.contributed_bounties) do + local player = core.get_player_by_name(pname) + if player then + local label = string.format( + "label[%d,0.1;%s: %s score]", + x, + pname, + core.colorize("cyan", bounty.total) + ) + table.insert(output, label) + + local model = "model[%d,1;4,6;player;character.b3d;%s;{0,160};;;]" + model = string.format( + model, + x, + player:get_properties().textures[1] + ) + table.insert(output, model) + x = x + 4.5 + end + end if #output <= 0 then return false, "There are no bounties you can claim" end x = x - 1.5 local formspec = "size[" .. x .. ",6]\n" .. table.concat(output, "\n") - minetest.show_formspec(name, "ctf_modebase:lb", formspec) + core.show_formspec(name, "ctf_modebase:lb", formspec) return true, "" - end + end, }) -ctf_core.register_chatcommand_alias("put_bounty", "pb", { - description = "Put bounty on some player", +ctf_core.register_chatcommand_alias("place_bounty", "pb", { + description = "Place bounty on some player", params = " ", privs = { ctf_admin = true }, func = function(name, param) @@ -202,9 +283,86 @@ ctf_core.register_chatcommand_alias("put_bounty", "pb", { pteam, { bounty_kills=1, score=amount } ) - return true, "Successfully placed a bounty of " .. amount .. " on " .. minetest.colorize(team_colour, player) .. "!" + return true, "Successfully placed a bounty of " .. amount .. " on " .. core.colorize(team_colour, player) .. "!" else return false, "Invalid Amount" end end, }) + + + +ctf_core.register_chatcommand_alias("bounty", "b", { + description = "Place a bounty on someone using your match score.\n" .. + "The score is returned to you if the match ends and nobody kills.\n" .. + "Use negative score to revoke a bounty", + params = " ", + func = function(name, params) + local bname, amount = string.match(params, "([^%s]*) ([^%s]*)") + if not (amount and bname) then + return false, "Missing argument(s)" + end + amount = math.floor(amount) + local bteam = ctf_teams.get(bname) + if not bteam then + return false, "This player isn't online or isn't in a team" + end + if bteam == ctf_teams.get(name) then + return false, "You cannot place a bounty on your teammate!" + end + local current_mode = ctf_modebase:get_current_mode() + if amount <= 0 then + local contributors = ctf_modebase.contribued_bounties[bname] + local contributed_amount = contributors[bname] or 0 + if math.abs(amount) > contributed_amount then + contributed_amount = math.abs(amount) + end + ctf_modebase.contributed_bounties[bname][name] = ctf_modebase.contributed_bounties[bname][name] - contributed_amount + current_mode.recent_rankings.add(name, { score = contributed_amount }, true) + return true, tostring(contributed_amount) .. " points returned to you." + end + + if amount < 5 then + return false, "Your bounty needs to be of at least 5 points" + end + if amount > 100 then + return false, "Your bounty cannot be of more than 100 points" + end + + if not current_mode or not ctf_modebase.match_started then + return false, "Match has not started yet." + end + local cur_score = current_mode.recent_rankings.get(name).score or 0 + if amount > cur_score then + return false, "You haven't got enough points" + end + current_mode.recent_rankings.add(name, {score=-amount}, true) + if not ctf_modebase.contributed_bounties[bname] then + local contributors = {} + contributors[name] = amount + ctf_modebase.contributed_bounties[bname] = { total = amount, contributors = contributors } + else + if not ctf_modebase.contributed_bounties[bname].contributors[name] then + ctf_modebase.contributed_bounties[bname].contributors[name] = amount + else + ctf_modebase.contributed_bounties[bname].contributors[name] = + ctf_modebase.contributed_bounties[bname].contributors[name] + amount + end + ctf_modebase.contributed_bounties[bname].total = ctf_modebase.contributed_bounties[bname].total + amount + end + local total = ctf_modebase.contributed_bounties[bname].total + core.chat_send_all( + core.colorize( + CHAT_COLOR, + string.format("%s placed %d bounty on %s!", get_contributors(bname), total, bname))) + end, +}) + +ctf_api.register_on_match_end(function() + local current_mode = ctf_modebase:get_current_mode() + for pname, bounty in pairs(ctf_modebase.contributed_bounties) do + for contributor, amount in pairs(bounty.contributors) do + current_mode.recent_rankings.add(contributor, {score=amount}, true) + end + end +end) diff --git a/mods/ctf/ctf_modebase/recent_rankings.lua b/mods/ctf/ctf_modebase/recent_rankings.lua index 42ea7fb07c..63da948fd0 100644 --- a/mods/ctf/ctf_modebase/recent_rankings.lua +++ b/mods/ctf/ctf_modebase/recent_rankings.lua @@ -74,6 +74,12 @@ return { on_match_end = function() rankings_players = {} rankings_teams = {} + local current_mode = ctf_modebase:get_current_mode() + for _, bounties in pairs(ctf_modebase.contributed_bounties) do + for bounty_donator, bounty_amount in pairs(bounties["contributors"]) do + current_mode.recent_rankings.add(bounty_donator, {score=bounty_amount}, true) + end + end end, players = function() return rankings_players end, teams = function() diff --git a/mods/ctf/ctf_rankings/default.lua b/mods/ctf/ctf_rankings/default.lua index e4ebb6eed0..bacd7e7f06 100644 --- a/mods/ctf/ctf_rankings/default.lua +++ b/mods/ctf/ctf_rankings/default.lua @@ -1,57 +1,57 @@ return function(top) - local modstorage = assert(core.get_mod_storage(), "Can only init rankings at runtime!") +local modstorage = assert(core.get_mod_storage(), "Can only init rankings at runtime!") - for k, v in pairs(modstorage:to_table()["fields"]) do - local rank = core.parse_json(v) - if rank ~= nil and rank.score then - top:set(k, rank.score) - end +for k, v in pairs(modstorage:to_table()["fields"]) do + local rank = core.parse_json(v) + if rank ~= nil and rank.score then + top:set(k, rank.score) end +end - return { - backend = "default", - top = top, - modstorage = modstorage, - - get = function(self, pname) - pname = PlayerName(pname) +return { + backend = "default", + top = top, + modstorage = modstorage, - local rank_str = self.modstorage:get_string(pname) + get = function(self, pname) + pname = PlayerName(pname) - if not rank_str or rank_str == "" then - return false - end + local rank_str = self.modstorage:get_string(pname) - return core.parse_json(rank_str) - end, - set = function(self, pname, newrankings, erase_unset) - pname = PlayerName(pname) + if not rank_str or rank_str == "" then + return false + end - if not erase_unset then - local rank = self:get(pname) - if rank then - for k, v in pairs(newrankings) do - rank[k] = v - end + return core.parse_json(rank_str) + end, + set = function(self, pname, newrankings, erase_unset) + pname = PlayerName(pname) - newrankings = rank + if not erase_unset then + local rank = self:get(pname) + if rank then + for k, v in pairs(newrankings) do + rank[k] = v end - end - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, core.write_json(newrankings)) - end, - add = function(self, pname, amounts) - pname = PlayerName(pname) + newrankings = rank + end + end - local newrankings = self:get(pname) or {} + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end, + add = function(self, pname, amounts) + pname = PlayerName(pname) - for k, v in pairs(amounts) do - newrankings[k] = (newrankings[k] or 0) + v - end + local newrankings = self:get(pname) or {} - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, core.write_json(newrankings)) + for k, v in pairs(amounts) do + newrankings[k] = (newrankings[k] or 0) + v end - } + + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end +} end From f9949f87cbdd0edc4fdc3210fcaa0f8a8504c561 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Tue, 3 Dec 2024 19:34:37 +0330 Subject: [PATCH 03/12] update map submodule --- mods/ctf/ctf_map/maps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/ctf/ctf_map/maps b/mods/ctf/ctf_map/maps index 3dad69c045..e831d074cf 160000 --- a/mods/ctf/ctf_map/maps +++ b/mods/ctf/ctf_map/maps @@ -1 +1 @@ -Subproject commit 3dad69c045fdadf59e38a09d8217f78cc7790118 +Subproject commit e831d074cfd388d06f961227706c00023e8ba2ff From 2a3aaa9ef1cf94b6f5a4efb60e5461894837a9c6 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Tue, 3 Dec 2024 19:39:57 +0330 Subject: [PATCH 04/12] make luacheck happy --- mods/ctf/ctf_modebase/bounties.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 1b7b6e4c79..144c456f5c 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -80,12 +80,7 @@ function ctf_modebase.bounties.claim(player, killer) end local rewards = bounties[pteam].rewards - local bounty_kill_text = string.format("[Bounty] %s killed %s and got %s", killer, player, get_reward_str(rewards)) - core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) - ctf_modebase.announce(bounty_kill_text) - local rewards = { bounty_kills = 0, score = 0 } if bounties[pteam] and bounties[pteam].rewards then - rewards = bounties[pteam].rewards local bounty_kill_text = string.format( "[Bounty] %s killed %s and got %s from the game!", @@ -101,6 +96,9 @@ function ctf_modebase.bounties.claim(player, killer) if ctf_modebase.contributed_bounties[player] then local score = ctf_modebase.contributed_bounties[player].total rewards.score = rewards.score + score + rewards.bounty_kills = + #ctf_modebase.contributed_bounties[player].contributors + + rewards.bounty_kills local bounty_kill_text = string.format( "[Player bounty] %s killed %s and got %d from %s!", From 5de4c44e1d4241b90a12722c430506a38c1f9cbf Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sat, 17 May 2025 10:00:17 +0330 Subject: [PATCH 05/12] make luacheck happy for rankings --- mods/ctf/ctf_rankings/default.lua | 216 +++++++++++++++--------------- 1 file changed, 109 insertions(+), 107 deletions(-) diff --git a/mods/ctf/ctf_rankings/default.lua b/mods/ctf/ctf_rankings/default.lua index 84ef4e8302..b08249a361 100644 --- a/mods/ctf/ctf_rankings/default.lua +++ b/mods/ctf/ctf_rankings/default.lua @@ -1,139 +1,141 @@ local top = ctf_core.include_files("top.lua"):new() return function(prefix, sorting_finished) + local modstorage = + assert(minetest.get_mod_storage(), "Can only init rankings at runtime!") + + -- If callback isn't passed then coroutine will never yield + local function op_all(operation, callback) + if not callback then + minetest.log( + "warning", + "op_all() called without callback, it will block the server step until it finishes" + ) + end -local modstorage = assert(minetest.get_mod_storage(), "Can only init rankings at runtime!") + local TARGET_INTERVAL = 0.1 + local interval = 0.05 + local time = minetest.get_us_time() + local times = 0 + local keys = modstorage:to_table()["fields"] + local c = coroutine.wrap(function() + for k, v in pairs(keys) do + times = times + 1 + operation(k, v) + + if callback and ((minetest.get_us_time() - time) / 1e6) >= interval then + coroutine.yield() + end + end --- If callback isn't passed then coroutine will never yield -local function op_all(operation, callback) - if not callback then - minetest.log("warning", "op_all() called without callback, it will block the server step until it finishes") - end + return "done" + end) - local TARGET_INTERVAL = 0.1 - local interval = 0.05 - local time = minetest.get_us_time() - local times = 0 - local keys = modstorage:to_table()["fields"] - local c = coroutine.wrap(function() - for k, v in pairs(keys) do - times = times + 1 - operation(k, v) - - if callback and ((minetest.get_us_time()-time) / 1e6) >= interval then - coroutine.yield() + local function rep() + if ((minetest.get_us_time() - time) / 1e6) > TARGET_INTERVAL then + interval = interval - 0.01 + else + interval = interval + 0.01 end - end - - return "done" - end) + time = minetest.get_us_time() - local function rep() - if ((minetest.get_us_time()-time) / 1e6) > TARGET_INTERVAL then - interval = interval - 0.01 - else - interval = interval + 0.01 + if c() ~= "done" then + minetest.after(0, rep) + elseif callback then + callback() + end end - time = minetest.get_us_time() - if c() ~= "done" then - minetest.after(0, rep) - elseif callback then - callback() - end + rep() end - rep() -end - -local timer = minetest.get_us_time() -op_all(function(noprefix_key, value) - local rank = minetest.parse_json(value) - - for k, v in pairs(modstorage:to_table()["fields"]) do - local rank = core.parse_json(v) - if rank ~= nil and rank.score then - top:set(k, rank.score) - end - end -end, -function() - core.log( - "action", - "Sorted rankings by score '"..prefix:sub(1, -2).."'. Took "..((minetest.get_us_time()-timer) / 1e6) - ) -end) + local timer = minetest.get_us_time() + op_all(function(noprefix_key, value) + for k, v in pairs(modstorage:to_table()["fields"]) do + local rank = core.parse_json(v) + if rank ~= nil and rank.score then + top:set(k, rank.score) + end + end + end, function() + core.log( + "action", + "Sorted rankings by score '" + .. prefix:sub(1, -2) + .. "'. Took " + .. ((minetest.get_us_time() - timer) / 1e6) + ) + end) -return { - backend = "default", - top = top, - modstorage = modstorage, + return { + backend = "default", + top = top, + modstorage = modstorage, + prefix = "", - prefix = "", + op_all = op_all, - op_all = op_all, + get_top = function(self, rend, sortby, rstart) + local t = self.top:get_top(rend) - get_top = function(self, rend, sortby, rstart) - local t = self.top:get_top(rend) + if sortby ~= "score" then + core.log("error", "Modstorage rankings only support sorting by score") + end - if sortby ~= "score" then - core.log("error", "Modstorage rankings only support sorting by score") - end + local out = {} + for i = (rstart or 1), #t do + out[i] = { t[i] } + end - local out = {} - for i=(rstart or 1), #t do - out[i] = {t[i]} - end + return out + end, + get_place = function(self, pname, sortby) + if sortby ~= "score" then + core.log("error", "Modstorage rankings only support sorting by score") + end - return out - end, - get_place = function(self, pname, sortby) - if sortby ~= "score" then - core.log("error", "Modstorage rankings only support sorting by score") - end + return self.top:get_place(pname) + end, + get = function(self, pname) + pname = PlayerName(pname) - return self.top:get_place(pname) - end, - get = function(self, pname) - pname = PlayerName(pname) + local rank_str = self.modstorage:get_string(pname) - local rank_str = self.modstorage:get_string(pname) + if not rank_str or rank_str == "" then + return false + end - if not rank_str or rank_str == "" then - return false - end + return core.parse_json(rank_str) + end, + set = function(self, pname, newrankings, erase_unset) + pname = PlayerName(pname) - return core.parse_json(rank_str) - end, - set = function(self, pname, newrankings, erase_unset) - pname = PlayerName(pname) + if not erase_unset then + local rank = self:get(pname) + if rank then + for k, v in pairs(newrankings) do + rank[k] = v + end - if not erase_unset then - local rank = self:get(pname) - if rank then - for k, v in pairs(newrankings) do - rank[k] = v + newrankings = rank end - - newrankings = rank end - end - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, core.write_json(newrankings)) - end, - add = function(self, pname, amounts) - pname = PlayerName(pname) + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end, + add = function(self, pname, amounts) + pname = PlayerName(pname) - local newrankings = self:get(pname) or {} + local newrankings = self:get(pname) or {} - for k, v in pairs(amounts) do - newrankings[k] = (newrankings[k] or 0) + v - end + for k, v in pairs(amounts) do + newrankings[k] = (newrankings[k] or 0) + v + end - self.top:set(pname, newrankings.score or 0) - self.modstorage:set_string(pname, core.write_json(newrankings)) - end -} + self.top:set(pname, newrankings.score or 0) + self.modstorage:set_string(pname, core.write_json(newrankings)) + end, + } end From 9b21e62f0494deb9bf390bec856c8f684adeca70 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sat, 17 May 2025 10:02:57 +0330 Subject: [PATCH 06/12] make luacheck happy for ctf_modebase codes --- mods/ctf/ctf_modebase/bounties.lua | 127 ++++++++++++++++------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 63e0b022f4..1fd9b6058a 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -5,7 +5,7 @@ local bounties = {} ctf_modebase.bounties = {} -- ^ This is for game's own bounties ctf_modebase.contributed_bounties = { ---[[ + --[[ ["player_name"] = { total = score, contributors = {["player1"] = amount, ["player2"] = amount, ...} @@ -41,7 +41,13 @@ local function get_reward_str(rewards) local ret = "" for reward, amount in pairs(rewards) do - ret = string.format("%s%s%d %s, ", ret, amount >= 0 and "+" or "-", amount, HumanReadable(reward)) + ret = string.format( + "%s%s%d %s, ", + ret, + amount >= 0 and "+" or "-", + amount, + HumanReadable(reward) + ) end return ret:sub(1, -3) @@ -54,10 +60,10 @@ local function set(pname, pteam, rewards) -- -- bounty_kills(int) which is usually 1 -- -- score(int) which is the amount of score given to the one -- -- -- who claims the bounty - local bounty_message = core.colorize(CHAT_COLOR, string.format( - S("[Bounty] %s. Rewards: %s"), - pname, get_reward_str(rewards) - )) + local bounty_message = core.colorize( + CHAT_COLOR, + string.format(S("[Bounty] %s. Rewards: %s"), pname, get_reward_str(rewards)) + ) for _, team in ipairs(ctf_teams.current_team_list) do -- show bounty to all but target's team if team ~= pteam then @@ -65,11 +71,13 @@ local function set(pname, pteam, rewards) end end - bounties[pteam] = {name = pname, rewards = rewards, msg = bounty_message} + bounties[pteam] = { name = pname, rewards = rewards, msg = bounty_message } end local function remove(pname, pteam) - core.chat_send_all(minetest.colorize(CHAT_COLOR, S("[Bounty] @1 is no longer bountied", pname))) + core.chat_send_all( + minetest.colorize(CHAT_COLOR, S("[Bounty] @1 is no longer bountied", pname)) + ) bounties[pteam] = nil end @@ -83,24 +91,23 @@ function ctf_modebase.bounties.claim(player, killer) local rewards = bounties[pteam].rewards if bounties[pteam] and bounties[pteam].rewards then - local bounty_kill_text = S("[Bounty] @1 killed @2 and got @3", killer, player, get_reward_str(rewards)) + local bounty_kill_text = + S("[Bounty] @1 killed @2 and got @3", killer, player, get_reward_str(rewards)) core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) bounties[pteam] = nil end if ctf_modebase.contributed_bounties[player] then local score = ctf_modebase.contributed_bounties[player].total rewards.score = rewards.score + score - rewards.bounty_kills = - #ctf_modebase.contributed_bounties[player].contributors + rewards.bounty_kills = #ctf_modebase.contributed_bounties[player].contributors + rewards.bounty_kills - local bounty_kill_text = - string.format( - "[Player bounty] %s killed %s and got %d from %s!", - killer, - player, - score, - get_contributors(player) - ) + local bounty_kill_text = string.format( + "[Player bounty] %s killed %s and got %d from %s!", + killer, + player, + score, + get_contributors(player) + ) core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) ctf_modebase.announce(bounty_kill_text) ctf_modebase.contributed_bounties[player] = nil @@ -163,7 +170,7 @@ ctf_api.register_on_match_end(function() end) function ctf_modebase.bounties.bounty_reward_func() - return {bounty_kills = 1, score = 500} + return { bounty_kills = 1, score = 500 } end function ctf_modebase.bounties.get_next_bounty(team_members) @@ -173,7 +180,12 @@ end ctf_teams.register_on_allocplayer(function(player, new_team, old_team) local pname = player:get_player_name() - if old_team and old_team ~= new_team and bounties[old_team] and bounties[old_team].name == pname then + if + old_team + and old_team ~= new_team + and bounties[old_team] + and bounties[old_team].name == pname + then remove(pname, old_team) end @@ -208,12 +220,9 @@ ctf_core.register_chatcommand_alias("list_bounties", "lb", { ) table.insert(output, label) - local model = "model[%d,1;4,6;player;character.b3d;%s,blank.png;{0,160};;;]" - model = string.format( - model, - x, - player:get_properties().textures[1] - ) + local model = + "model[%d,1;4,6;player;character.b3d;%s,blank.png;{0,160};;;]" + model = string.format(model, x, player:get_properties().textures[1]) table.insert(output, model) x = x + 4.5 end @@ -230,11 +239,7 @@ ctf_core.register_chatcommand_alias("list_bounties", "lb", { table.insert(output, label) local model = "model[%d,1;4,6;player;character.b3d;%s;{0,160};;;]" - model = string.format( - model, - x, - player:get_properties().textures[1] - ) + model = string.format(model, x, player:get_properties().textures[1]) table.insert(output, model) x = x + 4.5 end @@ -250,7 +255,6 @@ ctf_core.register_chatcommand_alias("list_bounties", "lb", { end, }) - ctf_core.register_chatcommand_alias("put_bounty", "pb", { description = S("Put bounty on some player"), params = S(" "), @@ -271,24 +275,23 @@ ctf_core.register_chatcommand_alias("put_bounty", "pb", { amount = ctf_core.to_number(amount) if amount then - set( - player, - pteam, - { bounty_kills=1, score=amount } - ) - return true, S("Successfully placed a bounty of ") .. amount .. S(" on ") .. core.colorize(team_colour, player) .. "!" + set(player, pteam, { bounty_kills = 1, score = amount }) + return true, + S("Successfully placed a bounty of ") + .. amount + .. S(" on ") + .. core.colorize(team_colour, player) + .. "!" else return false, S("Invalid Amount") end end, }) - - ctf_core.register_chatcommand_alias("bounty", "b", { - description = "Place a bounty on someone using your match score.\n" .. - "The score is returned to you if the match ends and nobody kills.\n" .. - "Use negative score to revoke a bounty", + description = "Place a bounty on someone using your match score.\n" + .. "The score is returned to you if the match ends and nobody kills.\n" + .. "Use negative score to revoke a bounty", params = " ", func = function(name, params) local bname, amount = string.match(params, "([^%s]*) ([^%s]*)") @@ -310,7 +313,8 @@ ctf_core.register_chatcommand_alias("bounty", "b", { if math.abs(amount) > contributed_amount then contributed_amount = math.abs(amount) end - ctf_modebase.contributed_bounties[bname][name] = ctf_modebase.contributed_bounties[bname][name] - contributed_amount + ctf_modebase.contributed_bounties[bname][name] = ctf_modebase.contributed_bounties[bname][name] + - contributed_amount current_mode.recent_rankings.add(name, { score = contributed_amount }, true) return true, tostring(contributed_amount) .. " points returned to you." end @@ -329,33 +333,42 @@ ctf_core.register_chatcommand_alias("bounty", "b", { if amount > cur_score then return false, "You haven't got enough points" end - current_mode.recent_rankings.add(name, {score=-amount}, true) + current_mode.recent_rankings.add(name, { score = -amount }, true) if not ctf_modebase.contributed_bounties[bname] then local contributors = {} contributors[name] = amount - ctf_modebase.contributed_bounties[bname] = { total = amount, contributors = contributors } + ctf_modebase.contributed_bounties[bname] = + { total = amount, contributors = contributors } else - if not ctf_modebase.contributed_bounties[bname].contributors[name] then - ctf_modebase.contributed_bounties[bname].contributors[name] = amount + local contrib = ctf_modebase.contributed_bounties -- this is a variable to for less typing + if not contrib[bname].contributors[name] then + contrib[bname].contributors[name] = amount else - ctf_modebase.contributed_bounties[bname].contributors[name] = - ctf_modebase.contributed_bounties[bname].contributors[name] + amount + contrib[bname].contributors[name] = contrib[bname].contributors[name] + + amount end - ctf_modebase.contributed_bounties[bname].total = ctf_modebase.contributed_bounties[bname].total + amount + contrib[bname].total = contrib[bname].total + amount end local total = ctf_modebase.contributed_bounties[bname].total core.chat_send_all( core.colorize( - CHAT_COLOR, - string.format("%s placed %d bounty on %s!", get_contributors(bname), total, bname))) + CHAT_COLOR, + string.format( + "%s placed %d bounty on %s!", + get_contributors(bname), + total, + bname + ) + ) + ) end, }) ctf_api.register_on_match_end(function() local current_mode = ctf_modebase:get_current_mode() for pname, bounty in pairs(ctf_modebase.contributed_bounties) do - for contributor, amount in pairs(bounty.contributors) do - current_mode.recent_rankings.add(contributor, {score=amount}, true) - end + for contributor, amount in pairs(bounty.contributors) do + current_mode.recent_rankings.add(contributor, { score = amount }, true) + end end end) From 037380f81443ebfb99a81c1084dadd3de5e82354 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sat, 17 May 2025 10:38:35 +0330 Subject: [PATCH 07/12] fix regressions after resolve conflict --- mods/ctf/ctf_modebase/bounties.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 1fd9b6058a..67c7e5e7fc 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -88,16 +88,25 @@ function ctf_modebase.bounties.claim(player, killer) -- checking if there is bounty on this player return end - - local rewards = bounties[pteam].rewards + local rewards = nil if bounties[pteam] and bounties[pteam].rewards then - local bounty_kill_text = - S("[Bounty] @1 killed @2 and got @3", killer, player, get_reward_str(rewards)) + rewards = bounties[pteam].rewards + end + if rewards then + local bounty_kill_text = S( + "[Bounty] @1 killed @2 and got @3 from the game", + killer, + player, + get_reward_str(rewards) + ) core.chat_send_all(core.colorize(CHAT_COLOR, bounty_kill_text)) bounties[pteam] = nil end if ctf_modebase.contributed_bounties[player] then local score = ctf_modebase.contributed_bounties[player].total + if rewards == nil then + rewards = { bounty_kills = 0, score = 0 } + end rewards.score = rewards.score + score rewards.bounty_kills = #ctf_modebase.contributed_bounties[player].contributors + rewards.bounty_kills From c9f0881318bb35afac60483752be36e807af0252 Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sat, 17 May 2025 10:39:00 +0330 Subject: [PATCH 08/12] add correct dep for sprint mod --- mods/other/sprint/mod.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/other/sprint/mod.conf b/mods/other/sprint/mod.conf index c7aa33a9e1..e2d2506811 100644 --- a/mods/other/sprint/mod.conf +++ b/mods/other/sprint/mod.conf @@ -1,2 +1,2 @@ name = sprint -depends = physics, ctf_api +depends = physics, ctf_api, hudbars From 925dca7d2ba9ee83ae3a758349de43cde4796fdc Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sun, 25 May 2025 17:05:38 +0330 Subject: [PATCH 09/12] use translator fn properly with @ --- mods/ctf/ctf_modebase/bounties.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 67c7e5e7fc..c21dbd0a36 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -62,7 +62,7 @@ local function set(pname, pteam, rewards) -- -- -- who claims the bounty local bounty_message = core.colorize( CHAT_COLOR, - string.format(S("[Bounty] %s. Rewards: %s"), pname, get_reward_str(rewards)) + string.format(S("[Bounty] @1. Rewards: @2", pname, get_reward_str(rewards))) ) for _, team in ipairs(ctf_teams.current_team_list) do -- show bounty to all but target's team @@ -286,11 +286,11 @@ ctf_core.register_chatcommand_alias("put_bounty", "pb", { if amount then set(player, pteam, { bounty_kills = 1, score = amount }) return true, - S("Successfully placed a bounty of ") - .. amount - .. S(" on ") - .. core.colorize(team_colour, player) - .. "!" + S( + "Successfully placed a bounty of @1 on @2", + amount, + core.colorize(team_colour, player) + ) else return false, S("Invalid Amount") end From 5ad3e9ddbed7fc845ad71b4ef5c2acf619ec3fcb Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sun, 25 May 2025 17:11:48 +0330 Subject: [PATCH 10/12] remove duplicated code for returning bounty scores if unclaimed --- mods/ctf/ctf_modebase/bounties.lua | 9 --------- 1 file changed, 9 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index c21dbd0a36..55b9731aa7 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -372,12 +372,3 @@ ctf_core.register_chatcommand_alias("bounty", "b", { ) end, }) - -ctf_api.register_on_match_end(function() - local current_mode = ctf_modebase:get_current_mode() - for pname, bounty in pairs(ctf_modebase.contributed_bounties) do - for contributor, amount in pairs(bounty.contributors) do - current_mode.recent_rankings.add(contributor, { score = amount }, true) - end - end -end) From 91d35841703517a50d947d16994415a65d1e39dc Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sun, 25 May 2025 18:05:49 +0330 Subject: [PATCH 11/12] fix return unclaimed bounty score to the player --- mods/ctf/ctf_modebase/bounties.lua | 15 ++ mods/ctf/ctf_modebase/recent_rankings.lua | 168 +++++++++++----------- 2 files changed, 101 insertions(+), 82 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index 55b9731aa7..c9b33e5f5d 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -372,3 +372,18 @@ ctf_core.register_chatcommand_alias("bounty", "b", { ) end, }) + +ctf_api.register_on_match_end(function() + -- there might be some unclaimed player bounties, here we return + -- the points to their contributors + local current_mode = ctf_modebase:get_current_mode() + for _, bounties2 in pairs(ctf_modebase.contributed_bounties) do + for bounty_donator, bounty_amount in pairs(bounties2["contributors"]) do + current_mode.recent_rankings.add( + bounty_donator, + { score = bounty_amount }, + true + ) + end + end +end) diff --git a/mods/ctf/ctf_modebase/recent_rankings.lua b/mods/ctf/ctf_modebase/recent_rankings.lua index 63da948fd0..9e2678c2e1 100644 --- a/mods/ctf/ctf_modebase/recent_rankings.lua +++ b/mods/ctf/ctf_modebase/recent_rankings.lua @@ -1,98 +1,102 @@ ctf_modebase.recent_rankings = function(rankings) + local rankings_players = {} + local rankings_teams = {} + + return { + add = function(player, amounts, no_hud) + player = PlayerName(player) + + if not no_hud then + local hud_text = "" + for name, val in pairs(amounts) do + hud_text = string.format( + "%s%s%d %s | ", + hud_text, + val >= 0 and "+" or "", + math.round(val), + HumanReadable(name) + ) + end -local rankings_players = {} -local rankings_teams = {} - -return { - add = function(player, amounts, no_hud) - player = PlayerName(player) - - if not no_hud then - local hud_text = "" - for name, val in pairs(amounts) do - hud_text = string.format("%s%s%d %s | ", hud_text, val >= 0 and "+" or "", math.round(val), HumanReadable(name)) + hud_events.new(player, { text = hud_text:sub(1, -4) }) end - hud_events.new(player, {text = hud_text:sub(1, -4)}) - end - - if not rankings_players[player] then - rankings_players[player] = {} - end + if not rankings_players[player] then + rankings_players[player] = {} + end - local team = rankings_players[player]._team + local team = rankings_players[player]._team - for stat, amount in pairs(amounts) do - rankings_players[player][stat] = (rankings_players[player][stat] or 0) + amount + for stat, amount in pairs(amounts) do + rankings_players[player][stat] = (rankings_players[player][stat] or 0) + + amount - if team then - rankings_teams[team][stat] = (rankings_teams[team][stat] or 0) + amount - if stat == "score" then - rankings_players[player][team.."_"..stat] = (rankings_players[player][team.."_"..stat] or 0) + amount + if team then + rankings_teams[team][stat] = (rankings_teams[team][stat] or 0) + + amount + if stat == "score" then + rankings_players[player][team .. "_" .. stat] = ( + rankings_players[player][team .. "_" .. stat] or 0 + ) + amount + end end end - end - - rankings:add(player, amounts) - end, - get = function(player) - player = PlayerName(player) - return rankings_players[player] or {} - end, - set_team = function(player, team) - player = PlayerName(player) - local tcolor = ctf_teams.team[team].color - - if not rankings_players[player] then - rankings_players[player] = {} - end - - if not rankings_teams[team] then - rankings_teams[team] = {} - end - - rankings_players[player]._row_color = tcolor - rankings_players[player]._team = team - end, - on_leaveplayer = function(player) - player = PlayerName(player) - - if rankings_players[player] then - local count = 0 - - for k in pairs(rankings_players[player]) do - if k:sub(1, 1) ~= "_" then - count = count + 1 - end + + rankings:add(player, amounts) + end, + get = function(player) + player = PlayerName(player) + return rankings_players[player] or {} + end, + set_team = function(player, team) + player = PlayerName(player) + local tcolor = ctf_teams.team[team].color + + if not rankings_players[player] then + rankings_players[player] = {} end - if count == 0 then - rankings_players[player] = nil + if not rankings_teams[team] then + rankings_teams[team] = {} end - end - end, - on_match_end = function() - rankings_players = {} - rankings_teams = {} - local current_mode = ctf_modebase:get_current_mode() - for _, bounties in pairs(ctf_modebase.contributed_bounties) do - for bounty_donator, bounty_amount in pairs(bounties["contributors"]) do - current_mode.recent_rankings.add(bounty_donator, {score=bounty_amount}, true) + + rankings_players[player]._row_color = tcolor + rankings_players[player]._team = team + end, + on_leaveplayer = function(player) + player = PlayerName(player) + + if rankings_players[player] then + local count = 0 + + for k in pairs(rankings_players[player]) do + if k:sub(1, 1) ~= "_" then + count = count + 1 + end + end + + if count == 0 then + rankings_players[player] = nil + end end - end - end, - players = function() return rankings_players end, - teams = function() - local out = {} - - for k, v in pairs(rankings_teams) do - if not ctf_teams.team[k].not_playing then - out[k] = v + end, + on_match_end = function() + rankings_players = {} + rankings_teams = {} + end, + players = function() + return rankings_players + end, + teams = function() + local out = {} + + for k, v in pairs(rankings_teams) do + if not ctf_teams.team[k].not_playing then + out[k] = v + end end - end - - return out - end, -} + return out + end, + } end From d85bebd0ed3c5dedb1ffd9c9ff410fbf6419ae4b Mon Sep 17 00:00:00 2001 From: Farooq Karimi Zadeh Date: Sun, 25 May 2025 18:36:47 +0330 Subject: [PATCH 12/12] correctly return unclaimed bounty score, prevent putting bounties on teammates after team change --- mods/ctf/ctf_modebase/bounties.lua | 14 + mods/ctf/ctf_modebase/features.lua | 1615 +++++++++++++++------------- 2 files changed, 901 insertions(+), 728 deletions(-) diff --git a/mods/ctf/ctf_modebase/bounties.lua b/mods/ctf/ctf_modebase/bounties.lua index c9b33e5f5d..55d1b42452 100644 --- a/mods/ctf/ctf_modebase/bounties.lua +++ b/mods/ctf/ctf_modebase/bounties.lua @@ -16,6 +16,20 @@ ctf_modebase.contributed_bounties = { } -- ^ This is for player contributed bounties +-- Whenever players change team, e.g. in many team maps or on rejoin +-- this has to be called +ctf_teams.register_on_allocplayer(function() + local cur_mode = ctf_modebase:get_current_mode() + for target_name, bounties2 in pairs(ctf_modebase.contributed_bounties) do + for contributor, amount in pairs(bounties2.contributors) do + if ctf_teams.get(target_name) == ctf_teams.get(contributor) then + cur_mode.recent_rankings.add(contributor, { score = amount }, true) + bounties[contributor] = nil + end + end + end +end) + local function get_contributors(name) local bounty = ctf_modebase.contributed_bounties[name] if not bounty then diff --git a/mods/ctf/ctf_modebase/features.lua b/mods/ctf/ctf_modebase/features.lua index 204c9c66f0..a4c0181541 100644 --- a/mods/ctf/ctf_modebase/features.lua +++ b/mods/ctf/ctf_modebase/features.lua @@ -6,7 +6,9 @@ local S = minetest.get_translator(minetest.get_current_modname()) local function supports_observers(x) if x then - if x.object then x = x.object end + if x.object then + x = x.object + end if x.get_observers and x:get_pos() then return true @@ -17,9 +19,10 @@ local function supports_observers(x) end local function update_playertag(player, t, nametag, team_nametag, symbol_nametag) - if not supports_observers(nametag.object) or - not supports_observers(team_nametag.object) or - not supports_observers(symbol_nametag.object) + if + not supports_observers(nametag.object) + or not supports_observers(team_nametag.object) + or not supports_observers(symbol_nametag.object) then return end @@ -33,7 +36,8 @@ local function update_playertag(player, t, nametag, team_nametag, symbol_nametag local setting = extra if setting == true then - setting = ctf_settings.get(minetest.get_player_by_name(n), "teammate_nametag_style") + setting = + ctf_settings.get(minetest.get_player_by_name(n), "teammate_nametag_style") end if setting == "3" then @@ -51,9 +55,9 @@ local function update_playertag(player, t, nametag, team_nametag, symbol_nametag end end - nametag.object:set_observers(entity_players ) - team_nametag.object:set_observers(nametag_players) - symbol_nametag.object:set_observers(symbol_players ) + nametag.object:set_observers(entity_players) + team_nametag.object:set_observers(nametag_players) + symbol_nametag.object:set_observers(symbol_players) end local tags_hidden = false @@ -99,10 +103,14 @@ local function set_playertags_state(state) local nametag = playertag.entity local symbol_entity = playertag.symbol_entity - if supports_observers(nametag) and supports_observers(team_nametag) and supports_observers(symbol_entity) then - team_nametag.object:set_observers({}) + if + supports_observers(nametag) + and supports_observers(team_nametag) + and supports_observers(symbol_entity) + then + team_nametag.object:set_observers({}) symbol_entity.object:set_observers({}) - nametag.object:set_observers({}) + nametag.object:set_observers({}) end end end @@ -119,24 +127,24 @@ function ctf_modebase.map_chosen(map, ...) if ctf_teams.get(p) then mapload_huds:add(p, "loading_screen", { hud_elem_type = "image", - position = {x = 0.5, y = 0.5}, + position = { x = 0.5, y = 0.5 }, image_scale = -100, z_index = 1000, - texture = "[combine:1x1^[invert:rgba^[opacity:1^[colorize:#141523:255" + texture = "[combine:1x1^[invert:rgba^[opacity:1^[colorize:#141523:255", }) mapload_huds:add(p, "map_image", { hud_elem_type = "image", - position = {x = 0.5, y = 0.5}, + position = { x = 0.5, y = 0.5 }, image_scale = -100, z_index = 1001, - texture = map.dirname.."_screenshot.png^[opacity:30", + texture = map.dirname .. "_screenshot.png^[opacity:30", }) mapload_huds:add(p, "loading_text", { hud_elem_type = "text", - position = {x = 0.5, y = 0.5}, - alignment = {x = "center", y = "up"}, + position = { x = 0.5, y = 0.5 }, + alignment = { x = "center", y = "up" }, text_scale = 2, text = S("Loading Map") .. ": " .. map.name .. "...", color = 0x7ec5ff, @@ -144,8 +152,8 @@ function ctf_modebase.map_chosen(map, ...) }) mapload_huds:add(p, { hud_elem_type = "text", - position = {x = 0.5, y = 0.75}, - alignment = {x = "center", y = "center"}, + position = { x = 0.5, y = 0.75 }, + alignment = { x = "center", y = "center" }, text = random_messages.get_random_message(), color = 0xffffff, z_index = 1002, @@ -161,889 +169,1040 @@ end ctf_settings.register("teammate_nametag_style", { type = "list", description = S("Controls what style of nametag to use for teammates."), - list = {"Minetest Nametag: Full", "Minetest Nametag: Symbol", "Entity Nametag"}, + list = { "Minetest Nametag: Full", "Minetest Nametag: Symbol", "Entity Nametag" }, default = "1", on_change = function(player, new_value) - minetest.log("action", "Player "..player:get_player_name().." changed their nametag setting") + minetest.log( + "action", + "Player " .. player:get_player_name() .. " changed their nametag setting" + ) ctf_modebase.update_playertags() - end + end, }) ctf_modebase.features = function(rankings, recent_rankings) + local FLAG_MESSAGE_COLOR = "#d9b72a" + local FLAG_CAPTURE_TIMER = 60 * 3 + local many_teams = false + local team_list + local teams_left + local death_messages = { + ["grenades_frag"] = { "blown up", "fragged" }, + ["knockback_grenade"] = { "sent flying", "doomed to fall" }, + ["black_hole_grenade"] = { "sucked into the void" }, + ["sword"] = { "killed", "sliced up" }, + ["axe"] = { "killed", "chopped up" }, + ["shovel"] = { "killed with the tool that dug your grave" }, + ["pick"] = { "killed", "mistaken for mese ore" }, + ["ctf_ranged"] = { "shot" }, + ["rifle"] = { "sniped" }, + ["default_water"] = { "drowned", "over-hydrated" }, + ["damage_cobble"] = { "mined a little too much damage cobble" }, + ["lava"] = { "tried to swim in lava", "took a hot bath" }, + ["fire"] = { "burnt to a crisp" }, + } -local FLAG_MESSAGE_COLOR = "#d9b72a" -local FLAG_CAPTURE_TIMER = 60 * 3 -local many_teams = false -local team_list -local teams_left -local death_messages = { - ["grenades_frag"] = {"blown up", "fragged"}, - ["knockback_grenade"] = {"sent flying", "doomed to fall"}, - ["black_hole_grenade"] = {"sucked into the void"}, - ["sword"] = {"killed", "sliced up"}, - ["axe"] = {"killed", "chopped up",}, - ["shovel"] = {"killed with the tool that dug your grave"}, - ["pick"] = {"killed", "mistaken for mese ore"}, - ["ctf_ranged"] = {"shot"}, - ["rifle"] = {"sniped"}, - ["default_water"] = {"drowned", "over-hydrated"}, - ["damage_cobble"] = {"mined a little too much damage cobble"}, - ["lava"] = {"tried to swim in lava", "took a hot bath"}, - ["fire"] = {"burnt to a crisp"}, -} - -local function calculate_killscore(player) - local match_rank = recent_rankings.players()[player] or {} - local kd = (match_rank.kills or 1) / (match_rank.deaths or 1) - local flag_multiplier = 1 - for tname, carrier in pairs(ctf_modebase.flag_taken) do - if carrier.p == player then - flag_multiplier = flag_multiplier * 2 - end - end - - minetest.log("ACTION", string.format( - "[KILLDEBUG] { og = %f, kills = %d, assists = %f, deaths = %d, score = %f, hp_healed = %f, attempts = %d, " .. - "reward_given_to_enemy = %f },", - math.max(1, math.round(kd * 7 * flag_multiplier)), - match_rank.kills or 1, - match_rank.kill_assists or 0, - match_rank.deaths or 1, - match_rank.score or 0, - match_rank.hp_healed or 0, - match_rank.flag_attempts or 0, - match_rank.reward_given_to_enemy or 0 - )) - - return math.max(1, math.round(kd * 7 * flag_multiplier)) -end - -local damage_group_textures = { - grenade = "grenades_frag.png", - knockback_grenade = "ctf_mode_nade_fight_knockback_grenade.png", - black_hole_grenade = "ctf_mode_nade_fight_black_hole_grenade.png", - damage_cobble = "ctf_map_damage_cobble.png", - landmine = "ctf_landmine_landmine.png", -} - -local function get_weapon_image(hitter, tool_capabilities) - local image - - for group, texture in pairs(damage_group_textures) do - if tool_capabilities.damage_groups[group] then - image = texture - break + local function calculate_killscore(player) + local match_rank = recent_rankings.players()[player] or {} + local kd = (match_rank.kills or 1) / (match_rank.deaths or 1) + local flag_multiplier = 1 + for tname, carrier in pairs(ctf_modebase.flag_taken) do + if carrier.p == player then + flag_multiplier = flag_multiplier * 2 + end end - end - if not image then - image = hitter:get_wielded_item():get_definition().inventory_image - end + minetest.log( + "ACTION", + string.format( + "[KILLDEBUG] { og = %f, kills = %d, assists = %f, deaths = %d, score = %f, hp_healed = %f, attempts = %d, " + .. "reward_given_to_enemy = %f },", + math.max(1, math.round(kd * 7 * flag_multiplier)), + match_rank.kills or 1, + match_rank.kill_assists or 0, + match_rank.deaths or 1, + match_rank.score or 0, + match_rank.hp_healed or 0, + match_rank.flag_attempts or 0, + match_rank.reward_given_to_enemy or 0 + ) + ) - if image == "" then - image = "ctf_kill_list_punch.png" + return math.max(1, math.round(kd * 7 * flag_multiplier)) end - if tool_capabilities.damage_groups.ranged then - image = image .. "^[transformFX" - elseif tool_capabilities.damage_groups.poison_grenade then - image = "grenades_smoke_grenade.png^[multiply:#00ff00" - end + local damage_group_textures = { + grenade = "grenades_frag.png", + knockback_grenade = "ctf_mode_nade_fight_knockback_grenade.png", + black_hole_grenade = "ctf_mode_nade_fight_black_hole_grenade.png", + damage_cobble = "ctf_map_damage_cobble.png", + landmine = "ctf_landmine_landmine.png", + } - return image -end + local function get_weapon_image(hitter, tool_capabilities) + local image -local function get_suicide_image(reason) - local image = "ctf_modebase_skull.png" - - if reason.type == "node_damage" then - local node = reason.node - if node == "ctf_map:spike" then - image = "ctf_map_spike.png" - elseif node:match("default:lava") then - image = "default_lava.png" - elseif node:match("fire:") then - image = "fire_basic_flame.png" + for group, texture in pairs(damage_group_textures) do + if tool_capabilities.damage_groups[group] then + image = texture + break + end end - elseif reason.type == "drown" then - image = "default_water.png" - end - return image -end + if not image then + image = hitter:get_wielded_item():get_definition().inventory_image + end -local function send_death_message(player, killer, weapon_image) - local death_setting = ctf_settings.get(minetest.get_player_by_name(player), "send_death_message") - local assist_message = "" - local weapon_message - local hitters = ctf_combat_mode.get_other_hitters(player, killer) + if image == "" then + image = "ctf_kill_list_punch.png" + end - local k_teamcolor = ctf_teams.get(killer) - if k_teamcolor then - k_teamcolor = ctf_teams.team[k_teamcolor].color - end - for key, data in pairs(death_messages) do - if weapon_image:find(tostring(key)) then - weapon_message = data[math.random(1,#data)] - end - end - - if #hitters > 0 then - assist_message = ", assisted by " - for index, pname in ipairs(hitters) do - local a_teamcolor = ctf_teams.get(pname) - if a_teamcolor then - a_teamcolor = ctf_teams.team[a_teamcolor].color - end - if index == 1 then - assist_message = assist_message .. minetest.colorize(a_teamcolor, pname) - elseif index == #hitters then - assist_message = assist_message .. ", and " .. minetest.colorize(a_teamcolor, pname) - else - assist_message = assist_message .. ", " .. minetest.colorize(a_teamcolor, pname) - end + if tool_capabilities.damage_groups.ranged then + image = image .. "^[transformFX" + elseif tool_capabilities.damage_groups.poison_grenade then + image = "grenades_smoke_grenade.png^[multiply:#00ff00" end - end - - if (death_setting == "true") then - if player ~= killer then - if weapon_message then - local death_message = "You were " .. weapon_message - .. " by " .. minetest.colorize(k_teamcolor, killer) .. assist_message .. "." - minetest.chat_send_player(player, death_message) - else - local death_message = "You were killed by " - .. minetest.colorize(k_teamcolor, killer) .. assist_message .. "." - minetest.chat_send_player(player, death_message) - end - end - if player == killer and #hitters == 0 then - local suicide_message = weapon_message or "suicided" - local death_message = "You " .. suicide_message .. assist_message .. "." - minetest.chat_send_player(player, death_message) - end - end -end -local function tp_player_near_flag(player) - local tname = ctf_teams.get(player) - if not tname then return end + return image + end - local rotation_y - local pos + local function get_suicide_image(reason) + local image = "ctf_modebase_skull.png" + + if reason.type == "node_damage" then + local node = reason.node + if node == "ctf_map:spike" then + image = "ctf_map_spike.png" + elseif node:match("default:lava") then + image = "default_lava.png" + elseif node:match("fire:") then + image = "fire_basic_flame.png" + end + elseif reason.type == "drown" then + image = "default_water.png" + end - if ctf_map.current_map.teams[tname] then - pos = vector.offset(ctf_map.current_map.teams[tname].flag_pos, - math.random(-1, 1), - 0.5, - math.random(-1, 1) - ) - rotation_y = vector.dir_to_rotation( - vector.direction(pos, ctf_map.current_map.teams[tname].look_pos or ctf_map.current_map.flag_center) - ).y - else - pos = vector.add(ctf_map.current_map.pos1, vector.divide(ctf_map.current_map.size, 2)) - rotation_y = player:get_look_horizontal() + return image end - local function apply() - player:set_pos(pos) - player:set_look_vertical(0) - player:set_look_horizontal(rotation_y) - end + local function send_death_message(player, killer, weapon_image) + local death_setting = + ctf_settings.get(minetest.get_player_by_name(player), "send_death_message") + local assist_message = "" + local weapon_message + local hitters = ctf_combat_mode.get_other_hitters(player, killer) - apply() - minetest.after(0.1, function() -- TODO remove after respawn bug will be fixed - if player:is_player() then - apply() + local k_teamcolor = ctf_teams.get(killer) + if k_teamcolor then + k_teamcolor = ctf_teams.team[k_teamcolor].color end - end) - - return true -end - -local function celebrate_team(teamname) - for _, player in ipairs(minetest.get_connected_players()) do - local pname = player:get_player_name() - local pteam = ctf_teams.get(pname) - local volume = (tonumber(ctf_settings.get(player, "flag_sound_volume")) or 10.0) / 10 - - if volume > 0 then - if pteam == teamname then - minetest.sound_play("ctf_modebase_trumpet_positive", { - to_player = pname, - gain = volume, - pitch = 1.0, - }, true) - else - minetest.sound_play("ctf_modebase_trumpet_negative", { - to_player = pname, - gain = volume, - pitch = 1.0, - }, true) + for key, data in pairs(death_messages) do + if weapon_image:find(tostring(key)) then + weapon_message = data[math.random(1, #data)] end end - end -end -local function drop_flag(teamname) - for _, player in ipairs(minetest.get_connected_players()) do - local pname = player:get_player_name() - local pteam = ctf_teams.get(pname) - local drop_volume = (tonumber(ctf_settings.get(player, "flag_sound_volume")) or 10.0) / 10 - - if pteam and drop_volume > 0 then - if pteam == teamname then - minetest.sound_play("ctf_modebase_drop_flag_negative", { - to_player = pname, - gain = math.max(0.1, drop_volume - 0.5), - pitch = 1.0, - }, true) - else - minetest.sound_play("ctf_modebase_drop_flag_positive", { - to_player = pname, - gain = math.max(0.1, drop_volume - 0.5), - pitch = 1.0, - }, true) + if #hitters > 0 then + assist_message = ", assisted by " + for index, pname in ipairs(hitters) do + local a_teamcolor = ctf_teams.get(pname) + if a_teamcolor then + a_teamcolor = ctf_teams.team[a_teamcolor].color + end + if index == 1 then + assist_message = assist_message + .. minetest.colorize(a_teamcolor, pname) + elseif index == #hitters then + assist_message = assist_message + .. ", and " + .. minetest.colorize(a_teamcolor, pname) + else + assist_message = assist_message + .. ", " + .. minetest.colorize(a_teamcolor, pname) + end end end - end -end -local function end_combat_mode(player, reason, killer, weapon_image) - local comment = nil - - if ctf_teams.get(player) then - if reason == "combatlog" then - killer, weapon_image = ctf_combat_mode.get_last_hitter(player) - if killer then - comment = " (Combat Log)" - recent_rankings.add(player, {deaths = 1}, true) - end - else - if reason ~= "punch" or killer == player then - if reason == "punch" then - ctf_kill_list.add(player, player, weapon_image) - send_death_message(player, killer, weapon_image) + if death_setting == "true" then + if player ~= killer then + if weapon_message then + local death_message = "You were " + .. weapon_message + .. " by " + .. minetest.colorize(k_teamcolor, killer) + .. assist_message + .. "." + minetest.chat_send_player(player, death_message) else - ctf_kill_list.add("", player, get_suicide_image(reason)) - send_death_message(player, player, get_suicide_image(reason)) + local death_message = "You were killed by " + .. minetest.colorize(k_teamcolor, killer) + .. assist_message + .. "." + minetest.chat_send_player(player, death_message) end - - killer, weapon_image = ctf_combat_mode.get_last_hitter(player) - comment = " (Suicide)" end - recent_rankings.add(player, {deaths = 1}, true) + if player == killer and #hitters == 0 then + local suicide_message = weapon_message or "suicided" + local death_message = "You " .. suicide_message .. assist_message .. "." + minetest.chat_send_player(player, death_message) + end end + end - if killer then - local killscore = calculate_killscore(player) - local total_enemy_reward = 0 - - local rewards = {kills = 1, score = killscore} - local bounty = ctf_modebase.bounties.claim(player, killer) + local function tp_player_near_flag(player) + local tname = ctf_teams.get(player) + if not tname then + return + end - if bounty then - for name, amount in pairs(bounty) do - rewards[name] = (rewards[name] or 0) + amount - end - end + local rotation_y + local pos - recent_rankings.add(killer, rewards) - total_enemy_reward = total_enemy_reward + rewards.score - - if ctf_teams.get(killer) then - ctf_kill_list.add(killer, player, weapon_image, comment) - hud_events.new(player, { - quick = false, - text = killer .. " " .. S("killed you for") .. " " .. rewards.score .." " .. S("points!"), - color = "warning", - }) - send_death_message(player, killer, weapon_image) - end + if ctf_map.current_map.teams[tname] then + pos = vector.offset( + ctf_map.current_map.teams[tname].flag_pos, + math.random(-1, 1), + 0.5, + math.random(-1, 1) + ) + rotation_y = vector.dir_to_rotation( + vector.direction( + pos, + ctf_map.current_map.teams[tname].look_pos + or ctf_map.current_map.flag_center + ) + ).y + else + pos = vector.add( + ctf_map.current_map.pos1, + vector.divide(ctf_map.current_map.size, 2) + ) + rotation_y = player:get_look_horizontal() + end - -- share kill score with other hitters - local hitters = ctf_combat_mode.get_other_hitters(player, killer) - for _, pname in ipairs(hitters) do - recent_rankings.add(pname, {kill_assists = 1, score = math.ceil(killscore / #hitters)}) - total_enemy_reward = total_enemy_reward + math.ceil(killscore / #hitters) - end + local function apply() + player:set_pos(pos) + player:set_look_vertical(0) + player:set_look_horizontal(rotation_y) + end - -- share kill score with healers - local healers = ctf_combat_mode.get_healers(killer) - for _, pname in ipairs(healers) do - recent_rankings.add(pname, {score = math.ceil(killscore / #healers)}) - total_enemy_reward = total_enemy_reward + math.ceil(killscore / #healers) + apply() + minetest.after(0.1, function() -- TODO remove after respawn bug will be fixed + if player:is_player() then + apply() end + end) - recent_rankings.add(player, {reward_given_to_enemy = total_enemy_reward}, true) + return true + end - if ctf_combat_mode.is_only_hitter(killer, player) then - ctf_combat_mode.set_kill_time(killer, 5) + local function celebrate_team(teamname) + for _, player in ipairs(minetest.get_connected_players()) do + local pname = player:get_player_name() + local pteam = ctf_teams.get(pname) + local volume = ( + tonumber(ctf_settings.get(player, "flag_sound_volume")) or 10.0 + ) / 10 + + if volume > 0 then + if pteam == teamname then + minetest.sound_play("ctf_modebase_trumpet_positive", { + to_player = pname, + gain = volume, + pitch = 1.0, + }, true) + else + minetest.sound_play("ctf_modebase_trumpet_negative", { + to_player = pname, + gain = volume, + pitch = 1.0, + }, true) + end end end end - ctf_combat_mode.end_combat(player) -end - -local function can_punchplayer(player, hitter) - if not ctf_modebase.match_started then - return false, S("The match hasn't started yet!") + local function drop_flag(teamname) + for _, player in ipairs(minetest.get_connected_players()) do + local pname = player:get_player_name() + local pteam = ctf_teams.get(pname) + local drop_volume = ( + tonumber(ctf_settings.get(player, "flag_sound_volume")) or 10.0 + ) / 10 + + if pteam and drop_volume > 0 then + if pteam == teamname then + minetest.sound_play("ctf_modebase_drop_flag_negative", { + to_player = pname, + gain = math.max(0.1, drop_volume - 0.5), + pitch = 1.0, + }, true) + else + minetest.sound_play("ctf_modebase_drop_flag_positive", { + to_player = pname, + gain = math.max(0.1, drop_volume - 0.5), + pitch = 1.0, + }, true) + end + end + end end - local pname, hname = player:get_player_name(), hitter:get_player_name() - local pteam, hteam = ctf_teams.get(player), ctf_teams.get(hitter) - - if not ctf_modebase.remove_respawn_immunity(hitter) then - return false, S("You can't attack while immune") - end + local function end_combat_mode(player, reason, killer, weapon_image) + local comment = nil - if not pteam then - return false, pname .. " " .. S("is not in a team!") - end + if ctf_teams.get(player) then + if reason == "combatlog" then + killer, weapon_image = ctf_combat_mode.get_last_hitter(player) + if killer then + comment = " (Combat Log)" + recent_rankings.add(player, { deaths = 1 }, true) + end + else + if reason ~= "punch" or killer == player then + if reason == "punch" then + ctf_kill_list.add(player, player, weapon_image) + send_death_message(player, killer, weapon_image) + else + ctf_kill_list.add("", player, get_suicide_image(reason)) + send_death_message(player, player, get_suicide_image(reason)) + end - if not hteam then - return false, S("You are not in a team!") - end + killer, weapon_image = ctf_combat_mode.get_last_hitter(player) + comment = " (Suicide)" + end + recent_rankings.add(player, { deaths = 1 }, true) + end - if pteam == hteam and pname ~= hname then - return false, pname .. " " .. S("is on your team!") - end + if killer then + local killscore = calculate_killscore(player) + local total_enemy_reward = 0 - return true -end + local rewards = { kills = 1, score = killscore } + local bounty = ctf_modebase.bounties.claim(player, killer) -local item_levels = { - "wood", - "stone", - "bronze", - "steel", - "mese", - "diamond", -} - -local delete_queue = {} -local team_switch_after_capture = false - -return { - tp_player_near_flag = tp_player_near_flag, - - on_new_match = function() - team_list = {} - for tname in pairs(ctf_map.current_map.teams) do - table.insert(team_list, tname) - end - teams_left = #team_list - many_teams = #team_list > 2 + if bounty then + for name, amount in pairs(bounty) do + rewards[name] = (rewards[name] or 0) + amount + end + end - local map_treasures = table.copy(ctf_modebase:get_current_mode().treasures or {}) + recent_rankings.add(killer, rewards) + total_enemy_reward = total_enemy_reward + rewards.score + + if ctf_teams.get(killer) then + ctf_kill_list.add(killer, player, weapon_image, comment) + hud_events.new(player, { + quick = false, + text = killer + .. " " + .. S("killed you for") + .. " " + .. rewards.score + .. " " + .. S("points!"), + color = "warning", + }) + send_death_message(player, killer, weapon_image) + end - for k, v in pairs(ctf_map.treasure.treasure_from_string(ctf_map.current_map.treasures)) do - map_treasures[k] = v - end + -- share kill score with other hitters + local hitters = ctf_combat_mode.get_other_hitters(player, killer) + for _, pname in ipairs(hitters) do + recent_rankings.add( + pname, + { kill_assists = 1, score = math.ceil(killscore / #hitters) } + ) + total_enemy_reward = total_enemy_reward + + math.ceil(killscore / #hitters) + end - if #delete_queue > 0 and delete_queue._map ~= ctf_map.current_map.dirname then - local p1, p2 = unpack(delete_queue) + -- share kill score with healers + local healers = ctf_combat_mode.get_healers(killer) + for _, pname in ipairs(healers) do + recent_rankings.add( + pname, + { score = math.ceil(killscore / #healers) } + ) + total_enemy_reward = total_enemy_reward + + math.ceil(killscore / #healers) + end - for _, object_drop in pairs(minetest.get_objects_in_area(p1, p2)) do - if not object_drop:is_player() then - local drop = object_drop:get_luaentity() + recent_rankings.add( + player, + { reward_given_to_enemy = total_enemy_reward }, + true + ) - if drop and drop.name == "__builtin:item" then - object_drop:remove() - end + if ctf_combat_mode.is_only_hitter(killer, player) then + ctf_combat_mode.set_kill_time(killer, 5) end end + end - minetest.delete_area(p1, p2) + ctf_combat_mode.end_combat(player) + end - delete_queue = {} + local function can_punchplayer(player, hitter) + if not ctf_modebase.match_started then + return false, S("The match hasn't started yet!") end - ctf_map.prepare_map_nodes( - ctf_map.current_map, - function(inv) ctf_map.treasure.treasurefy_node(inv, map_treasures) end, - ctf_modebase:get_current_mode().team_chest_items or {}, - ctf_modebase:get_current_mode().blacklisted_nodes or {} - ) - - if loading_screen_time then - local total_time = (minetest.get_us_time() - loading_screen_time) / 1e6 + local pname, hname = player:get_player_name(), hitter:get_player_name() + local pteam, hteam = ctf_teams.get(player), ctf_teams.get(hitter) - minetest.after(math.max(0, LOADING_SCREEN_TARGET_TIME - total_time), function() - mapload_huds:clear_all() - set_playertags_state(PLAYERTAGS_ON) + if not ctf_modebase.remove_respawn_immunity(hitter) then + return false, S("You can't attack while immune") + end - ctf_modebase.build_timer.start() - end) + if not pteam then + return false, pname .. " " .. S("is not in a team!") end - end, - on_match_end = function() - recent_rankings.on_match_end() - - if ctf_map.current_map then - minetest.log("action", - "matchend: Match ended for map "..ctf_map.current_map.name.. - " in mode "..(ctf_modebase.current_mode or "").. - ". Duration: "..ctf_map.get_duration() - ) - -- Queue deletion for after the players have left - delete_queue = {ctf_map.current_map.pos1, ctf_map.current_map.pos2, _map = ctf_map.current_map.dirname} + + if not hteam then + return false, S("You are not in a team!") end - end, - -- If you set this in a mode def it will replace the call to ctf_teams.allocate_teams() in match.lua - -- allocate_teams = function() - team_allocator = function(player) - player = PlayerName(player) - local team_scores = recent_rankings.teams() + if pteam == hteam and pname ~= hname then + return false, pname .. " " .. S("is on your team!") + end - local best_kd = nil - local worst_kd = nil - local best_players = nil - local worst_players = nil - local total_players = 0 + return true + end - for _, team in ipairs(team_list) do - local players_count = ctf_teams.online_players[team].count - local players = ctf_teams.online_players[team].players + local item_levels = { + "wood", + "stone", + "bronze", + "steel", + "mese", + "diamond", + } - local bk = 0 - local bd = 1 + local delete_queue = {} + local team_switch_after_capture = false - for name in pairs(players) do - local rank = rankings:get(name) + return { + tp_player_near_flag = tp_player_near_flag, - if rank then - if bk <= (rank.kills or 0) then - bk = rank.kills or 0 - bd = rank.deaths or 0 - end - end + on_new_match = function() + team_list = {} + for tname in pairs(ctf_map.current_map.teams) do + table.insert(team_list, tname) + end + teams_left = #team_list + many_teams = #team_list > 2 + + local map_treasures = + table.copy(ctf_modebase:get_current_mode().treasures or {}) + + for k, v in + pairs( + ctf_map.treasure.treasure_from_string(ctf_map.current_map.treasures) + ) + do + map_treasures[k] = v end - total_players = total_players + players_count + if #delete_queue > 0 and delete_queue._map ~= ctf_map.current_map.dirname then + local p1, p2 = unpack(delete_queue) - local kd = bk / bd - local match_kd = 0 - local tk = 0 - if team_scores[team] then - if (team_scores[team].score or 0) >= 50 then - tk = team_scores[team].kills or 0 + for _, object_drop in pairs(minetest.get_objects_in_area(p1, p2)) do + if not object_drop:is_player() then + local drop = object_drop:get_luaentity() - kd = math.max(kd, (team_scores[team].kills or bk) / (team_scores[team].deaths or bd)) + if drop and drop.name == "__builtin:item" then + object_drop:remove() + end + end end - match_kd = (team_scores[team].kills or 0) / (team_scores[team].deaths or 1) - end + minetest.delete_area(p1, p2) - if not best_kd or match_kd > best_kd.a then - best_kd = {s = kd, a = match_kd, t = team, kills = tk} + delete_queue = {} end - if not worst_kd or match_kd < worst_kd.a then - worst_kd = {s = kd, a = match_kd, t = team, kills = tk} - end + ctf_map.prepare_map_nodes( + ctf_map.current_map, + function(inv) + ctf_map.treasure.treasurefy_node(inv, map_treasures) + end, + ctf_modebase:get_current_mode().team_chest_items or {}, + ctf_modebase:get_current_mode().blacklisted_nodes or {} + ) - if not best_players or players_count > best_players.s then - best_players = {s = players_count, t = team} - end + if loading_screen_time then + local total_time = (minetest.get_us_time() - loading_screen_time) / 1e6 + + minetest.after( + math.max(0, LOADING_SCREEN_TARGET_TIME - total_time), + function() + mapload_huds:clear_all() + set_playertags_state(PLAYERTAGS_ON) - if not worst_players or players_count < worst_players.s then - worst_players = {s = players_count, t = team} + ctf_modebase.build_timer.start() + end + ) end - end + end, + on_match_end = function() + recent_rankings.on_match_end() + + if ctf_map.current_map then + minetest.log( + "action", + "matchend: Match ended for map " + .. ctf_map.current_map.name + .. " in mode " + .. (ctf_modebase.current_mode or "") + .. ". Duration: " + .. ctf_map.get_duration() + ) + -- Queue deletion for after the players have left + delete_queue = { + ctf_map.current_map.pos1, + ctf_map.current_map.pos2, + _map = ctf_map.current_map.dirname, + } + end + end, + -- If you set this in a mode def it will replace the call to ctf_teams.allocate_teams() in match.lua + -- allocate_teams = function() + team_allocator = function(player) + player = PlayerName(player) + + local team_scores = recent_rankings.teams() + + local best_kd = nil + local worst_kd = nil + local best_players = nil + local worst_players = nil + local total_players = 0 + + for _, team in ipairs(team_list) do + local players_count = ctf_teams.online_players[team].count + local players = ctf_teams.online_players[team].players + + local bk = 0 + local bd = 1 + + for name in pairs(players) do + local rank = rankings:get(name) + + if rank then + if bk <= (rank.kills or 0) then + bk = rank.kills or 0 + bd = rank.deaths or 0 + end + end + end - if worst_players.s == 0 then - return worst_players.t - end + total_players = total_players + players_count - local kd_diff = best_kd.s - worst_kd.s - local actual_kd_diff = best_kd.a - worst_kd.a - local players_diff = best_players.s - worst_players.s + local kd = bk / bd + local match_kd = 0 + local tk = 0 + if team_scores[team] then + if (team_scores[team].score or 0) >= 50 then + tk = team_scores[team].kills or 0 - local rem_team = ctf_teams.get(player) - local player_rankings = recent_rankings.get(player) --[pteam.."_score"] + kd = math.max( + kd, + (team_scores[team].kills or bk) + / (team_scores[team].deaths or bd) + ) + end - if not rem_team or - math.max(player_rankings[rem_team.."_kills"] or 0, player_rankings[rem_team.."_deaths"] or 0) <= 6 then - player_rankings = rankings:get(player) or {} - else - player_rankings.kills = player_rankings[rem_team.."_kills"] or 0 - player_rankings.deaths = player_rankings[rem_team.."_deaths"] or 1 - end + match_kd = (team_scores[team].kills or 0) + / (team_scores[team].deaths or 1) + end - local one_third = math.ceil(0.34 * total_players) + if not best_kd or match_kd > best_kd.a then + best_kd = { s = kd, a = match_kd, t = team, kills = tk } + end - -- Allocate player to remembered team unless teams are imbalanced - if rem_team and not ctf_modebase.flag_captured[rem_team] and - (worst_kd.kills <= total_players or actual_kd_diff <= 0.8) and players_diff <= one_third then - return rem_team - end + if not worst_kd or match_kd < worst_kd.a then + worst_kd = { s = kd, a = match_kd, t = team, kills = tk } + end + + if not best_players or players_count > best_players.s then + best_players = { s = players_count, t = team } + end - local pkd = (player_rankings.kills or 0) / (player_rankings.deaths or 1) + if not worst_players or players_count < worst_players.s then + worst_players = { s = players_count, t = team } + end + end - local one_fourth = math.ceil(0.25 * total_players) - local avg = (kd_diff + actual_kd_diff) / 2 - local pcount_diff_limit = ( - (players_diff <= math.min(one_fourth, 1)) or - (pkd >= 1.8 and players_diff <= math.min(one_third, 2)) - ) - if best_kd.kills + worst_kd.kills >= 30 then - avg = actual_kd_diff - end + if worst_players.s == 0 then + return worst_players.t + end - local result = pcount_diff_limit and ((best_kd.kills + worst_kd.kills >= 30 and best_kd.t == best_players.t) or - (pkd >= math.min(1, kd_diff/2) and avg >= 0.4)) + local kd_diff = best_kd.s - worst_kd.s + local actual_kd_diff = best_kd.a - worst_kd.a + local players_diff = best_players.s - worst_players.s - if players_diff == 0 or result then - return worst_kd.t - else - return worst_players.t - end - end, - can_take_flag = function(player, teamname) - if not ctf_modebase.match_started then - tp_player_near_flag(player) + local rem_team = ctf_teams.get(player) + local player_rankings = recent_rankings.get(player) --[pteam.."_score"] - return S("You can't take the enemy flag during build time!") - end - end, - on_flag_take = function(player, teamname) - local pname = player:get_player_name() - local pteam = ctf_teams.get(player) - local tcolor = ctf_teams.team[pteam].color + if + not rem_team + or math.max( + player_rankings[rem_team .. "_kills"] or 0, + player_rankings[rem_team .. "_deaths"] or 0 + ) + <= 6 + then + player_rankings = rankings:get(player) or {} + else + player_rankings.kills = player_rankings[rem_team .. "_kills"] or 0 + player_rankings.deaths = player_rankings[rem_team .. "_deaths"] or 1 + end - ctf_modebase.remove_immunity(player) - playertag.set(player, playertag.TYPE_BUILTIN, tcolor) + local one_third = math.ceil(0.34 * total_players) - local text = " has taken the flag" - if many_teams then - text = " has taken " .. HumanReadable(teamname) .. "'s flag" - end + -- Allocate player to remembered team unless teams are imbalanced + if + rem_team + and not ctf_modebase.flag_captured[rem_team] + and (worst_kd.kills <= total_players or actual_kd_diff <= 0.8) + and players_diff <= one_third + then + return rem_team + end - minetest.chat_send_all( - minetest.colorize(tcolor, pname) .. - minetest.colorize(FLAG_MESSAGE_COLOR, text) - ) - ctf_modebase.announce(string.format("Player %s (team %s)%s", pname, pteam, text)) + local pkd = (player_rankings.kills or 0) / (player_rankings.deaths or 1) - celebrate_team(ctf_teams.get(pname)) + local one_fourth = math.ceil(0.25 * total_players) + local avg = (kd_diff + actual_kd_diff) / 2 + local pcount_diff_limit = ( + (players_diff <= math.min(one_fourth, 1)) + or (pkd >= 1.8 and players_diff <= math.min(one_third, 2)) + ) + if best_kd.kills + worst_kd.kills >= 30 then + avg = actual_kd_diff + end - recent_rankings.add(pname, { - score = ctf_teams.online_players[teamname].count * 3 * #ctf_modebase.taken_flags[pname], - flag_attempts = 1 - }) + local result = pcount_diff_limit + and ( + (best_kd.kills + worst_kd.kills >= 30 and best_kd.t == best_players.t) + or (pkd >= math.min(1, kd_diff / 2) and avg >= 0.4) + ) - ctf_modebase.flag_huds.track_capturer(pname, FLAG_CAPTURE_TIMER) - end, - on_flag_drop = function(player, teamnames, pteam) - local pname = player:get_player_name() - local tcolor = pteam and ctf_teams.team[pteam].color or "#FFF" + if players_diff == 0 or result then + return worst_kd.t + else + return worst_players.t + end + end, + can_take_flag = function(player, teamname) + if not ctf_modebase.match_started then + tp_player_near_flag(player) - local text = " has dropped the flag" - if many_teams then - text = " has dropped the flag of team(s) " .. HumanReadable(teamnames) - end + return S("You can't take the enemy flag during build time!") + end + end, + on_flag_take = function(player, teamname) + local pname = player:get_player_name() + local pteam = ctf_teams.get(player) + local tcolor = ctf_teams.team[pteam].color - minetest.chat_send_all( - minetest.colorize(tcolor, pname) .. - minetest.colorize(FLAG_MESSAGE_COLOR, text) - ) - ctf_modebase.announce(string.format("Player %s (team %s)%s", pname, pteam, text)) + ctf_modebase.remove_immunity(player) + playertag.set(player, playertag.TYPE_BUILTIN, tcolor) - ctf_modebase.flag_huds.untrack_capturer(pname) + local text = " has taken the flag" + if many_teams then + text = " has taken " .. HumanReadable(teamname) .. "'s flag" + end - playertag.set(player, playertag.TYPE_ENTITY) + minetest.chat_send_all( + minetest.colorize(tcolor, pname) + .. minetest.colorize(FLAG_MESSAGE_COLOR, text) + ) + ctf_modebase.announce( + string.format("Player %s (team %s)%s", pname, pteam, text) + ) - if player.set_observers then - ctf_modebase.update_playertags() - end + celebrate_team(ctf_teams.get(pname)) - drop_flag(pteam) - end, - on_flag_capture = function(player, teamnames) - local pname = player:get_player_name() - local pteam = ctf_teams.get(pname) - local tcolor = ctf_teams.team[pteam].color + recent_rankings.add(pname, { + score = ctf_teams.online_players[teamname].count + * 3 + * #ctf_modebase.taken_flags[pname], + flag_attempts = 1, + }) - playertag.set(player, playertag.TYPE_ENTITY) + ctf_modebase.flag_huds.track_capturer(pname, FLAG_CAPTURE_TIMER) + end, + on_flag_drop = function(player, teamnames, pteam) + local pname = player:get_player_name() + local tcolor = pteam and ctf_teams.team[pteam].color or "#FFF" - if player.set_observers then - ctf_modebase.update_playertags() - end + local text = " has dropped the flag" + if many_teams then + text = " has dropped the flag of team(s) " .. HumanReadable(teamnames) + end - celebrate_team(pteam) + minetest.chat_send_all( + minetest.colorize(tcolor, pname) + .. minetest.colorize(FLAG_MESSAGE_COLOR, text) + ) + ctf_modebase.announce( + string.format("Player %s (team %s)%s", pname, pteam, text) + ) - ctf_modebase.flag_huds.untrack_capturer(pname) + ctf_modebase.flag_huds.untrack_capturer(pname) - local team_scores = recent_rankings.teams() - local player_scores = recent_rankings.players() - local capture_reward = 0 - for _, lost_team in ipairs(teamnames) do - local team_score = 0 + playertag.set(player, playertag.TYPE_ENTITY) - for n in pairs(ctf_teams.online_players[lost_team].players) do - team_score = team_score + (player_scores[n].score or 0) + if player.set_observers then + ctf_modebase.update_playertags() end - local score = team_score / math.max(1, ctf_teams.online_players[lost_team].count) - score = math.max( - 8 * ((player_scores[pname].flag_attempts or 0) + math.min( - (os.time() - ctf_map.start_time) / 60, - 10 - )), - score * (2.4 + ctf_teams.online_players[lost_team].count/30) - ) + drop_flag(pteam) + end, + on_flag_capture = function(player, teamnames) + local pname = player:get_player_name() + local pteam = ctf_teams.get(pname) + local tcolor = ctf_teams.team[pteam].color - score = math.max(score, #ctf_teams.get_connected_players() * 1.4) - - capture_reward = capture_reward + math.min(score, 800) - - minetest.log("action", string.format( - "[CAPDEBUG] div: %.1f {team_score = %d, capture_score = %d, connected_players = %d, lost_team_count = %d, ".. - "player_attempts = %d, time = %d, winteam_score = %d, \"%s\"},", - team_score / score, - team_score, - score, - #ctf_teams.get_connected_players(), - ctf_teams.online_players[lost_team].count, - player_scores[pname].flag_attempts or 0, - os.time() - ctf_map.start_time, - team_scores[pteam].score or 0, - many_teams and "many teams" or "2 teams" - )) - end + playertag.set(player, playertag.TYPE_ENTITY) - local text = string.format(" has captured the flag and got %d points", capture_reward) - if many_teams then - text = string.format( - " has captured the flag of team(s) %s and got %d points", - HumanReadable(teamnames), - capture_reward - ) - end + if player.set_observers then + ctf_modebase.update_playertags() + end - minetest.chat_send_all( - minetest.colorize(tcolor, pname) .. minetest.colorize(FLAG_MESSAGE_COLOR, text) - ) + celebrate_team(pteam) - ctf_modebase.announce(string.format("Player %s (team %s)%s", pname, pteam, text)) + ctf_modebase.flag_huds.untrack_capturer(pname) - local team_score = team_scores[pteam].score - local healers = ctf_combat_mode.get_healers(pname) - for teammate in pairs(ctf_teams.online_players[pteam].players) do - if teammate ~= pname then - local teammate_value = (recent_rankings.get(teammate)[pteam.."_score"] or 0) / (team_score or 1) + local team_scores = recent_rankings.teams() + local player_scores = recent_rankings.players() + local capture_reward = 0 + for _, lost_team in ipairs(teamnames) do + local team_score = 0 - if table.indexof(healers, teammate) ~= -1 then - teammate_value = teammate_value + ((#ctf_teams.get_connected_players() / 10) / #healers) + for n in pairs(ctf_teams.online_players[lost_team].players) do + team_score = team_score + (player_scores[n].score or 0) end - local victory_bonus = math.max(5, math.min(capture_reward / 2, capture_reward * teammate_value)) - recent_rankings.add(teammate, {score = victory_bonus}, true) + local score = team_score + / math.max(1, ctf_teams.online_players[lost_team].count) + score = math.max( + 8 + * ( + (player_scores[pname].flag_attempts or 0) + + math.min((os.time() - ctf_map.start_time) / 60, 10) + ), + score * (2.4 + ctf_teams.online_players[lost_team].count / 30) + ) + + score = math.max(score, #ctf_teams.get_connected_players() * 1.4) + + capture_reward = capture_reward + math.min(score, 800) + + minetest.log( + "action", + string.format( + "[CAPDEBUG] div: %.1f {team_score = %d, capture_score = %d, connected_players = %d, lost_team_count = %d, " + .. 'player_attempts = %d, time = %d, winteam_score = %d, "%s"},', + team_score / score, + team_score, + score, + #ctf_teams.get_connected_players(), + ctf_teams.online_players[lost_team].count, + player_scores[pname].flag_attempts or 0, + os.time() - ctf_map.start_time, + team_scores[pteam].score or 0, + many_teams and "many teams" or "2 teams" + ) + ) end - end - - recent_rankings.add(pname, {score = capture_reward, flag_captures = #teamnames}) - teams_left = teams_left - #teamnames - - if teams_left <= 1 then - local capture_text = "Player %s captured and got %d points" + local text = + string.format(" has captured the flag and got %d points", capture_reward) if many_teams then - capture_text = "Player %s captured the last flag and got %d points" + text = string.format( + " has captured the flag of team(s) %s and got %d points", + HumanReadable(teamnames), + capture_reward + ) end - ctf_modebase.summary.set_winner(string.format(capture_text, minetest.colorize(tcolor, pname), capture_reward)) - - local win_text = HumanReadable(pteam) .. " Team Wins!" - - local match_rankings, special_rankings, rank_values, formdef = ctf_modebase.summary.get() - formdef.title = win_text - - for _, p in ipairs(minetest.get_connected_players()) do - ctf_modebase.summary.show_gui(p:get_player_name(), match_rankings, special_rankings, rank_values, formdef) - end + minetest.chat_send_all( + minetest.colorize(tcolor, pname) + .. minetest.colorize(FLAG_MESSAGE_COLOR, text) + ) - ctf_modebase.announce(win_text) + ctf_modebase.announce( + string.format("Player %s (team %s)%s", pname, pteam, text) + ) - ctf_modebase.start_new_match(5) - else - for _, lost_team in ipairs(teamnames) do - table.remove(team_list, table.indexof(team_list, lost_team)) + local team_score = team_scores[pteam].score + local healers = ctf_combat_mode.get_healers(pname) + for teammate in pairs(ctf_teams.online_players[pteam].players) do + if teammate ~= pname then + local teammate_value = ( + recent_rankings.get(teammate)[pteam .. "_score"] or 0 + ) / (team_score or 1) + + if table.indexof(healers, teammate) ~= -1 then + teammate_value = teammate_value + + ((#ctf_teams.get_connected_players() / 10) / #healers) + end - for lost_player in pairs(ctf_teams.online_players[lost_team].players) do - team_switch_after_capture = true - ctf_teams.allocate_player(lost_player) - team_switch_after_capture = false + local victory_bonus = math.max( + 5, + math.min(capture_reward / 2, capture_reward * teammate_value) + ) + recent_rankings.add(teammate, { score = victory_bonus }, true) end end - end - end, - on_allocplayer = function(player, new_team) - player:set_hp(player:get_properties().hp_max) - if not team_switch_after_capture then - ctf_modebase.update_wear.cancel_player_updates(player) + recent_rankings.add( + pname, + { score = capture_reward, flag_captures = #teamnames } + ) - ctf_modebase.player.remove_bound_items(player) - ctf_modebase.player.give_initial_stuff(player) - end + teams_left = teams_left - #teamnames - local tcolor = ctf_teams.team[new_team].color - player:hud_set_hotbar_image("gui_hotbar.png^[colorize:" .. tcolor .. ":128") - player:hud_set_hotbar_selected_image("gui_hotbar_selected.png^[multiply:" .. tcolor) + if teams_left <= 1 then + local capture_text = "Player %s captured and got %d points" + if many_teams then + capture_text = "Player %s captured the last flag and got %d points" + end + -- there might be some unclaimed player bounties, here we return + -- the points to their contributors + local current_mode = ctf_modebase:get_current_mode() + for _, bounties2 in pairs(ctf_modebase.contributed_bounties) do + for bounty_donator, bounty_amount in pairs(bounties2["contributors"]) do + current_mode.recent_rankings.add( + bounty_donator, + { score = bounty_amount }, + true + ) + end + end - player_api.set_texture(player, 1, ctf_cosmetics.get_skin(player)) + ctf_modebase.summary.set_winner( + string.format( + capture_text, + minetest.colorize(tcolor, pname), + capture_reward + ) + ) + + local win_text = HumanReadable(pteam) .. " Team Wins!" + + local match_rankings, special_rankings, rank_values, formdef = + ctf_modebase.summary.get() + formdef.title = win_text + + for _, p in ipairs(minetest.get_connected_players()) do + ctf_modebase.summary.show_gui( + p:get_player_name(), + match_rankings, + special_rankings, + rank_values, + formdef + ) + end - recent_rankings.set_team(player, new_team) + ctf_modebase.announce(win_text) - playertag.set(player, playertag.TYPE_ENTITY) + ctf_modebase.start_new_match(5) + else + for _, lost_team in ipairs(teamnames) do + table.remove(team_list, table.indexof(team_list, lost_team)) - if player.set_observers then - ctf_modebase.update_playertags() - end + for lost_player in pairs(ctf_teams.online_players[lost_team].players) do + team_switch_after_capture = true + ctf_teams.allocate_player(lost_player) + team_switch_after_capture = false + end + end + ctf_modebase.remove_bounties_on_teammates() + end + end, + on_allocplayer = function(player, new_team) + player:set_hp(player:get_properties().hp_max) - tp_player_near_flag(player) - end, - on_leaveplayer = function(player) - if not ctf_modebase.match_started then - ctf_combat_mode.end_combat(player) - return - end + if not team_switch_after_capture then + ctf_modebase.update_wear.cancel_player_updates(player) - local pname = player:get_player_name() + ctf_modebase.player.remove_bound_items(player) + ctf_modebase.player.give_initial_stuff(player) + end - -- should be no_hud to avoid a race - end_combat_mode(pname, "combatlog") + local tcolor = ctf_teams.team[new_team].color + player:hud_set_hotbar_image("gui_hotbar.png^[colorize:" .. tcolor .. ":128") + player:hud_set_hotbar_selected_image( + "gui_hotbar_selected.png^[multiply:" .. tcolor + ) - recent_rankings.on_leaveplayer(pname) - end, - on_dieplayer = function(player, reason) - if not ctf_modebase.match_started then return end + player_api.set_texture(player, 1, ctf_cosmetics.get_skin(player)) - -- punch is handled in on_punchplayer - if reason.type ~= "punch" then - end_combat_mode(player:get_player_name(), reason) - end + recent_rankings.set_team(player, new_team) - if ctf_teams.get(player) then - ctf_modebase.prepare_respawn_delay(player) - end - end, - on_respawnplayer = function(player) - tp_player_near_flag(player) - end, - get_chest_access = function(pname) - local rank = rankings:get(pname) - local player = minetest.get_player_by_name(pname) - local pro_chest = player and player:get_meta():get_int("ctf_rankings:pro_chest:".. - (ctf_modebase.current_mode or "")) >= 1 - local deny_pro = "You need to have more than 1.4 kills per death, ".. - "5 captures, and at least 8,000 score to access the pro section." - if rank then - local captures_needed = math.max(0, 5 - (rank.flag_captures or 0)) - local score_needed = math.floor(math.max(0, 8000 - (rank.score or 0))) - local current_kd = math.floor((rank.kills or 0) / (rank.deaths or 1) * 10) - current_kd = current_kd / 10 - deny_pro = deny_pro .. " You still need " .. captures_needed - .. " captures, " .. score_needed .. - " score, and your kills per death is " .. - current_kd .. "." - end + playertag.set(player, playertag.TYPE_ENTITY) - -- Remember to update /make_pro in ranking_commands.lua if you change anything here - if pro_chest or rank then - if - pro_chest - or - (rank.score or 0) >= 8000 and - (rank.kills or 0) / (rank.deaths or 1) >= 1.4 and - (rank.flag_captures or 0) >= 5 - then - return true, true + if player.set_observers then + ctf_modebase.update_playertags() end - if (rank.score or 0) >= 10 then - return true, deny_pro + tp_player_near_flag(player) + end, + on_leaveplayer = function(player) + if not ctf_modebase.match_started then + ctf_combat_mode.end_combat(player) + return end - end - return "You need at least 10 score to access this chest", deny_pro - end, - can_punchplayer = can_punchplayer, - on_punchplayer = function(player, hitter, damage, _, tool_capabilities) - if not hitter:is_player() or player:get_hp() <= 0 then return false end + local pname = player:get_player_name() - local allowed, message = can_punchplayer(player, hitter) + -- should be no_hud to avoid a race + end_combat_mode(pname, "combatlog") - if not allowed then - return false, message - end + recent_rankings.on_leaveplayer(pname) + end, + on_dieplayer = function(player, reason) + if not ctf_modebase.match_started then + return + end - local weapon_image = get_weapon_image(hitter, tool_capabilities) + -- punch is handled in on_punchplayer + if reason.type ~= "punch" then + end_combat_mode(player:get_player_name(), reason) + end - if player:get_hp() <= damage then - end_combat_mode(player:get_player_name(), "punch", hitter:get_player_name(), weapon_image) - elseif player:get_player_name() ~= hitter:get_player_name() then - ctf_combat_mode.add_hitter(player, hitter, weapon_image, 15) - end + if ctf_teams.get(player) then + ctf_modebase.prepare_respawn_delay(player) + end + end, + on_respawnplayer = function(player) + tp_player_near_flag(player) + end, + get_chest_access = function(pname) + local rank = rankings:get(pname) + local player = minetest.get_player_by_name(pname) + local pro_chest = player + and player:get_meta():get_int( + "ctf_rankings:pro_chest:" .. (ctf_modebase.current_mode or "") + ) + >= 1 + local deny_pro = "You need to have more than 1.4 kills per death, " + .. "5 captures, and at least 8,000 score to access the pro section." + if rank then + local captures_needed = math.max(0, 5 - (rank.flag_captures or 0)) + local score_needed = math.floor(math.max(0, 8000 - (rank.score or 0))) + local current_kd = math.floor((rank.kills or 0) / (rank.deaths or 1) * 10) + current_kd = current_kd / 10 + deny_pro = deny_pro + .. " You still need " + .. captures_needed + .. " captures, " + .. score_needed + .. " score, and your kills per death is " + .. current_kd + .. "." + end - return damage - end, - on_healplayer = function(player, patient, amount) - if not ctf_modebase.match_started then - return S("The match hasn't started yet!") - end + -- Remember to update /make_pro in ranking_commands.lua if you change anything here + if pro_chest or rank then + if + pro_chest + or (rank.score or 0) >= 8000 + and (rank.kills or 0) / (rank.deaths or 1) >= 1.4 + and (rank.flag_captures or 0) >= 5 + then + return true, true + end - local score = nil + if (rank.score or 0) >= 10 then + return true, deny_pro + end + end - if ctf_combat_mode.in_combat(patient) then - score = 1 - end + return "You need at least 10 score to access this chest", deny_pro + end, + can_punchplayer = can_punchplayer, + on_punchplayer = function(player, hitter, damage, _, tool_capabilities) + if not hitter:is_player() or player:get_hp() <= 0 then + return false + end - ctf_combat_mode.add_healer(patient, player, 60) - recent_rankings.add(player, {hp_healed = amount, score = score}, true) - end, - initial_stuff_item_levels = { - pick = function(item) - local match = item:get_name():match("default:pick_(%a+)") + local allowed, message = can_punchplayer(player, hitter) - if match then - return table.indexof(item_levels, match) + if not allowed then + return false, message end - end, - axe = function(item) - local match = item:get_name():match("default:axe_(%a+)") - if match then - return table.indexof(item_levels, match) + local weapon_image = get_weapon_image(hitter, tool_capabilities) + + if player:get_hp() <= damage then + end_combat_mode( + player:get_player_name(), + "punch", + hitter:get_player_name(), + weapon_image + ) + elseif player:get_player_name() ~= hitter:get_player_name() then + ctf_combat_mode.add_hitter(player, hitter, weapon_image, 15) end - end, - shovel = function(item) - local match = item:get_name():match("default:shovel_(%a+)") - if match then - return table.indexof(item_levels, match) - end + return damage end, - sword = function(item) - local mod, match = item:get_name():match("([^:]+):sword_(%a+)") + on_healplayer = function(player, patient, amount) + if not ctf_modebase.match_started then + return S("The match hasn't started yet!") + end + + local score = nil - if mod and (mod == "default" or mod == "ctf_melee") and match then - return table.indexof(item_levels, match) + if ctf_combat_mode.in_combat(patient) then + score = 1 end + + ctf_combat_mode.add_healer(patient, player, 60) + recent_rankings.add(player, { hp_healed = amount, score = score }, true) end, - } -} + initial_stuff_item_levels = { + pick = function(item) + local match = item:get_name():match("default:pick_(%a+)") + if match then + return table.indexof(item_levels, match) + end + end, + axe = function(item) + local match = item:get_name():match("default:axe_(%a+)") + + if match then + return table.indexof(item_levels, match) + end + end, + shovel = function(item) + local match = item:get_name():match("default:shovel_(%a+)") + + if match then + return table.indexof(item_levels, match) + end + end, + sword = function(item) + local mod, match = item:get_name():match("([^:]+):sword_(%a+)") + + if mod and (mod == "default" or mod == "ctf_melee") and match then + return table.indexof(item_levels, match) + end + end, + }, + } end ctf_settings.register("send_death_messages", { - type = "bool", - label = "Receive death messages.", - description = "When enabled, you will receive a death message whenever you die stating who killed you.", - default = "true", + type = "bool", + label = "Receive death messages.", + description = "When enabled, you will receive a death message whenever you die stating who killed you.", + default = "true", })