New scoreboard HUD (closes #18 and #13)

This commit is contained in:
Giov4 2024-12-11 03:49:09 +01:00
parent edc0dc54bc
commit a128feb1ad
8 changed files with 254 additions and 223 deletions

View File

@ -44,7 +44,6 @@ dofile(minetest.get_modpath("fantasy_brawl") .. "/src/deps/visible_wielditem.lua
dofile(minetest.get_modpath("fantasy_brawl") .. "/src/hud/hud.lua")
dofile(minetest.get_modpath("fantasy_brawl") .. "/src/sounds.lua")
dofile(minetest.get_modpath("fantasy_brawl") .. "/src/score.lua")
dofile(minetest.get_modpath("fantasy_brawl") .. "/src/invulnerability.lua")
dofile(minetest.get_modpath("fantasy_brawl") .. "/src/utils.lua")

View File

@ -1,4 +1,4 @@
name = fantasy_brawl
depends = skills, arena_lib, controls, panel_lib, hud_fs
depends = skills, arena_lib, controls, hud_fs
description = The first brawler minigame on Minetest. Choose your class, prepare your skills and defeat them all!
author = giov4

View File

@ -55,7 +55,7 @@ arena_lib.on_celebration("fantasy_brawl", function(arena, winners)
end
for pl_name, _ in pairs(arena.players_and_spectators) do
fbrawl.show_podium_HUD(pl_name)
fbrawl.show_scoreboard(pl_name)
end
end)
@ -63,13 +63,14 @@ end)
arena_lib.on_death("fantasy_brawl", function(arena, pl_name, reason)
arena.classes[pl_name]:on_death(arena, pl_name, reason)
fbrawl.update_arena_scores(arena)
fbrawl.respawn_player(pl_name)
end)
arena_lib.on_timeout("fantasy_brawl", function(arena)
fbrawl.update_score(arena)
fbrawl.update_arena_scores(arena)
if not arena.scores[2] or (arena.scores[1].score ~= arena.scores[2].score) then
arena_lib.load_celebration("fantasy_brawl", arena, arena.scores[1].pl_name)

View File

@ -4,5 +4,38 @@ ChatCmdBuilder.new("debug", function(cmd)
local arena = arena_lib.get_arena_by_player(name)
arena.players[name].ultimate_recharge = 5
end)
end)
cmd:sub("add_pl :name :score", function(name, target_name, score)
local arena = arena_lib.get_arena_by_player("Giov4")
if not arena then return end
score = tonumber(score)
if not arena.players[target_name] then
arena.players[target_name] = {
kills = score,
deaths = 0
}
else
arena.players[target_name].kills = arena.players[target_name].kills + score
end
fbrawl.update_arena_scores(arena)
end)
cmd:sub("sounds", function(name)
local id = -1
local iteration = 0
local play = function(self_ref)
iteration = iteration + 1
local c_id = id
id = minetest.sound_play({name="fbrawl_puddle"}, {loop=true, to_player=name})
minetest.sound_stop(c_id)
if iteration > 20 then
minetest.sound_stop(id)
return
end
minetest.after(0.1, self_ref, self_ref)
end
play(play)
end)
end)

View File

@ -12,7 +12,6 @@ function fbrawl.init_HUD(arena, pl_name, as_spectator)
local player = minetest.get_player_by_name(pl_name)
fbrawl.generate_scoreboard(pl_name)
fbrawl.generate_timer_HUD(pl_name)
if not as_spectator then
fbrawl.generate_stats(player, pl_name)
@ -53,9 +52,7 @@ function fbrawl.remove_huds(pl_name)
player:hud_remove(id)
end
if panel_lib.has_panel(pl_name, "fbrawl:podium") then
panel_lib.get_panel(pl_name, "fbrawl:podium"):remove()
end
hud_fs.close_hud(player, "fantasy_brawl:scoreboard")
fbrawl.saved_huds[pl_name] = nil
end)

View File

