From 2234a59c61db500a24a722a9649d1b2aa3124406 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Fri, 13 Oct 2017 00:09:25 +0100 Subject: [PATCH] Rewrite scoring algorithm --- mods/ctf_stats/README.md | 14 ++++ mods/ctf_stats/gui.lua | 38 +++------ mods/ctf_stats/init.lua | 152 +++++++++++++++++++++++++++--------- mods/ctf_team_base/init.lua | 1 - 4 files changed, 139 insertions(+), 66 deletions(-) create mode 100644 mods/ctf_stats/README.md diff --git a/mods/ctf_stats/README.md b/mods/ctf_stats/README.md new file mode 100644 index 0000000..c51d254 --- /dev/null +++ b/mods/ctf_stats/README.md @@ -0,0 +1,14 @@ +## Score + +* +5 for picking up a flag. +* +20 for then capturing the flag. +* +X for killing someone, where X is: + * +5 for every kill they've made since last death in this match. + * `15 * kd` ratio, with variable cap based on player's score: + * capped at X * 10 for < X * 100 player score + (eg 10 for 100, 20 for 200, 40 for 400+). + * capped to 40. + * deaths > kills gives 0 score + * Limited to 0 ≤ X ≤ 100 + * If they don't have a good weapon: half given score. + A good weapon is a non-default one which is not a pistol, stone sword, or crossbow. diff --git a/mods/ctf_stats/gui.lua b/mods/ctf_stats/gui.lua index 9ba2ccc..669c2c0 100644 --- a/mods/ctf_stats/gui.lua +++ b/mods/ctf_stats/gui.lua @@ -15,36 +15,10 @@ function ctf_stats.get_formspec_match_summary(stats) return ret end -local function calc_scores(players) - for i = 1, #players do - local pstat = players[i] - pstat.kills = pstat.kills or 0 - pstat.deaths = pstat.deaths or 0 - pstat.captures = pstat.captures or 0 - pstat.attempts = pstat.attempts or 0 - local kd = pstat.kills - if pstat.deaths > 0 then - kd = kd / pstat.deaths - end - --[[local killbonus = 0 - if pstat.kills > 50 and pstat.kills < 200 then - killbonus = pstat.kills - 50 - elseif pstat.kills >= 200 then - killbonus = 150 - end]]-- - pstat.score = --killbonus + - 50 * pstat.captures + - 8 * pstat.attempts + - 6 * kd - end +function ctf_stats.get_formspec(title, players) table.sort(players, function(one, two) return one.score > two.score end) -end -ctf_stats.calc_scores = calc_scores - -function ctf_stats.get_formspec(title, players) - calc_scores(players) local ret = "size[12,6.5]" ret = ret .. "vertlabel[0,0;" .. title .. "]" @@ -81,7 +55,9 @@ function ctf_stats.get_formspec(title, players) end function ctf_stats.get_html(title, players) - calc_scores(players) + table.sort(players, function(one, two) + return one.score > two.score + end) local ret = "

" .. title .. "

