capturetheflag/mods/ctf/ctf_stats/gui.lua
2021-05-26 22:16:37 +02:00

289 lines
8.3 KiB
Lua

-- Number of entries to display in the player rankings table
ctf_stats.rankings_display_count = 50
-- Formspec element that governs table columns and their attributes
local tablecolumns = {
"tablecolumns[color;",
"text;",
"text,width=10;",
"text,width=4;",
"text,width=4;",
"text,width=4;",
"text,width=6;",
"text,width=6;",
"text,width=6;",
"text,width=6;",
"text,width=6]"
}
tablecolumns = table.concat(tablecolumns, "")
local function render_team_stats(red, blue, stat, round)
local red_stat, blue_stat = red[stat], blue[stat]
if round then
red_stat = math.floor(red_stat * 10) / 10
blue_stat = math.floor(blue_stat * 10) / 10
end
return red_stat + blue_stat .. " (" ..
minetest.colorize(red.color, tostring(red_stat)) .. " - " ..
minetest.colorize(blue.color, tostring(blue_stat)) .. ")"
end
function ctf_stats.get_formspec_match_summary(stats, winner_team, winner_player, time)
local players = {}
local red = {
color = ctf.flag_colors.red:gsub("0x", "#"),
kills = 0,
deaths = 0,
attempts = 0,
score = 0,
}
local blue = {
color = ctf.flag_colors.blue:gsub("0x", "#"),
kills = 0,
deaths = 0,
attempts = 0,
score = 0,
}
for name, pstat in pairs(stats.red) do
pstat.name = name
pstat.color = ctf.flag_colors.red
table.insert(players, pstat)
red.kills = red.kills + pstat.kills
red.deaths = red.deaths + pstat.deaths
red.attempts = red.attempts + pstat.attempts
red.score = red.score + pstat.score
end
for name, pstat in pairs(stats.blue) do
pstat.name = name
pstat.color = ctf.flag_colors.blue
table.insert(players, pstat)
blue.kills = blue.kills + pstat.kills
blue.deaths = blue.deaths + pstat.deaths
blue.attempts = blue.attempts + pstat.attempts
blue.score = blue.score + pstat.score
end
local match_length = "-"
if time then
match_length = string.format("%02d:%02d:%02d",
math.floor(time / 3600), -- hours
math.floor((time % 3600) / 60), -- minutes
math.floor(time % 60)) -- seconds
end
local red_kd = math.floor(red.kills / red.deaths * 10) / 10
if red.deaths <1 then
red_kd = red.kills
end
local blue_kd = math.floor(blue.kills / blue.deaths * 10) / 10
if blue.deaths <1 then
blue_kd = blue.kills
end
local ret = ctf_stats.get_formspec("Match Summary", players, 1)
-- Winning team and flag capturer name
if stats[winner_team] then
local winner_color = ctf.flag_colors[winner_team]:gsub("0x", "#")
ret = ret .. "item_image[0,0;1,1;ctf_flag:flag_top_"..winner_team.."]"
ret = ret .. "label[1,0;" .. minetest.colorize(winner_color,
"TEAM " .. winner_team:upper() .. " WON!") .. "]"
ret = ret .. "label[1,0.5;Flag captured by " ..
minetest.colorize(winner_color, winner_player) .. "]"
else
ret = ret .. "label[1,0;NO WINNER]"
end
-- Map name
ret = ret .. "label[1,7.6;Map: " .. minetest.colorize("#EEEE00", stats.map) .. "]"
ret = ret .. "label[7.5,0;Kills]"
ret = ret .. "label[9,0;" .. render_team_stats(red, blue, "kills") .. "]"
ret = ret .. "label[4,0;Team K/D]"
ret = ret .. "label[5.5,0;" .. minetest.colorize(red.color, tostring(red_kd)) ..
" - " .. " " .. minetest.colorize(blue.color, tostring(blue_kd)) .. "]"
ret = ret .. "label[7.5,0.5;Attempts]"
ret = ret .. "label[9,0.5;" .. render_team_stats(red, blue, "attempts") .. "]"
ret = ret .. "label[11.5,0;Duration]"
ret = ret .. "label[13,0;" .. match_length .. "]"
ret = ret .. "label[11.5,0.5;Total score]"
ret = ret .. "label[13,0.5;" .. render_team_stats(red, blue, "score", true) .. "]"
ret = ret .. "label[10,7.2;Tip: type /rankings for league tables]"
return ret
end
function ctf_stats.get_formspec(title, players, header, target)
table.sort(players, function(one, two)
return one.score > two.score
end)
local ret = "size[15.5," .. 7 + header .. "]"
ret = ret .. "container[0," .. header .. "]"
ret = ret .. "vertlabel[0,1;" .. title .. "]"
ret = ret .. tablecolumns
ret = ret .. "tableoptions[highlight=#00000000]"
ret = ret .. "table[0.5,0;14.75,6.1;scores;"
ret = ret .. "#ffffff,,Player,Kills,Deaths,K/D,Bounty Kills,Captures,Attempts,Capture Rate,Score"
local hstat, hplace
if type(target) == "number" then
hstat = players[target]
hplace = target
elseif type(target) == "string" then
for i, stat in pairs(players) do
if stat.name == target then
hplace = i
hstat = stat
break
end
end
end
for i = 1, math.min(#players, ctf_stats.rankings_display_count) do
local pstat = players[i]
local color
if hplace == i then
color = "#ffff00"
else
color = pstat.color or "#ffffff"
end
local kd = pstat.kills
if pstat.deaths > 1 then
kd = kd / pstat.deaths
end
local ca = pstat.captures
if pstat.attempts > 1 then
ca = ca / pstat.attempts
end
ret = ret ..
"," .. string.gsub(color, "0x", "#") ..
"," .. i ..
"," .. pstat.name ..
"," .. pstat.kills ..
"," .. pstat.deaths ..
"," .. math.floor(kd * 10) / 10 ..
"," .. pstat.bounty_kills ..
"," .. pstat.captures ..
"," .. pstat.attempts ..
"," .. math.floor(ca * 100) .. "%" ..
"," .. math.floor(pstat.score * 10) / 10
end
ret = ret .. ";-1]"
-- If target not in top 50, add a separate table
-- This would result in the player's score displayed at the bottom
-- of the list but yet be visible without having to scroll
if hplace and hplace > ctf_stats.rankings_display_count then
local h_kd = hstat.kills
if hstat.deaths > 1 then
h_kd = h_kd / hstat.deaths
end
local h_ca = hstat.captures
if hstat.attempts > 1 then
h_ca = h_ca / hstat.attempts
end
ret = ret .. tablecolumns
ret = ret .. "tableoptions[highlight=#00000000]"
ret = ret .. "table[0.5,6.1;13.25,0.4;hlt_score;"
ret = ret .. "#ffff00" ..
"," .. hplace ..
"," .. hstat.name ..
"," .. hstat.kills ..
"," .. hstat.deaths ..
"," .. math.floor(h_kd * 10) / 10 ..
"," .. hstat.bounty_kills ..
"," .. hstat.captures ..
"," .. hstat.attempts ..
"," .. math.floor(h_ca * 100) .. "%" ..
"," .. math.floor(hstat.score * 10) / 10 .. ";-1]"
--[[ else
ret = ret .. "box[0.5,6.1;13.25,0.4;#101010]"
Adds a box where the extra table should be, in order to make it
appear as an extension of the main table, but the color can't be
matched, and looks slightly brighter or slightly darker than the table]]
end
ret = ret .. "button_exit[10,6.5;3,1;close;Close]"
ret = ret .. "container_end[]"
return ret
end
function ctf_stats.get_html(title)
local players = ctf_stats.get_ordered_players()
local ret = "<h1>" .. title .. "</h1>"
ret = ret .. "<table>" ..
"<tr><th></th>" ..
"<th>Player</th>" ..
"<th>Kills</th>" ..
"<th>Deaths</th>" ..
"<th>K/D ratio</th>" ..
"<th>Bounty kills</th>" ..
"<th>Captures</th>" ..
"<th>Attempts</th>" ..
"<th>Capture Rate</th>" ..
"<th>Score</th></tr>"
for i = 1, math.min(#players, 50) do
local pstat = players[i]
local kd = pstat.kills
if pstat.deaths > 1 then
kd = kd / pstat.deaths
end
local ca = pstat.captures
if pstat.attempts > 1 then
ca = ca / pstat.attempts
end
ret = ret ..
"<tr><td>" .. i ..
"</td><td>" .. pstat.name ..
"</td><td>" .. pstat.kills ..
"</td><td>" .. pstat.deaths ..
"</td><td>" .. math.floor(kd * 10) / 10 ..
"</td><td>" .. pstat.bounty_kills ..
"</td><td>" .. pstat.captures ..
"</td><td>" .. pstat.attempts ..
"</td><td>" .. math.floor(ca * 100) .. "%" ..
"</td><td>" .. math.floor(pstat.score*10)/10 .. "</td></tr>"
end
ret = ret .. "</table>\n"
return ret
end
function ctf_stats.html_to_file(filepath)
local f = io.open(filepath, "w")
f:write("<!doctype html>\n")
f:write("<html><head>\n")
f:write("<meta charset=\"utf-8\">\n")
f:write("<title>Player Rankings</title>\n")
f:write("<link rel=\"stylesheet\" href=\"score_style.css\">\n")
f:write("</head><body>\n")
f:write(ctf_stats.get_html("Player Rankings"))
f:write("</body></html>\n")
f:close()
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "ctf_stats:match_summary" then
return
end
local fs
if fields.b_prev then
fs = ctf_stats.prev_match_summary
fs = fs .. "button[6,7.5;4,1;b_curr;Current match >>]"
elseif fields.b_curr then
fs = ctf_stats.get_formspec_match_summary(ctf_stats.current,
ctf_stats.winner_team, ctf_stats.winner_player, ctf_match.get_match_duration())
fs = fs .. "button[6,7.5;4,1;b_prev;<< Previous match]"
else
return
end
minetest.show_formspec(player:get_player_name(), "ctf_stats:match_summary", fs)
end)