@ -1,157 +1,235 @@
function fbrawl.generate_scoreboard(pl_name)
local imgs_scale = 4
--[[
This file implements a dynamic scoreboard HUD that displays player rankings during matches.
local pl_name_y_offset = 80
Features:
- displays players ranked by kills, then by score (score = kills - deaths/2)
- handles ties correctly: tied players share the same position number and crown
- shows crowns for 1st (golden), 2nd (iron), and 3rd (bronze) positions
- dynamically adjusts vertical background height based on the number of top-3 positions
- highlights the current player's row
- shows kills/deaths for each player
- shows holding AUX1 key
]]
local crown_x_offset = 16*imgs_scale + 300
local crown_y_offset = 16*imgs_scale
-- local function prototypes (no prototypes for globals)
local function scoreboard_to_ranking(arena) end
local function get_crown_material(score_entry, arena) end
local function get_podium_count(arena) end
local function get_scoreboard_formspec(player_name) end
local function show_scoreboard(player_name) end
local scores_scale = imgs_scale - 1
local score_x_offset = 14*scores_scale + 8
local score_y_offset = 18*scores_scale + pl_name_y_offset
local score_value_y_offset = 14
local score_value_x_offset = 16
hud_fs.set_scale("fantasy_brawl:scoreboard", 80)
local active_scoreboards = {} -- [player_name] = true/nil
local panel = Panel:new("fbrawl:podium", {
player = pl_name,
position = {x=0.5, y=0.15},
alignment = {x=0, y=0.5},
bg_scale = {x = 1000, y = 1000},
-- IMAGES
sub_img_elems = {
iron_crown = {
scale = {x = imgs_scale, y = imgs_scale},
offset = {x = -crown_x_offset, y = crown_y_offset},
text = "fbrawl_iron_crown.png",
},
iron_kills = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = -crown_x_offset - score_x_offset, y = crown_y_offset + score_y_offset},
text = "fbrawl_hud_kills_score.png",
},
iron_deaths = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = -crown_x_offset + score_x_offset, y = crown_y_offset + score_y_offset},
text = "fbrawl_hud_deaths_score.png",
},
golden_crown = {
scale = {x = imgs_scale, y = imgs_scale},
offset = {x = 0, y = 0},
text = "fbrawl_golden_crown.png"
},
golden_kills = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = -score_x_offset, y = score_y_offset},
text = "fbrawl_hud_kills_score.png",
},
golden_deaths = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = score_x_offset, y = score_y_offset},
text = "fbrawl_hud_deaths_score.png",
},
function fbrawl.update_arena_scores(arena)
local scores = {}
local score_id = 0
bronze_crown = {
scale = {x = imgs_scale, y = imgs_scale},
offset = {x = crown_x_offset, y = crown_y_offset},
text = "fbrawl_bronze_crown.png"
},
bronze_kills = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = crown_x_offset - score_x_offset, y = crown_y_offset + score_y_offset},
text = "fbrawl_hud_kills_score.png",
},
bronze_deaths = {
scale = {x = scores_scale, y = scores_scale},
offset = {x = crown_x_offset + score_x_offset, y = crown_y_offset + score_y_offset},
text = "fbrawl_hud_deaths_score.png",
},
},
-- calculate and store scores for each player
for player_name, props in pairs(arena.players) do
local score = props.kills - (props.deaths / 2)
table.insert(scores, {player_name = player_name, kills = props.kills, score = score, id = score_id})
score_id = score_id + 1
end
-- TEXTS
sub_txt_elems = {
iron_pl_name = {
offset = {x = -crown_x_offset, y = crown_y_offset + pl_name_y_offset},
text = "-",
size = {x=2}
},
iron_kills_value = {
offset = {x = -crown_x_offset - score_x_offset + score_value_x_offset, y = crown_y_offset + score_y_offset + score_value_y_offset},
text = "-",
},
iron_deaths_value = {
offset = {x = -crown_x_offset + score_x_offset - score_value_x_offset, y = crown_y_offset + score_y_offset + score_value_y_offset},
text = "-",
},
-- sort scores by kills, then score, then by id
table.sort(scores, function(a, b)
if a.kills ~= b.kills then
return a.kills > b.kills
elseif a.score ~= b.score then
return a.score > b.score
else
return a.id > b.id
end
end)
golden_pl_name = {
offset = {x = 0, y = pl_name_y_offset},
text = "-",
size = {x=2}
},
golden_kills_value = {
offset = {x = -score_x_offset + score_value_x_offset, y = score_y_offset + score_value_y_offset},
text = "-",
},
golden_deaths_value = {
offset = {x = score_x_offset - score_value_x_offset, y = score_y_offset + score_value_y_offset},
text = "-",
},
bronze_pl_name = {
offset = {x = crown_x_offset, y = crown_y_offset + pl_name_y_offset},
text = "-",
size = {x=2}
},
bronze_kills_value = {
offset = {x = crown_x_offset - score_x_offset + score_value_x_offset, y = crown_y_offset + score_y_offset + score_value_y_offset},
text = "-",
},
bronze_deaths_value = {
offset = {x = crown_x_offset + score_x_offset - score_value_x_offset, y = crown_y_offset + score_y_offset + score_value_y_offset},
text = "-",
},
}
})
panel:hide()
end
function fbrawl.show_podium_HUD(pl_name)
if not panel_lib.has_panel(pl_name, "fbrawl:podium") then return end
local panel = panel_lib.get_panel(pl_name, "fbrawl:podium")
fbrawl.update_score_hud(pl_name)
panel:show()
arena.scores = scores
end
-- get player ranks from scores:
-- returns a table where `rank[i]` is the rank of the player at index `i` in the scoreboard
-- if `i` and `i-1` have the same score, then `rank[i] == rank[i-1]`
function scoreboard_to_ranking(arena)
local scores = arena.scores
local rank = {}
if #scores == 0 then
return rank
end
for i, curr in ipairs(scores) do
if i == 1 then
rank[i] = 1
else
local prev = scores[i-1]
if curr.kills == prev.kills and curr.score == prev.score then
rank[i] = rank[i-1]
else
rank[i] = rank[i-1] + 1
end
end
end
return rank
end
-- determine the crown type based on rank
function get_crown_material(scoreboard_idx, arena)
if not scoreboard_idx or #arena.scores == 0 then
return nil
end
local entry_rank = scoreboard_to_ranking(arena)[scoreboard_idx]
if entry_rank == 1 then return "golden" end
if entry_rank == 2 then return "iron" end
if entry_rank == 3 then return "bronze" end
return nil
end
-- get the number of podium positions
function get_podium_count(arena)
if #arena.scores == 0 then
return 0
end
local rankings = scoreboard_to_ranking(arena)
local count = 0
for _, rank in pairs(rankings or {}) do
if rank <= 3 then
count = count + 1
end
end
return count
end
-- generate the formspec for the scoreboard
function get_scoreboard_formspec(player_name)
local arena = arena_lib.get_arena_by_player(player_name)
if not arena then
return ""
end
local podium_positions = get_podium_count(arena)
local bg_height = (1.54 / 3) * podium_positions
local formspec = string.format([[
formspec_version[4]
size[0.01,8.1]
position[0.5,0.5]
anchor[0.5,0.5]
no_prepend[]
bgcolor[#FF00FF00]
style[small_label;border=false;font_size=*1;font=mono]
style[title;border=false;font_size=*2;font=bold]
style_type[label;font=mono]
image[-4.85,0.8;0.6,%f;blank.png^[fill:1x1:#472d3ca9]
image[-4.25,0;9.05,0.8;blank.png^[fill:1x1:#7a444a]
image[3.05,0;0.8,0.8;fbrawl_warrior_icon.png]
image[4.02,0.07;0.65,0.65;fbrawl_kills_icon.png^[transformFX]
container[0,-0.45]
]], bg_height)
local rankings = scoreboard_to_ranking(arena)
-- add each player's data to the formspec
for i, score_entry in ipairs(arena.scores) do
local y_offset = (i - 1) * 0.5
local is_current = (score_entry.player_name == player_name)
local bg_color = is_current and "#472d3ca9" or "#472d3c66"
local props = arena.players[score_entry.player_name]
-- bg color and separating line
formspec = formspec .. string.format([[
container[0,%f]
image[-4.25,1.25;9.05,0.5;blank.png^[fill:1x1:%s]
image[-4.25,1.75;9.05,0.02;blank.png^[fill:1x1:#472d3c66]
]], y_offset, bg_color)
-- crown icon
local crown_type = get_crown_material(i, arena)
if crown_type then
formspec = formspec .. string.format(
"image[-4.75,1.3;0.4,0.4;fbrawl_%s_crown.png]\n",
crown_type
)
end
-- place. name kills deaths
formspec = formspec .. string.format([[
label[-4,1.5;%d. %s]
button[3,1;1,1;small_label;%d]
button[3.85,1;1,1;small_label;%d]
container_end[]
]], rankings[i], score_entry.player_name, props.kills, props.deaths)
end
formspec = formspec .. "container_end[]"
return formspec
end
-- display the scoreboard HUD
function show_scoreboard(player_name)
if active_scoreboards[player_name] then
return
end
local player = minetest.get_player_by_name(player_name)
if not player then
return
end
active_scoreboards[player_name] = true
local formspec = get_scoreboard_formspec(player_name)
hud_fs.show_hud(player, "fantasy_brawl:scoreboard", formspec)
end
-- register controls for showing/hiding the scoreboard
controls.register_on_press(function(player, control_name)
local pl_name = player:get_player_name()
local mod = arena_lib.get_mod_by_player(pl_name)
local arena = arena_lib.get_arena_by_player(pl_name)
local player_name = player:get_player_name()
local mod = arena_lib.get_mod_by_player(player_name)
local arena = arena_lib.get_arena_by_player(player_name)
if mod == "fantasy_brawl" and arena.in_game and control_name == "aux1" then
fbrawl.show_podium_HUD(pl_name)
end
if mod == "fantasy_brawl" and arena.in_game and control_name == "aux1" then
show_scoreboard(player_name)
end
end)
controls.register_on_release(function(player, control_name)
local pl_name = player:get_player_name()
local mod = arena_lib.get_mod_by_player(pl_name)
local arena = arena_lib.get_arena_by_player(pl_name)
local player_name = player:get_player_name()
local mod = arena_lib.get_mod_by_player(player_name)
local arena = arena_lib.get_arena_by_player(player_name)
if
mod == "fantasy_brawl" and arena.in_game
and not arena.in_celebration and control_name == "aux1"
then
if not panel_lib.has_panel(pl_name, "fbrawl:podium") then return end
local panel = panel_lib.get_panel(pl_name, "fbrawl:podium")
panel:hide()
end
if mod == "fantasy_brawl" and arena.in_game
and not arena.in_celebration
and control_name == "aux1" then
active_scoreboards[player_name] = nil
local p = minetest.get_player_by_name(player_name)
if p then
hud_fs.close_hud(p, "fantasy_brawl:scoreboard")
end
end
end)
-- cleanup on player leave
minetest.register_on_leaveplayer(function(player)
active_scoreboards[player:get_player_name()] = nil
end)

View File

@ -1,77 +0,0 @@
local function assign_tier() end
local function update_tiers_hud() end
function fbrawl.update_score(arena)
local scores = {}
local score_id = 0
for pl_name, props in pairs(arena.players) do
local score = props.kills - (props.deaths / 2)
table.insert(scores, {pl_name = pl_name, kills = props.kills, score = score, id = score_id})
score_id = score_id + 1
end
table.sort(scores, function (a, b)
if a.kills ~= b.kills then return a.kills > b.kills
elseif a.score ~= b.score then return a.score > b.score
else return a.id > b.id end
end)
arena.scores = scores
end
function fbrawl.update_score_hud(pl_name)
local arena = arena_lib.get_arena_by_player(pl_name)
fbrawl.update_score(arena)
update_tiers_hud(pl_name)
end
function update_tiers_hud(pl_name)
local arena = arena_lib.get_arena_by_player(pl_name)
for i=1, 3, 1 do
local classified_pl_name = "-"
if arena.scores[i] then classified_pl_name = arena.scores[i].pl_name end
if i == 1 then
assign_tier("golden", pl_name, classified_pl_name)
elseif i == 2 then
assign_tier("iron", pl_name, classified_pl_name)
elseif i == 3 then
assign_tier("bronze", pl_name, classified_pl_name)
else
break
end
end
end
function assign_tier(tier, pl_name, classified_pl_name)
local panel = panel_lib.get_panel(pl_name, "fbrawl:podium")
local arena = arena_lib.get_arena_by_player(pl_name)
local override_hud_value = false
if classified_pl_name == "-" then override_hud_value = "-" end
if
not arena.players
or
((not arena.players[classified_pl_name] or not arena.players[classified_pl_name].kills) and not override_hud_value)
then
return
end
panel:update(nil, {
[tier.."_pl_name"] = {text=override_hud_value or classified_pl_name},
[tier.."_deaths_value"] = {text=override_hud_value or arena.players[classified_pl_name].deaths},
[tier.."_kills_value"] = {text=override_hud_value or arena.players[classified_pl_name].kills},
}, nil)
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B