2018-11-12 21:31:05 +00:00
|
|
|
-- Formspec element that governs table columns and their attributes
|
|
|
|
local tablecolumns = {
|
|
|
|
"tablecolumns[color;",
|
|
|
|
"text;",
|
2019-02-23 15:11:35 +00:00
|
|
|
"text,width=16;",
|
2018-11-12 21:31:05 +00:00
|
|
|
"text,width=4;",
|
|
|
|
"text,width=4;",
|
|
|
|
"text,width=4;",
|
|
|
|
"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)
|
2018-04-18 20:49:47 +00:00
|
|
|
local red_stat, blue_stat = red[stat], blue[stat]
|
|
|
|
if round then
|
2018-11-12 21:31:05 +00:00
|
|
|
red_stat = math.floor(red_stat * 10) / 10
|
|
|
|
blue_stat = math.floor(blue_stat * 10) / 10
|
2018-04-18 20:49:47 +00:00
|
|
|
end
|
2018-11-12 21:31:05 +00:00
|
|
|
return red_stat + blue_stat .. " (" ..
|
|
|
|
minetest.colorize(red.color, tostring(red_stat)) .. " - " ..
|
|
|
|
minetest.colorize(blue.color, tostring(blue_stat)) .. ")"
|
2018-04-18 20:49:47 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ctf_stats.get_formspec_match_summary(stats, winner_team, winner_player, time)
|
2015-12-03 23:46:52 +00:00
|
|
|
local players = {}
|
2018-04-18 20:49:47 +00:00
|
|
|
local red = {
|
|
|
|
color = ctf.flag_colors.red:gsub("0x", "#"),
|
|
|
|
kills = 0,
|
|
|
|
attempts = 0,
|
|
|
|
score = 0,
|
|
|
|
}
|
|
|
|
local blue = {
|
|
|
|
color = ctf.flag_colors.blue:gsub("0x", "#"),
|
|
|
|
kills = 0,
|
|
|
|
attempts = 0,
|
|
|
|
score = 0,
|
|
|
|
}
|
2015-12-03 23:46:52 +00:00
|
|
|
for name, pstat in pairs(stats.red) do
|
|
|
|
pstat.name = name
|
|
|
|
pstat.color = ctf.flag_colors.red
|
|
|
|
table.insert(players, pstat)
|
2018-04-18 20:49:47 +00:00
|
|
|
red.kills = red.kills + pstat.kills
|
|
|
|
red.attempts = red.attempts + pstat.attempts
|
|
|
|
red.score = red.score + pstat.score
|
2015-12-03 23:46:52 +00:00
|
|
|
end
|
|
|
|
for name, pstat in pairs(stats.blue) do
|
|
|
|
pstat.name = name
|
|
|
|
pstat.color = ctf.flag_colors.blue
|
|
|
|
table.insert(players, pstat)
|
2018-04-18 20:49:47 +00:00
|
|
|
blue.kills = blue.kills + pstat.kills
|
|
|
|
blue.attempts = blue.attempts + pstat.attempts
|
|
|
|
blue.score = blue.score + pstat.score
|
|
|
|
end
|
|
|
|
|
2018-08-03 17:51:25 +00:00
|
|
|
local match_length = string.format("%02d:%02d:%02d",
|
2018-11-12 21:31:05 +00:00
|
|
|
math.floor(time / 3600), -- hours
|
|
|
|
math.floor((time % 3600) / 60), -- minutes
|
|
|
|
math.floor(time % 60)) -- seconds
|
2018-08-03 17:51:25 +00:00
|
|
|
|
2018-04-18 20:49:47 +00:00
|
|
|
local ret = ctf_stats.get_formspec("Match Summary", players, 1)
|
|
|
|
|
|
|
|
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.."]"
|
2018-11-12 21:31:05 +00:00
|
|
|
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) .. "]"
|
2018-04-18 20:49:47 +00:00
|
|
|
else
|
|
|
|
ret = ret .. "label[1,0;NO WINNER]"
|
2015-12-03 23:46:52 +00:00
|
|
|
end
|
2018-04-18 20:49:47 +00:00
|
|
|
|
2018-08-03 17:51:25 +00:00
|
|
|
ret = ret .. "label[6.5,0;Kills]"
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. "label[8,0;" .. render_team_stats(red, blue, "kills") .. "]"
|
2018-08-03 17:51:25 +00:00
|
|
|
ret = ret .. "label[6.5,0.5;Attempts]"
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. "label[8,0.5;" .. render_team_stats(red, blue, "attempts") .. "]"
|
|
|
|
ret = ret .. "label[10.5,0;Duration]"
|
|
|
|
ret = ret .. "label[12,0;" .. match_length .. "]"
|
|
|
|
ret = ret .. "label[10.5,0.5;Total score]"
|
|
|
|
ret = ret .. "label[12,0.5;" .. render_team_stats(red, blue, "score", true) .. "]"
|
|
|
|
ret = ret .. "label[2,7.75;Tip: type /rankings for league tables]"
|
2018-07-02 08:18:25 +00:00
|
|
|
|
2015-12-03 23:46:52 +00:00
|
|
|
return ret
|
|
|
|
end
|
|
|
|
|
2018-08-03 11:38:34 +00:00
|
|
|
function ctf_stats.get_formspec(title, players, header, hlt_name)
|
2015-12-03 23:46:52 +00:00
|
|
|
table.sort(players, function(one, two)
|
2015-12-04 11:14:22 +00:00
|
|
|
return one.score > two.score
|
2015-12-03 23:46:52 +00:00
|
|
|
end)
|
|
|
|
|
2018-11-12 21:31:05 +00:00
|
|
|
local ret = "size[14," .. 7 + header .. "]"
|
2018-04-18 22:02:55 +00:00
|
|
|
ret = ret .. default.gui_bg .. default.gui_bg_img
|
2018-08-03 11:38:34 +00:00
|
|
|
ret = ret .. "container[0," .. header .. "]"
|
2018-04-18 20:49:47 +00:00
|
|
|
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. "vertlabel[0,1;" .. title .. "]"
|
|
|
|
ret = ret .. tablecolumns
|
2015-12-03 23:46:52 +00:00
|
|
|
ret = ret .. "tableoptions[highlight=#00000000]"
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. "table[0.5,0;13.25,6.1;scores;"
|
|
|
|
ret = ret .. "#ffffff,,Player,Kills,Deaths,K/D,Bounty Kills,Captures,Attempts,Score"
|
2015-12-03 23:46:52 +00:00
|
|
|
|
2018-08-03 11:38:34 +00:00
|
|
|
local player_in_top_50 = false
|
|
|
|
|
|
|
|
for i = 1, math.min(#players, 50) do
|
2015-12-04 11:14:22 +00:00
|
|
|
local pstat = players[i]
|
2018-08-03 11:38:34 +00:00
|
|
|
local color
|
|
|
|
if hlt_name and pstat.name == hlt_name then
|
|
|
|
color = "#ffff00"
|
|
|
|
player_in_top_50 = true
|
|
|
|
else
|
|
|
|
color = pstat.color or "#ffffff"
|
|
|
|
end
|
2015-12-18 02:14:33 +00:00
|
|
|
local kd = pstat.kills
|
2018-08-03 11:38:34 +00:00
|
|
|
if pstat.deaths > 1 then
|
2015-12-18 02:14:33 +00:00
|
|
|
kd = kd / pstat.deaths
|
|
|
|
end
|
2015-12-03 23:46:52 +00:00
|
|
|
ret = ret ..
|
2018-08-03 11:38:34 +00:00
|
|
|
"," .. 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(pstat.score * 10) / 10
|
|
|
|
end
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. ";-1]"
|
2018-08-03 11:38:34 +00:00
|
|
|
|
2018-11-12 21:31:05 +00:00
|
|
|
-- If hlt_name 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
|
2018-08-03 11:38:34 +00:00
|
|
|
if hlt_name and not player_in_top_50 then
|
|
|
|
local hlt_player, hlt_rank, hlt_kd
|
|
|
|
|
|
|
|
for i = 1, #players do
|
|
|
|
if players[i].name == hlt_name then
|
|
|
|
hlt_player = players[i]
|
|
|
|
hlt_rank = i
|
|
|
|
break
|
|
|
|
end
|
2015-12-03 23:57:28 +00:00
|
|
|
end
|
2018-08-03 11:38:34 +00:00
|
|
|
|
2018-11-12 21:31:05 +00:00
|
|
|
if hlt_player then
|
|
|
|
hlt_kd = hlt_player.kills
|
|
|
|
if hlt_player.deaths > 1 then
|
|
|
|
hlt_kd = hlt_kd / hlt_player.deaths
|
|
|
|
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" ..
|
|
|
|
"," .. hlt_rank ..
|
|
|
|
"," .. hlt_player.name ..
|
|
|
|
"," .. hlt_player.kills ..
|
|
|
|
"," .. hlt_player.deaths ..
|
|
|
|
"," .. math.floor(hlt_kd * 10) / 10 ..
|
|
|
|
"," .. hlt_player.bounty_kills ..
|
|
|
|
"," .. hlt_player.captures ..
|
|
|
|
"," .. hlt_player.attempts ..
|
|
|
|
"," .. math.floor(hlt_player.score * 10) / 10 .. ";-1]"
|
2018-08-03 11:38:34 +00:00
|
|
|
end
|
2018-11-12 21:31:05 +00:00
|
|
|
-- 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
|
2015-12-03 23:46:52 +00:00
|
|
|
end
|
|
|
|
|
2018-11-12 21:31:05 +00:00
|
|
|
ret = ret .. "button_exit[10,6.5;3,1;close;Close]"
|
2018-04-18 20:49:47 +00:00
|
|
|
ret = ret .. "container_end[]"
|
2015-12-03 23:46:52 +00:00
|
|
|
return ret
|
|
|
|
end
|
2015-12-03 23:57:28 +00:00
|
|
|
|
2015-12-07 23:49:58 +00:00
|
|
|
function ctf_stats.get_html(title, players)
|
2017-10-12 23:09:25 +00:00
|
|
|
table.sort(players, function(one, two)
|
|
|
|
return one.score > two.score
|
|
|
|
end)
|
2015-12-07 23:49:58 +00:00
|
|
|
|
|
|
|
local ret = "<h1>" .. title .. "</h1>"
|
|
|
|
ret = ret .. "<table>" ..
|
|
|
|
"<tr><th></th>" ..
|
2018-08-03 11:38:34 +00:00
|
|
|
"<th>Player</th>" ..
|
|
|
|
"<th>Kills</th>" ..
|
|
|
|
"<th>Deaths</th>" ..
|
2015-12-07 23:49:58 +00:00
|
|
|
"<th>K/D ratio</th>" ..
|
2018-08-03 11:38:34 +00:00
|
|
|
"<th>Bounty kills</th>" ..
|
|
|
|
"<th>Captures</th>" ..
|
|
|
|
"<th>Attempts</th>" ..
|
|
|
|
"<th>Score</th></tr>"
|
2015-12-07 23:49:58 +00:00
|
|
|
|
2018-08-03 11:38:34 +00:00
|
|
|
for i = 1, math.min(#players, 50) do
|
2015-12-07 23:49:58 +00:00
|
|
|
local pstat = players[i]
|
2015-12-18 02:14:33 +00:00
|
|
|
local kd = pstat.kills
|
2018-08-03 11:38:34 +00:00
|
|
|
if pstat.deaths > 1 then
|
2015-12-18 02:14:33 +00:00
|
|
|
kd = kd / pstat.deaths
|
|
|
|
end
|
2015-12-07 23:49:58 +00:00
|
|
|
ret = ret ..
|
|
|
|
"<tr><td>" .. i ..
|
|
|
|
"</td><td>" .. pstat.name ..
|
|
|
|
"</td><td>" .. pstat.kills ..
|
|
|
|
"</td><td>" .. pstat.deaths ..
|
2018-08-03 11:38:34 +00:00
|
|
|
"</td><td>" .. math.floor(kd * 10) / 10 ..
|
|
|
|
"</td><td>" .. pstat.bounty_kills ..
|
2015-12-07 23:49:58 +00:00
|
|
|
"</td><td>" .. pstat.captures ..
|
|
|
|
"</td><td>" .. pstat.attempts ..
|
|
|
|
"</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 players = {}
|
|
|
|
for name, pstat in pairs(ctf_stats.players) do
|
|
|
|
pstat.name = name
|
|
|
|
pstat.color = nil
|
|
|
|
table.insert(players, pstat)
|
|
|
|
end
|
|
|
|
local html = ctf_stats.get_html("Player Rankings", players)
|
|
|
|
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(html)
|
|
|
|
f:write("</body></html>\n")
|
|
|
|
f:close()
|
|
|
|
end
|
2015-12-03 23:57:28 +00:00
|
|
|
|
2018-12-30 23:10:39 +00:00
|
|
|
local function return_as_chat_result(to, name)
|
2018-01-03 00:23:38 +00:00
|
|
|
local players = {}
|
|
|
|
for pname, pstat in pairs(ctf_stats.players) do
|
|
|
|
pstat.name = pname
|
|
|
|
pstat.color = nil
|
|
|
|
table.insert(players, pstat)
|
|
|
|
end
|
|
|
|
|
|
|
|
table.sort(players, function(one, two)
|
|
|
|
return one.score > two.score
|
|
|
|
end)
|
|
|
|
|
|
|
|
local place = -1
|
|
|
|
local me = nil
|
|
|
|
for i = 1, #players do
|
|
|
|
local pstat = players[i]
|
|
|
|
if pstat.name == name then
|
|
|
|
me = pstat
|
|
|
|
place = i
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if place < 1 then
|
|
|
|
place = #players + 1
|
|
|
|
end
|
2018-11-12 15:15:55 +00:00
|
|
|
local you_are_in = (to == name) and "You are in " or name .. " is in "
|
|
|
|
local result = you_are_in .. place .. " place.\n"
|
2018-01-03 00:23:38 +00:00
|
|
|
if me then
|
|
|
|
local kd = me.kills
|
2018-08-03 11:38:34 +00:00
|
|
|
if me.deaths > 1 then
|
2018-01-03 00:23:38 +00:00
|
|
|
kd = kd / me.deaths
|
|
|
|
end
|
2018-01-26 23:51:36 +00:00
|
|
|
result = result .. "Kills: " .. me.kills ..
|
2018-01-03 00:23:38 +00:00
|
|
|
" | Deaths: " .. me.deaths ..
|
2018-08-03 11:38:34 +00:00
|
|
|
" | K/D: " .. math.floor(kd * 10) / 10 ..
|
2018-11-12 15:15:55 +00:00
|
|
|
"\nBounty kills: " .. me.bounty_kills ..
|
2018-01-03 00:23:38 +00:00
|
|
|
" | Captures: " .. me.captures ..
|
|
|
|
" | Attempts: " .. me.attempts ..
|
2018-11-12 15:15:55 +00:00
|
|
|
"\nScore: " .. math.floor(me.score)
|
2018-01-03 00:23:38 +00:00
|
|
|
end
|
2018-01-26 23:51:36 +00:00
|
|
|
return true, result
|
2018-01-03 00:23:38 +00:00
|
|
|
end
|
|
|
|
|
2018-11-12 15:17:17 +00:00
|
|
|
minetest.register_chatcommand("r", {
|
2018-12-30 23:10:39 +00:00
|
|
|
params = "[<name>]",
|
|
|
|
description = "Display rankings of yourself or another player as a chat result.",
|
2018-11-12 15:17:17 +00:00
|
|
|
func = function(name, param)
|
2018-12-30 23:10:39 +00:00
|
|
|
local target
|
|
|
|
if param ~= "" then
|
|
|
|
param = param:trim()
|
|
|
|
if ctf_stats.players[param] then
|
|
|
|
target = param
|
|
|
|
else
|
|
|
|
return false, "Can't find player '" .. param .. "'"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
target = name
|
|
|
|
end
|
|
|
|
return return_as_chat_result(name, target)
|
2018-11-12 15:17:17 +00:00
|
|
|
end
|
|
|
|
})
|
|
|
|
|
2015-12-03 23:57:28 +00:00
|
|
|
minetest.register_chatcommand("rankings", {
|
2018-10-17 21:25:40 +00:00
|
|
|
params = "[<name>]",
|
|
|
|
description = "Display rankings of yourself or another player.",
|
2015-12-18 02:14:33 +00:00
|
|
|
func = function(name, param)
|
2018-08-03 11:38:34 +00:00
|
|
|
local target
|
|
|
|
if param ~= "" then
|
|
|
|
param = param:trim()
|
|
|
|
if ctf_stats.players[param] then
|
|
|
|
target = param
|
2018-01-03 00:23:38 +00:00
|
|
|
else
|
2018-08-03 11:38:34 +00:00
|
|
|
return false, "Can't find player '" .. param .. "'"
|
2015-12-18 02:14:33 +00:00
|
|
|
end
|
2018-08-03 11:38:34 +00:00
|
|
|
else
|
|
|
|
target = name
|
|
|
|
end
|
|
|
|
|
|
|
|
if not minetest.get_player_by_name(name) then
|
2018-12-30 23:10:39 +00:00
|
|
|
return return_as_chat_result(name, target)
|
2015-12-18 02:14:33 +00:00
|
|
|
else
|
|
|
|
local players = {}
|
|
|
|
for pname, pstat in pairs(ctf_stats.players) do
|
|
|
|
pstat.name = pname
|
|
|
|
pstat.color = nil
|
|
|
|
table.insert(players, pstat)
|
|
|
|
end
|
2018-08-03 11:38:34 +00:00
|
|
|
|
|
|
|
local fs = ctf_stats.get_formspec("Player Rankings", players, 0, target)
|
2015-12-18 02:14:33 +00:00
|
|
|
minetest.show_formspec(name, "ctf_stats:rankings", fs)
|
2015-12-03 23:57:28 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
})
|
2018-01-27 00:01:17 +00:00
|
|
|
|
|
|
|
local reset_y = {}
|
|
|
|
minetest.register_chatcommand("reset_rankings", {
|
2018-10-17 21:25:40 +00:00
|
|
|
params = "[<name>]",
|
|
|
|
description = "Reset the rankings of yourself or another player",
|
2018-01-27 00:01:17 +00:00
|
|
|
func = function(name, param)
|
|
|
|
param = param:trim()
|
2018-12-03 16:29:12 +00:00
|
|
|
if param ~= "" and not minetest.check_player_privs(name, {ctf_admin = true}) then
|
2018-01-27 00:01:17 +00:00
|
|
|
return false, "Missing privilege: ctf_admin"
|
|
|
|
end
|
|
|
|
|
|
|
|
local reset_name = param == "" and name or param
|
|
|
|
|
2018-12-03 16:29:12 +00:00
|
|
|
if not ctf_stats.players[reset_name] then
|
|
|
|
return false, "Player '" .. reset_name .. "' does not exist."
|
|
|
|
end
|
|
|
|
|
2018-01-27 00:01:17 +00:00
|
|
|
if reset_name == name and not reset_y[name] then
|
|
|
|
reset_y[name] = true
|
2018-12-03 16:29:12 +00:00
|
|
|
minetest.after(30, function()
|
|
|
|
reset_y[name] = nil
|
|
|
|
end)
|
|
|
|
return true, "This will reset your stats and rankings completely."
|
|
|
|
.. " You will lose access to any special privileges such as the"
|
|
|
|
.. " team chest or userlimit skip. This is irreversable. If you're"
|
|
|
|
.. " sure, re-type /reset_rankings within 30 seconds to reset."
|
2018-01-27 00:01:17 +00:00
|
|
|
end
|
|
|
|
reset_y[name] = nil
|
|
|
|
|
2018-12-03 16:29:12 +00:00
|
|
|
ctf_stats.players[reset_name] = nil
|
2018-01-27 00:01:17 +00:00
|
|
|
ctf_stats.player(reset_name)
|
2018-10-17 21:25:40 +00:00
|
|
|
return true, "Successfully reset the stats and ranking of " .. reset_name
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_chatcommand("transfer_rankings", {
|
|
|
|
params = "<src> <dest>",
|
|
|
|
description = "Transfer rankings of one player to another.",
|
|
|
|
privs = {ctf_admin = true},
|
|
|
|
func = function(name, param)
|
|
|
|
if not param then
|
|
|
|
return false, "Invalid syntax. Provide source and destination player names."
|
|
|
|
end
|
|
|
|
param = param:trim()
|
|
|
|
local src, dest = param:trim():match("([%a%d_-]+) ([%a%d_-]+)")
|
|
|
|
if not src or not dest then
|
|
|
|
return false, "Invalid usage, see /help transfer_rankings"
|
|
|
|
end
|
|
|
|
if not ctf_stats.players[src] then
|
|
|
|
return false, "Player '" .. src .. "' does not exist."
|
|
|
|
end
|
|
|
|
if not ctf_stats.players[dest] then
|
|
|
|
return false, "Player '" .. dest .. "' does not exist."
|
|
|
|
end
|
|
|
|
|
|
|
|
ctf_stats.players[dest] = ctf_stats.players[src]
|
|
|
|
ctf_stats.players[src] = nil
|
|
|
|
|
|
|
|
return true, "Stats of '" .. src .. "' have been transferred to '" .. dest .. "'."
|
2018-01-27 00:01:17 +00:00
|
|
|
end
|
|
|
|
})
|