diff --git a/interfaces/hud/scoreboard.gd b/interfaces/hud/scoreboard.gd
new file mode 100644
index 0000000..c936cbc
--- /dev/null
+++ b/interfaces/hud/scoreboard.gd
@@ -0,0 +1,86 @@
+# This file is part of open-fpsz.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+class_name Scoreboard extends Control
+
+@export var _entries : Dictionary = {}
+
+class ScoreboardEntry:
+ var nickname : String
+ var kills : int
+ var score : int
+ var nickname_label : Label = Label.new()
+ var kills_label : Label = Label.new()
+ var score_label : Label = Label.new()
+ func free() -> void:
+ nickname_label.queue_free()
+ kills_label.queue_free()
+ super.free()
+
+func _unhandled_input(event : InputEvent) -> void:
+ if event.is_action_pressed("scoreboard"):
+ show()
+ elif event.is_action_released("scoreboard"):
+ hide()
+
+func add_player(player : Player) -> void:
+ _add_scoreboard_entry.rpc(player.player_id, player.nickname)
+
+func remove_player(player : Player) -> void:
+ _remove_scoreboard_entry.rpc(player.player_id)
+
+func increment_kill_count(player : Player) -> void:
+ _entries[player.player_id].kills += 1
+
+func add_score_to_player(player : Player, amount : int) -> void:
+ var player_id : int = player.player_id
+ var entry : ScoreboardEntry = _entries[player_id]
+ _update_scoreboard_entry.rpc(player_id, entry.nickname, entry.kills, entry.score + amount)
+
+@rpc("any_peer", "call_remote", "reliable")
+func request_scoreboard_from_authority() -> void:
+ if is_multiplayer_authority():
+ var recipient_id : int = multiplayer.get_remote_sender_id()
+ for entry_player_id : int in _entries:
+ var entry : ScoreboardEntry = _entries[entry_player_id]
+ _add_scoreboard_entry.rpc_id(recipient_id, entry_player_id, entry.nickname, entry.kills, entry.score)
+
+@rpc("authority", "call_local", "reliable")
+func _add_scoreboard_entry(player_id : int, nickname : String, kills : int = 0, score : int = 0) -> void:
+ var new_entry : ScoreboardEntry = ScoreboardEntry.new()
+ _entries[player_id] = new_entry
+ %Scores.add_child(new_entry.nickname_label)
+ %Scores.add_child(new_entry.kills_label)
+ %Scores.add_child(new_entry.score_label)
+ _update_scoreboard_entry(player_id, nickname, kills, score)
+
+@rpc("authority", "call_local", "reliable")
+func _update_scoreboard_entry(player_id : int, nickname : String, kills : int, score : int) -> void:
+ var entry : ScoreboardEntry = _entries[player_id]
+ entry.nickname = nickname
+ entry.kills = kills
+ entry.score = score
+ _update_scoreboard_entry_ui(player_id)
+
+@rpc("authority", "call_local", "reliable")
+func _remove_scoreboard_entry(player_id : int) -> void:
+ var entry : ScoreboardEntry = _entries[player_id]
+ entry.free()
+ _entries.erase(player_id)
+
+func _update_scoreboard_entry_ui(player_id : int) -> void:
+ var entry : ScoreboardEntry = _entries[player_id]
+ entry.nickname_label.text = entry.nickname
+ entry.kills_label.text = str(entry.kills)
+ entry.score_label.text = str(entry.score)
diff --git a/interfaces/hud/scoreboard.tscn b/interfaces/hud/scoreboard.tscn
new file mode 100644
index 0000000..ab907da
--- /dev/null
+++ b/interfaces/hud/scoreboard.tscn
@@ -0,0 +1,59 @@
+[gd_scene load_steps=2 format=3 uid="uid://b8bosdd0o1lu7"]
+
+[ext_resource type="Script" path="res://interfaces/hud/scoreboard.gd" id="1_k2wav"]
+
+[node name="Scoreboard" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_k2wav")
+
+[node name="Panel" type="Panel" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 64
+theme_override_constants/margin_top = 64
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 4
+theme_type_variation = &"HeaderLarge"
+text = "Scoreboard"
+
+[node name="Scores" type="GridContainer" parent="Panel/MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+theme_override_constants/h_separation = 32
+theme_override_constants/v_separation = 16
+columns = 3
+
+[node name="PlayerLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/Scores"]
+layout_mode = 2
+text = "Player"
+
+[node name="KillsLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/Scores"]
+layout_mode = 2
+text = "Kills"
+
+[node name="ScoreLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/Scores"]
+layout_mode = 2
+text = "Score"
diff --git a/modes/multiplayer.gd b/modes/multiplayer.gd
index 9b01051..9dbb9f9 100644
--- a/modes/multiplayer.gd
+++ b/modes/multiplayer.gd
@@ -25,7 +25,6 @@ class_name Multiplayer extends Node
@onready var objectives : Node = $Objectives
@onready var map : Node = $Map
@onready var scoreboard : Scoreboard = $Scoreboard
-@onready var scoreboard_ui : Node = $ScoreboardUI
var _map_manager : Map
var _flag : Flag
@@ -71,7 +70,6 @@ func _on_player_died(player : Player, killer_id : int) -> void:
var killer : Player = players.get_node(node_name)
scoreboard.increment_kill_count(killer)
scoreboard.add_score_to_player(killer, 10)
- scoreboard.broadcast_player_score_update(killer)
await get_tree().create_timer(RESPAWN_TIME).timeout
respawn_player(player)
@@ -87,14 +85,14 @@ func add_player(peer_id : int, nickname : String) -> void:
player.global_position = _map_manager.get_player_spawn().position
players.add_child(player)
player.died.connect(_on_player_died)
- scoreboard.add_entry(player)
+ scoreboard.add_player(player)
print("Peer `%s` connected" % player.name)
func remove_player(peer_id : int) -> void:
var node_name : String = str(peer_id)
if players.has_node(node_name):
var player : Player = players.get_node(node_name)
- scoreboard.remove_entry(player)
+ scoreboard.remove_player(player)
player.die(-1)
player.queue_free()
print("Peer `%s` disconnected" % node_name)
@@ -114,19 +112,6 @@ func _add_flag() -> void:
_flag.global_position = _map_manager.get_flagstand().global_position
objectives.add_child(_flag)
-func _unhandled_input(event : InputEvent) -> void:
- if event.is_action_pressed("scoreboard"):
- var entries : Array = scoreboard.get_entries()
- for entry : Scoreboard.ScoreboardEntry in entries:
- var entry_label : Label = Label.new()
- entry_label.text = "%s | kills: %s | score: %s" % [entry.nickname, entry.kills, entry.score]
- %Scores.add_child(entry_label)
- scoreboard_ui.show()
- elif event.is_action_released("scoreboard"):
- scoreboard_ui.hide()
- for score_label in %Scores.get_children():
- score_label.queue_free()
-
@rpc("any_peer", "reliable")
func _join_match(nickname : String) -> void:
if is_multiplayer_authority():
@@ -134,7 +119,6 @@ func _join_match(nickname : String) -> void:
func _on_flag_grabbed(grabber : Player) -> void:
scoreboard.add_score_to_player(grabber, 10)
- scoreboard.broadcast_player_score_update(grabber)
_flag_carrier_scoring_timer.start()
func _on_flag_dropped() -> void:
@@ -142,7 +126,6 @@ func _on_flag_dropped() -> void:
func _on_flag_carrier_scoring_timer_timeout() -> void:
scoreboard.add_score_to_player(_flag.last_carrier, 10)
- scoreboard.broadcast_player_score_update(_flag.last_carrier)
func _exit_tree() -> void:
if is_multiplayer_authority():
diff --git a/modes/multiplayer.tscn b/modes/multiplayer.tscn
index cda1fae..f817cf6 100644
--- a/modes/multiplayer.tscn
+++ b/modes/multiplayer.tscn
@@ -4,7 +4,7 @@
[ext_resource type="Script" path="res://modes/multiplayer.gd" id="1_r1kd6"]
[ext_resource type="PackedScene" uid="uid://cbhx1xme0sb7k" path="res://entities/player/player.tscn" id="2_og1vb"]
[ext_resource type="PackedScene" uid="uid://c88l3h0ph00c7" path="res://entities/flag/flag.tscn" id="3_h0rie"]
-[ext_resource type="Script" path="res://modes/scoreboard.gd" id="4_n0mhp"]
+[ext_resource type="PackedScene" uid="uid://b8bosdd0o1lu7" path="res://interfaces/hud/scoreboard.tscn" id="5_uj0pp"]
[node name="Multiplayer" type="Node"]
script = ExtResource("1_r1kd6")
@@ -31,35 +31,5 @@ spawn_path = NodePath("../Players")
_spawnable_scenes = PackedStringArray("res://entities/flag/flag.tscn")
spawn_path = NodePath("../Objectives")
-[node name="Scoreboard" type="Node" parent="."]
-script = ExtResource("4_n0mhp")
-
-[node name="ScoreboardUI" type="Control" parent="."]
+[node name="Scoreboard" parent="." instance=ExtResource("5_uj0pp")]
visible = false
-layout_mode = 3
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-
-[node name="PanelContainer" type="PanelContainer" parent="ScoreboardUI"]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-
-[node name="VBoxContainer" type="VBoxContainer" parent="ScoreboardUI/PanelContainer"]
-layout_mode = 2
-size_flags_horizontal = 4
-size_flags_vertical = 0
-
-[node name="Label" type="Label" parent="ScoreboardUI/PanelContainer/VBoxContainer"]
-layout_mode = 2
-text = "Scoreboard"
-
-[node name="Scores" type="VBoxContainer" parent="ScoreboardUI/PanelContainer/VBoxContainer"]
-unique_name_in_owner = true
-layout_mode = 2
diff --git a/modes/scoreboard.gd b/modes/scoreboard.gd
deleted file mode 100644
index 0283944..0000000
--- a/modes/scoreboard.gd
+++ /dev/null
@@ -1,77 +0,0 @@
-# This file is part of open-fpsz.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-class_name Scoreboard extends Node
-
-@export var _entries : Dictionary = {}
-
-class ScoreboardEntry:
- var nickname : String
- var kills : int
- var score : int
-
-func add_entry(player : Player) -> void:
- var new_entry : ScoreboardEntry = ScoreboardEntry.new()
- new_entry.nickname = player.nickname
- new_entry.kills = 0
- new_entry.score = 0
- _entries[player.player_id] = new_entry
- _send_scoreboard_entry.rpc(player.player_id, new_entry.nickname, new_entry.kills, new_entry.score)
-
-func remove_entry(player : Player) -> void:
- _entries.erase(player.player_id)
- _broadcast_player_removed(player)
-
-func add_score_to_player(player : Player, amount : int) -> void:
- _entries[player.player_id].score += amount
-
-func increment_kill_count(player : Player) -> void:
- _entries[player.player_id].kills += 1
-
-func get_entries() -> Array:
- return _entries.values()
-
-@rpc("any_peer", "call_remote", "reliable")
-func request_scoreboard_from_authority() -> void:
- if is_multiplayer_authority():
- var recipient_id : int = multiplayer.get_remote_sender_id()
- _clear_scoreboard.rpc_id(recipient_id)
- for entry_key : int in _entries:
- var entry : ScoreboardEntry = _entries[entry_key]
- _send_scoreboard_entry.rpc_id(recipient_id, entry_key, entry.nickname, entry.kills, entry.score)
-
-@rpc("authority", "reliable")
-func _clear_scoreboard() -> void:
- _entries.clear()
-
-@rpc("authority", "reliable")
-func _send_scoreboard_entry(player_id : int, nickname : String, kills : int, score : int) -> void:
- var new_entry : ScoreboardEntry = ScoreboardEntry.new()
- new_entry.nickname = nickname
- new_entry.kills = kills
- new_entry.score = score
- _entries[player_id] = new_entry
-
-@rpc("authority", "reliable")
-func _remove_scoreboard_entry(player_id : int) -> void:
- _entries.erase(player_id)
-
-func broadcast_player_score_update(player : Player) -> void:
- var player_id : int = player.player_id
- var player_score_entry : ScoreboardEntry = _entries[player_id]
- _send_scoreboard_entry.rpc(player_id, player_score_entry.nickname, player_score_entry.kills, player_score_entry.score)
-
-func _broadcast_player_removed(player : Player) -> void:
- var player_id : int = player.player_id
- _remove_scoreboard_entry.rpc(player_id)
diff --git a/tests/test_scoreboard.gd b/tests/test_scoreboard.gd
index 43e8ec5..2e38007 100644
--- a/tests/test_scoreboard.gd
+++ b/tests/test_scoreboard.gd
@@ -15,24 +15,25 @@
extends GutTest
var PLAYER : PackedScene = preload("res://entities/player/player.tscn")
+var SCOREBOARD : PackedScene = preload("res://interfaces/hud/scoreboard.tscn")
var _subject : Scoreboard
func before_each() -> void:
- _subject = Scoreboard.new()
+ _subject = SCOREBOARD.instantiate()
add_child(_subject)
func after_each() -> void:
_subject.free()
func test_that_new_scoreboard_is_empty() -> void:
- assert_eq(_subject.get_entries(), [])
+ assert_eq(_subject._entries, {})
func test_that_added_entry_is_added_correctly() -> void:
var player : Player = PLAYER.instantiate()
player.nickname = "test_nickname"
- _subject.add_entry(player)
- var entries : Array = _subject.get_entries()
+ _subject.add_player(player)
+ var entries : Array = _subject._entries.values()
assert_eq(1, entries.size())
var tested_entry : Scoreboard.ScoreboardEntry = entries[0]
assert_eq("test_nickname", tested_entry.nickname)
@@ -42,16 +43,16 @@ func test_that_added_entry_is_added_correctly() -> void:
func test_that_scores_are_added_correctly() -> void:
var player : Player = PLAYER.instantiate()
- _subject.add_entry(player)
+ _subject.add_player(player)
_subject.add_score_to_player(player, 10)
- var tested_entry : Scoreboard.ScoreboardEntry = _subject.get_entries()[0]
+ var tested_entry : Scoreboard.ScoreboardEntry = _subject._entries.values()[0]
assert_eq(10, tested_entry.score)
player.free()
func test_that_kill_counts_are_incremented_correctly() -> void:
var player : Player = PLAYER.instantiate()
- _subject.add_entry(player)
+ _subject.add_player(player)
_subject.increment_kill_count(player)
- var tested_entry : Scoreboard.ScoreboardEntry = _subject.get_entries()[0]
+ var tested_entry : Scoreboard.ScoreboardEntry = _subject._entries.values()[0]
assert_eq(1, tested_entry.kills)
player.free()