diff --git a/mods/ctf_stats/README.md b/mods/ctf_stats/README.md
new file mode 100644
index 000000000..c51d2544d
--- /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 9ba2ccc91..669c2c057 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 b8e49d48d..22eb45049 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 02b9190c0..55935f930 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