" ret = ret .. "" .. @@ -148,7 +124,11 @@ minetest.register_chatcommand("rankings", { pstat.color = nil table.insert(players, pstat) end - calc_scores(players) + + table.sort(players, function(one, two) + return one.score > two.score + end) + local place = -1 local me = nil for i = 1, #players do diff --git a/mods/ctf_stats/init.lua b/mods/ctf_stats/init.lua index b8e49d4..22eb450 100644 --- a/mods/ctf_stats/init.lua +++ b/mods/ctf_stats/init.lua @@ -1,76 +1,95 @@ ctf_stats = {} +local storage = minetest.get_mod_storage() +local data_to_persist = { "matches", "players" } + function ctf_stats.load() - local file = io.open(minetest.get_worldpath().."/ctf_stats.txt", "r") + local file = io.open(minetest.get_worldpath() .. "/ctf_stats.txt", "r") if file then local table = minetest.deserialize(file:read("*all")) + file:close() if type(table) == "table" then + ctf.log("ctf_stats", "Migrating stats...") ctf_stats.matches = table.matches - ctf_stats.current = table.current ctf_stats.players = table.players - return + + for name, player_stats in pairs(ctf_stats.players) do + if not player_stats.score or player_stats.score < 0 then + player_stats.score = 0 + end + if player_stats.score > 300 then + player_stats.score = (player_stats.score - 300) / 30 + 300 + end + if player_stats.score > 800 then + player_stats.score = 800 + end + end + ctf.needs_save = true + end + os.remove(minetest.get_worldpath() .. "/ctf_stats.txt") + else + for _, key in pairs(data_to_persist) do + ctf_stats[key] = minetest.parse_json(storage:get_string(key)) end end - ctf_stats.matches = { - blue_wins = 0, - red_wins = 0, + ctf_stats.matches = ctf_stats.matches or { + wins = { + blue = 0, + red = 0, + }, skipped = 0 } - ctf_stats.current = { + ctf_stats.current = ctf_stats.current or { red = {}, blue = {} } - ctf_stats.players = {} + ctf_stats.players = ctf_stats.players or {} end ctf.register_on_save(function() - local file = io.open(minetest.get_worldpath().."/ctf_stats.txt", "w") - if file then - file:write(minetest.serialize({ - matches = ctf_stats.matches, - current = ctf_stats.current, - players = ctf_stats.players - })) - file:close() - else - ctf.error("io", "CTF file failed to save!") + for _, key in pairs(data_to_persist) do + storage:set_string(key, minetest.write_json(ctf_stats[key])) end return nil end) +-- Returns a tuple: `player_stats`, `match_player_stats` function ctf_stats.player(name) - local tplayer = ctf.player(name) - local player = ctf_stats.players[name] - if not player then - player = { + local player_stats = ctf_stats.players[name] + if not player_stats then + player_stats = { name = name, - red_wins = 0, - blue_wins = 0, + wins = { + red = 0, + blue = 0, + }, kills = 0, deaths = 0, captures = 0, attempts = 0, - score = -1, + score = 0, } - ctf_stats.players[name] = player + ctf_stats.players[name] = player_stats end - local mplayer = ctf_stats.current.red[name] or - ctf_stats.current.blue[name] + local match_player_stats = + ctf_stats.current.red[name] or ctf_stats.current.blue[name] - return player, mplayer + return player_stats, match_player_stats end ctf.register_on_join_team(function(name, tname) - ctf_stats.current[tname][name] = { + ctf_stats.current[tname][name] = ctf_stats.current[tname][name] or { kills = 0, + kills_since_death = 0, deaths = 0, attempts = 0, - captures = 0 + captures = 0, + score = 0, } end) @@ -82,15 +101,17 @@ end) ctf_flag.register_on_capture(function(name, flag) local main, match = ctf_stats.player(name) if main and match then - main.captures = main.captures + 1 + main.captures = main.captures + 1 + main.score = main.score + 20 match.captures = match.captures + 1 + match.score = match.score + 20 ctf.needs_save = true end end) ctf_match.register_on_winner(function(winner) ctf.needs_save = true - ctf_stats.matches[winner .. "_wins"] = ctf_stats.matches[winner .. "_wins"] + 1 + ctf_stats.matches.wins[winner] = ctf_stats.matches.wins[winner] + 1 end) ctf_match.register_on_new_match(function() @@ -110,8 +131,10 @@ end) ctf_flag.register_on_pick_up(function(name, flag) local main, match = ctf_stats.player(name) if main and match then - main.attempts = main.attempts + 1 + main.attempts = main.attempts + 1 + main.score = main.score + 5 match.attempts = match.attempts + 1 + match.score = match.score + 5 ctf.needs_save = true end end) @@ -120,17 +143,73 @@ ctf_flag.register_on_precapture(function(name, flag) local tplayer = ctf.player(name) local main, match = ctf_stats.player(name) if main then - main[tplayer.team .. "_wins"] = main[tplayer.team .. "_wins"] + 1 + main.wins[tplayer.team] = main.wins[tplayer.team] + 1 ctf.needs_save = true end return true end) +local good_weapons = { + "default:sword_steel", + "shooter:grenade", + "shooter:shotgun", + "shooter:rifle", + "shooter:machine_gun", +} +local function invHasGoodWeapons(inv) + for _, weapon in pairs(good_weapons) do + if inv:contains_item("main", weapon) then + return true + end + end + return false +end + +local function calculateKillReward(victim, killer) + local vmain, victim_match = ctf_stats.player(victim) + + -- +5 for every kill they've made since last death in this match. + local reward = victim_match.kills_since_death * 5 + ctf.log("ctf_stats", "Player " .. victim .. " has made " .. reward .. " kills since last death") + + -- 15 * kd ration, with variable based on player's score + local kdreward = 15 * vmain.kills / (vmain.deaths + 1) + local max = vmain.score / 10 + if kdreward > max then + kdreward = max + end + if kdreward > 40 then + kdreward = 40 + end + + -- Limited to 0 <= X <= 100 + if reward > 100 then + reward = 100 + elseif reward < 0 then + reward = 0 + end + + -- Half if no good weapons + local inv = minetest.get_inventory({ type="player", name = victim }) + if not invHasGoodWeapons(inv) then + ctf.log("ctf_stats", "Player " .. victim .. " has no good weapons") + reward = reward * 0.5 + else + ctf.log("ctf_stats", "Player " .. victim .. " has good weapons") + end + + return reward +end + ctf.register_on_killedplayer(function(victim, killer) local main, match = ctf_stats.player(killer) if main and match then - main.kills = main.kills + 1 + local reward = calculateKillReward(victim, killer) + main.kills = main.kills + 1 + main.score = main.score + reward match.kills = match.kills + 1 + match.score = match.score + reward + match.kills_since_death = match.kills_since_death + 1 ctf.needs_save = true end end) @@ -140,6 +219,7 @@ minetest.register_on_dieplayer(function(player) if main and match then main.deaths = main.deaths + 1 match.deaths = match.deaths + 1 + match.kills_since_death = 0 ctf.needs_save = true end end) diff --git a/mods/ctf_team_base/init.lua b/mods/ctf_team_base/init.lua index 02b9190..55935f9 100644 --- a/mods/ctf_team_base/init.lua +++ b/mods/ctf_team_base/init.lua @@ -42,7 +42,6 @@ local function get_is_player_pro(player) pstat.color = nil table.insert(players, pstat) end - ctf_stats.calc_scores(players) return ctf_stats.player(player:get_player_name()).score > 0 end