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()