From 1664ee797316354a454ee125c4e5757e9af0ecc0 Mon Sep 17 00:00:00 2001 From: Sofia Date: Fri, 24 Jul 2026 03:58:44 +0300 Subject: [PATCH] Add chat & chat box --- godot/maps/default_map/map0.tscn | 9 -- godot/project.godot | 5 ++ godot/scenes/lobby.tscn | 93 ++++++++++----------- godot/scenes/player/local_player.tscn | 13 ++- godot/scenes/ui/chat_box.tscn | 35 ++++++++ godot/scenes/ui/command_line_panel.tscn | 11 ++- rust/src/chat.rs | 7 +- rust/src/player/mod.rs | 22 ++++- rust/src/ui/chatbox.rs | 104 ++++++++++++++++++++++++ rust/src/ui/mod.rs | 1 + 10 files changed, 234 insertions(+), 66 deletions(-) create mode 100644 godot/scenes/ui/chat_box.tscn create mode 100644 rust/src/ui/chatbox.rs diff --git a/godot/maps/default_map/map0.tscn b/godot/maps/default_map/map0.tscn index af5222b..6cfc5cd 100644 --- a/godot/maps/default_map/map0.tscn +++ b/godot/maps/default_map/map0.tscn @@ -26,15 +26,6 @@ local_player = ExtResource("1_yjvui") remote_player = ExtResource("2_kcncr") ball = ExtResource("3_e0nj3") -[node name="net_stats_label" type="NetStatsLabel" parent="." unique_id=1821474986] -anchors_preset = 2 -anchor_top = 1.0 -anchor_bottom = 1.0 -offset_top = -152.0 -offset_right = 369.0 -grow_vertical = 0 -vertical_alignment = 2 - [node name="terrain" type="MeshInstance3D" parent="." unique_id=1474127252] material_override = SubResource("StandardMaterial3D_u8vuu") mesh = SubResource("PlaneMesh_1cgct") diff --git a/godot/project.godot b/godot/project.godot index f806a22..c8e2dd8 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -99,6 +99,11 @@ scoreboard={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +open_chat={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/godot/scenes/lobby.tscn b/godot/scenes/lobby.tscn index d4c50bc..91d9970 100644 --- a/godot/scenes/lobby.tscn +++ b/godot/scenes/lobby.tscn @@ -1,6 +1,7 @@ [gd_scene format=3 uid="uid://bwd6bj21x310s"] [ext_resource type="PackedScene" uid="uid://ccd0wj2usx5os" path="res://scenes/ui/lobby_player_listing.tscn" id="1_o1atq"] +[ext_resource type="PackedScene" uid="uid://ffh5txda3j1" path="res://scenes/ui/chat_box.tscn" id="2_5gu6x"] [sub_resource type="LabelSettings" id="LabelSettings_1cgct"] font_size = 32 @@ -12,7 +13,7 @@ bg_color = Color(0.30692267, 0.3069227, 0.30692264, 1) players_list = NodePath("v_box_container/players_list") name_field = NodePath("v_box_container/h_box_container/text_edit") player_listing_prefab = ExtResource("1_o1atq") -ready_button = NodePath("ready_button") +ready_button = NodePath("v_box_container3/ready_button") map_selection = NodePath("v_box_container2/option_button") options_panel = NodePath("options_panel") options_grid = NodePath("options_panel/h_box_container/options_grid") @@ -26,8 +27,8 @@ grow_vertical = 2 layout_mode = 1 anchors_preset = 10 anchor_right = 1.0 -offset_top = 69.0 -offset_bottom = 92.0 +offset_top = 8.0 +offset_bottom = 53.0 grow_horizontal = 2 text = "Lobby" label_settings = SubResource("LabelSettings_1cgct") @@ -35,14 +36,13 @@ horizontal_alignment = 1 [node name="v_box_container" type="VBoxContainer" parent="." unique_id=336946640] layout_mode = 0 -offset_left = 129.0 -offset_top = 146.0 -offset_right = 334.0 -offset_bottom = 200.0 +offset_left = 85.0 +offset_top = 111.0 +offset_right = 290.0 +offset_bottom = 165.0 [node name="h_box_container" type="HBoxContainer" parent="v_box_container" unique_id=1789302658] layout_mode = 2 -offset_transform_enabled = true offset_transform_position = Vector2(100, 0) metadata/_edit_lock_ = true @@ -63,38 +63,15 @@ metadata/_edit_lock_ = true layout_mode = 2 metadata/_edit_lock_ = true -[node name="netstats" type="NetStatsLabel" parent="." unique_id=1335814511] -layout_mode = 1 -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -144.0 -grow_horizontal = 2 -grow_vertical = 0 -vertical_alignment = 2 - -[node name="ready_button" type="Button" parent="." unique_id=311553419] -layout_mode = 1 -anchors_preset = 2 -anchor_top = 1.0 -anchor_bottom = 1.0 -offset_left = 129.0 -offset_top = -177.0 -offset_right = 278.0 -offset_bottom = -126.0 -grow_vertical = 0 -text = "Not Ready" - [node name="v_box_container2" type="VBoxContainer" parent="." unique_id=653902328] layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -329.0 -offset_top = 146.0 -offset_right = -221.0 -offset_bottom = 204.0 +offset_left = -835.0 +offset_top = 111.0 +offset_right = -727.0 +offset_bottom = 169.0 grow_horizontal = 0 [node name="option_button" type="OptionButton" parent="v_box_container2" unique_id=426342653] @@ -145,30 +122,48 @@ layout_mode = 2 layout_mode = 2 columns = 2 -[node name="options_button" type="Button" parent="." unique_id=2109111628] +[node name="chat_box" parent="." unique_id=1125601400 instance=ExtResource("2_5gu6x")] layout_mode = 1 anchors_preset = 3 anchor_left = 1.0 anchor_top = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = -309.0 -offset_top = -166.0 -offset_right = -239.0 -offset_bottom = -135.0 +offset_left = -535.0 +offset_top = -352.0 +offset_right = -35.0 +offset_bottom = -52.0 grow_horizontal = 0 grow_vertical = 0 -text = "Options" -[node name="exit_button" type="Button" parent="." unique_id=267703633] +[node name="v_box_container3" type="VBoxContainer" parent="." unique_id=1320505732] layout_mode = 0 -offset_left = 43.0 -offset_top = 33.0 -offset_right = 108.0 -offset_bottom = 86.0 -theme_override_font_sizes/font_size = 32 +offset_left = 87.0 +offset_top = 538.0 +offset_right = 198.0 +offset_bottom = 604.0 + +[node name="ready_button" type="Button" parent="v_box_container3" unique_id=311553419] +custom_minimum_size = Vector2(100, 0) +custom_maximum_size = Vector2(100, -1) +layout_mode = 2 +text = "Not Ready" +metadata/_edit_lock_ = true + +[node name="h_box_container" type="HBoxContainer" parent="v_box_container3" unique_id=1165099293] +layout_mode = 2 +metadata/_edit_lock_ = true + +[node name="exit_button" type="Button" parent="v_box_container3/h_box_container" unique_id=267703633] +layout_mode = 2 text = "Exit" +metadata/_edit_lock_ = true + +[node name="options_button" type="Button" parent="v_box_container3/h_box_container" unique_id=2109111628] +layout_mode = 2 +text = "Options" +metadata/_edit_lock_ = true [connection signal="pressed" from="options_panel/options_close" to="." method="close_options"] -[connection signal="pressed" from="options_button" to="." method="open_options"] -[connection signal="pressed" from="exit_button" to="." method="exit"] +[connection signal="pressed" from="v_box_container3/h_box_container/exit_button" to="." method="exit"] +[connection signal="pressed" from="v_box_container3/h_box_container/options_button" to="." method="open_options"] diff --git a/godot/scenes/player/local_player.tscn b/godot/scenes/player/local_player.tscn index 6632cb0..53c612d 100644 --- a/godot/scenes/player/local_player.tscn +++ b/godot/scenes/player/local_player.tscn @@ -4,13 +4,14 @@ [ext_resource type="PackedScene" uid="uid://cl66unms2gim0" path="res://scenes/player/effects.tscn" id="2_ywmwa"] [ext_resource type="PackedScene" uid="uid://n3u0jr4wiib1" path="res://scenes/ui/settings.tscn" id="3_beej8"] [ext_resource type="PackedScene" uid="uid://2sfy0vq0fqt3" path="res://scenes/ui/player_listing.tscn" id="4_okac2"] +[ext_resource type="PackedScene" uid="uid://ffh5txda3j1" path="res://scenes/ui/chat_box.tscn" id="5_cv68x"] [sub_resource type="Environment" id="Environment_3nx84"] glow_enabled = true [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_oh8vq"] -[node name="player" type="LocalPlayer" unique_id=863055906 node_paths=PackedStringArray("camera", "soldier_mesh", "collider", "effects", "dead_cam_target", "respawn_label", "ball_origin", "weapon_node", "pause_panel", "exit_to_lobby", "settings_panel", "scoreboard")] +[node name="player" type="LocalPlayer" unique_id=863055906 node_paths=PackedStringArray("camera", "soldier_mesh", "collider", "effects", "dead_cam_target", "respawn_label", "ball_origin", "weapon_node", "pause_panel", "exit_to_lobby", "settings_panel", "scoreboard", "chatbox")] look_sensitivity = 1.5 camera = NodePath("camera_3d") move_speed = 5.0 @@ -25,6 +26,7 @@ pause_panel = NodePath("pause_menu") exit_to_lobby = NodePath("pause_menu/v_box_container/exit_to_lobby") settings_panel = NodePath("settings") scoreboard = NodePath("scoreboard") +chatbox = NodePath("chat_box") health = 100 collision_mask = 4 @@ -137,6 +139,15 @@ layout_mode = 2 alignment = 1 metadata/_edit_lock_ = true +[node name="chat_box" parent="." unique_id=1125601400 instance=ExtResource("5_cv68x")] +in_game = true +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_top = -300.0 +offset_bottom = 0.0 +grow_vertical = 0 + [connection signal="pressed" from="pause_menu/v_box_container/settings" to="settings" method="open"] [connection signal="pressed" from="pause_menu/v_box_container/exit_to_lobby" to="." method="exit_to_lobby"] [connection signal="pressed" from="pause_menu/v_box_container/exit" to="." method="exit"] diff --git a/godot/scenes/ui/chat_box.tscn b/godot/scenes/ui/chat_box.tscn new file mode 100644 index 0000000..9566448 --- /dev/null +++ b/godot/scenes/ui/chat_box.tscn @@ -0,0 +1,35 @@ +[gd_scene format=3 uid="uid://ffh5txda3j1"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_61vwu"] +bg_color = Color(0.6, 0.6, 0.6, 0) + +[node name="chat_box" type="ChatBox" unique_id=1125601400 node_paths=PackedStringArray("messages", "line_edit")] +messages = NodePath("panel/scroll_container/v_box_container") +line_edit = NodePath("line_edit") +show_edit = true +custom_minimum_size = Vector2(500, 300) +custom_maximum_size = Vector2(500, 300) +offset_right = 500.0 +offset_bottom = 300.0 + +[node name="panel" type="Panel" parent="." unique_id=674673935] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_61vwu") +metadata/_edit_lock_ = true + +[node name="scroll_container" type="ScrollContainer" parent="panel" unique_id=810829343] +layout_mode = 0 +offset_right = 500.0 +offset_bottom = 270.0 +horizontal_scroll_mode = 3 +vertical_scroll_mode = 3 + +[node name="v_box_container" type="VBoxContainer" parent="panel/scroll_container" unique_id=406354045] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +alignment = 2 + +[node name="line_edit" type="LineEdit" parent="." unique_id=785348302] +layout_mode = 2 diff --git a/godot/scenes/ui/command_line_panel.tscn b/godot/scenes/ui/command_line_panel.tscn index c1161fd..23f94c9 100644 --- a/godot/scenes/ui/command_line_panel.tscn +++ b/godot/scenes/ui/command_line_panel.tscn @@ -10,9 +10,9 @@ scroll_container = NodePath("scroll_container") anchors_preset = 10 anchor_right = 1.0 offset_left = 1.0 -offset_top = -344.0 +offset_top = -540.0 offset_right = 1.0 -offset_bottom = -37.0 +offset_bottom = -233.0 grow_horizontal = 2 [node name="input_field" type="TextEdit" parent="command_line_panel" unique_id=2095748573] @@ -45,3 +45,10 @@ scroll_active = false autowrap_mode = 0 shortcut_keys_enabled = false metadata/_edit_lock_ = true + +[node name="net_stats_label" type="NetStatsLabel" parent="command_line_panel" unique_id=860927733] +layout_mode = 0 +offset_left = -1.0 +offset_top = 344.0 +offset_right = 341.0 +offset_bottom = 499.0 diff --git a/rust/src/chat.rs b/rust/src/chat.rs index f61a23e..9df325f 100644 --- a/rust/src/chat.rs +++ b/rust/src/chat.rs @@ -29,7 +29,7 @@ impl Chat { pub fn new_message(&mut self, message: ChatMessage) { self.messages.len() as i64; self.messages.push(message.clone()); - self.signals().on_new_message().emit(&message.as_label()); + self.run_deferred(move |s| s.signals().on_new_message().emit(&message.as_label())); } } @@ -42,6 +42,7 @@ pub enum ChatMessage { impl ChatMessage { pub fn as_label(&self) -> Gd { let mut label = RichTextLabel::new_alloc(); + label.set_fit_content(true); match self { ChatMessage::Death(source, player_id) => { if let Some(game) = Game::singleton() @@ -86,8 +87,8 @@ fn add_player(label: &mut Gd, player: &Player) { if let Some(game) = Game::singleton() && let Some(team) = game.bind().get_team(player.data.team) { - label.append_text(&format!("[color={}]", team.bind().color)); + label.push_color(team.bind().color); label.add_text(&player.data.name); - label.append_text(&format!("[/color]")); + label.pop(); } } diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index 6c90bc4..8776962 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -26,7 +26,7 @@ use crate::{ soldier_mesh::SoldierMesh, weapon::{BallWeapon, Raygun, Weapon, WeaponType}, }, - ui::{cli::CommandLinePanel, settings::SettingsPanel}, + ui::{chatbox::ChatBox, cli::CommandLinePanel, settings::SettingsPanel}, }; pub mod effects; @@ -208,6 +208,8 @@ pub struct LocalPlayer { #[export] scoreboard: Option>, #[export] + chatbox: Option>, + #[export] health: i32, pub player_id: Option, @@ -408,6 +410,17 @@ impl ICharacterBody3D for LocalPlayer { s.update_lock(); }); + if let Some(chatbox) = self.chatbox.clone() { + chatbox + .signals() + .on_opened() + .connect_other(self, |s| s.update_lock()); + chatbox + .signals() + .on_closed() + .connect_other(self, |s| s.update_lock()); + } + self.swap_weapon(WeaponType::Raygun); if let Some(camera) = &mut self.camera { @@ -628,7 +641,12 @@ impl LocalPlayer { } else { false }; - s.set_lock(!CommandLinePanel::singleton().bind().opened && !paused); + let chat_visible = if let Some(chat) = &s.chatbox { + chat.bind().is_editing() + } else { + false + }; + s.set_lock(!CommandLinePanel::singleton().bind().opened && !paused && !chat_visible); }); } diff --git a/rust/src/ui/chatbox.rs b/rust/src/ui/chatbox.rs new file mode 100644 index 0000000..bd0bfe7 --- /dev/null +++ b/rust/src/ui/chatbox.rs @@ -0,0 +1,104 @@ +use godot::{ + classes::{ + Engine, IVBoxContainer, InputEvent, LineEdit, Panel, RichTextLabel, TextEdit, VBoxContainer, + }, + prelude::*, +}; + +use crate::chat::Chat; + +#[derive(GodotClass)] +#[class(base=VBoxContainer, init, tool)] +pub struct ChatBox { + #[export] + messages: Option>, + #[export] + line_edit: Option>, + #[export] + pub in_game: bool, + + base: Base, +} + +#[godot_api] +impl IVBoxContainer for ChatBox { + fn ready(&mut self) { + if !Engine::singleton().is_editor_hint() { + self.run_deferred(|s| { + if let Some(chat) = Chat::singleton() { + for message in chat.bind().get_messages() { + s.on_new_message(message); + } + chat.signals() + .on_new_message() + .connect_other(s, |s, m| s.run_deferred(|s| s.on_new_message(m))); + } + }); + + if let Some(line_edit) = self.line_edit.clone() { + line_edit + .signals() + .text_submitted() + .connect_other(self, Self::on_submit); + } + } + + if self.in_game + && let Some(line_edit) = &mut self.line_edit + { + line_edit.set_visible(false); + } + } + + fn input(&mut self, event: Gd) { + if self.in_game && event.is_action_pressed("open_chat") { + if let Some(line_edit) = &mut self.line_edit { + let is_visible = line_edit.is_visible(); + if !is_visible { + line_edit.set_visible(true); + + let mut line_edit = line_edit.clone(); + self.run_deferred(move |_| { + if line_edit.is_visible() { + line_edit.grab_focus(); + } + }); + self.signals().on_opened().emit(); + } + } + } + } +} + +#[godot_api] +impl ChatBox { + #[signal] + pub fn on_opened(); + #[signal] + pub fn on_closed(); + + fn on_new_message(&mut self, message: Gd) { + if let Some(messages) = &mut self.messages { + messages.add_child(&message); + } + } + + pub fn on_submit(&mut self, message: GString) { + godot_print!("{}", message); + if let Some(line_edit) = &mut self.line_edit { + line_edit.clear(); + if self.in_game { + line_edit.set_visible(false); + } + } + self.signals().on_closed().emit(); + } + + pub fn is_editing(&self) -> bool { + if let Some(line_edit) = &self.line_edit { + line_edit.is_visible() + } else { + false + } + } +} diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs index 07ae55a..e4f5374 100644 --- a/rust/src/ui/mod.rs +++ b/rust/src/ui/mod.rs @@ -1,3 +1,4 @@ +pub mod chatbox; pub mod cli; pub mod game_finished_screen; pub mod lobby;