diff --git a/godot/mesh/replay_telegrenade.tscn b/godot/mesh/replay_telegrenade.tscn new file mode 100644 index 0000000..19c27b3 --- /dev/null +++ b/godot/mesh/replay_telegrenade.tscn @@ -0,0 +1,18 @@ +[gd_scene format=3 uid="uid://cnqxjag3wwtno"] + +[ext_resource type="PackedScene" uid="uid://bjnojwqmmv74y" path="res://assets/Telegrenade.fbx" id="1_o7yty"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sugp2"] +albedo_color = Color(0, 0, 1, 1) +emission_enabled = true +emission = Color(0, 0, 0.96862745, 1) +emission_energy_multiplier = 5.0 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jyhfs"] +albedo_color = Color(0.30692267, 0.3069227, 0.3069227, 1) + +[node name="Telegrenade" unique_id=1761178296 instance=ExtResource("1_o7yty")] + +[node name="Cube" parent="." index="0" unique_id=218178446] +surface_material_override/0 = SubResource("StandardMaterial3D_sugp2") +surface_material_override/1 = SubResource("StandardMaterial3D_jyhfs") diff --git a/godot/raw_assets/Soldier/Telegrenade.blend b/godot/raw_assets/Soldier/Telegrenade.blend index b133b84..1ca678a 100644 Binary files a/godot/raw_assets/Soldier/Telegrenade.blend and b/godot/raw_assets/Soldier/Telegrenade.blend differ diff --git a/godot/raw_assets/Soldier/Telegrenade.blend1 b/godot/raw_assets/Soldier/Telegrenade.blend1 index 2694934..b133b84 100644 Binary files a/godot/raw_assets/Soldier/Telegrenade.blend1 and b/godot/raw_assets/Soldier/Telegrenade.blend1 differ diff --git a/godot/scenes/misc/standalone_replay_global.tscn b/godot/scenes/misc/standalone_replay_global.tscn index 3a1a730..4ea06a5 100644 --- a/godot/scenes/misc/standalone_replay_global.tscn +++ b/godot/scenes/misc/standalone_replay_global.tscn @@ -3,8 +3,10 @@ [ext_resource type="PackedScene" uid="uid://d3377ecff6nyn" path="res://scenes/replay_finished.tscn" id="1_q5ujf"] [ext_resource type="PackedScene" uid="uid://c1gs6cqek1qlq" path="res://scenes/ui/hud.tscn" id="2_a451s"] [ext_resource type="PackedScene" uid="uid://lahirbqfppvw" path="res://scenes/main.tscn" id="2_r7g00"] +[ext_resource type="PackedScene" uid="uid://coqf6cn4hntqi" path="res://scenes/ui/replay_hud.tscn" id="4_v7405"] [node name="standalone_replay_global" type="StandaloneReplayManager" unique_id=730657724] replay_finished_screen = ExtResource("1_q5ujf") main_menu = ExtResource("2_r7g00") hud_scene = ExtResource("2_a451s") +replay_hud_scene = ExtResource("4_v7405") diff --git a/godot/scenes/ui/replay_hud.tscn b/godot/scenes/ui/replay_hud.tscn new file mode 100644 index 0000000..4cb18c9 --- /dev/null +++ b/godot/scenes/ui/replay_hud.tscn @@ -0,0 +1,61 @@ +[gd_scene format=3 uid="uid://coqf6cn4hntqi"] + +[node name="replay_hud" type="ReplayHud" unique_id=899758035 node_paths=PackedStringArray("current_time", "end_time", "slider")] +current_time = NodePath("panel/h_box_container/label") +end_time = NodePath("panel/h_box_container/label2") +slider = NodePath("panel/h_box_container/h_slider") +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 0 + +[node name="panel" type="Panel" parent="." unique_id=389733281] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -279.0 +offset_top = -152.0 +offset_right = 281.0 +offset_bottom = -49.0 +grow_horizontal = 2 +grow_vertical = 0 + +[node name="h_box_container" type="HBoxContainer" parent="panel" unique_id=343157918] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 +metadata/_edit_lock_ = true + +[node name="label" type="Label" parent="panel/h_box_container" unique_id=1140720179] +custom_minimum_size = Vector2(50, 0) +custom_maximum_size = Vector2(50, -1) +clip_contents = true +layout_mode = 2 +text = "0:00" +horizontal_alignment = 1 +metadata/_edit_lock_ = true + +[node name="h_slider" type="HSlider" parent="panel/h_box_container" unique_id=605407748] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +metadata/_edit_lock_ = true + +[node name="label2" type="Label" parent="panel/h_box_container" unique_id=1489275389] +custom_minimum_size = Vector2(50, 0) +custom_maximum_size = Vector2(50, -1) +clip_contents = true +layout_mode = 2 +text = "0:00" +horizontal_alignment = 1 +metadata/_edit_lock_ = true diff --git a/rust/src/replay.rs b/rust/src/replay.rs index 62c00da..beb6427 100644 --- a/rust/src/replay.rs +++ b/rust/src/replay.rs @@ -18,7 +18,7 @@ use crate::{ DamageSource, IPlayer, NetTransform, ball::Ball, remote_player::RemotePlayer, telegrenade::Telegrenade, weapon::WeaponType, }, - ui::hud::Hud, + ui::{hud::Hud, replay_hud::ReplayHud}, }; pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal"; @@ -32,16 +32,36 @@ pub struct StandaloneReplayManager { main_menu: Option>, #[export] hud_scene: Option>, + #[export] + replay_hud_scene: Option>, + end_time: f64, loading_replay: bool, map: Option>, pub playback: Option>, replay: Option, hud: Option>, + replay_hud: Option>, + since_last_update: f64, base: Base, } +#[godot_api] +impl INode for StandaloneReplayManager { + fn process(&mut self, delta: f64) { + self.since_last_update += delta; + if self.since_last_update >= 1. + && let Some(hud) = &mut self.replay_hud + && let Some(playback) = &self.playback + { + self.since_last_update = 0.; + hud.bind_mut() + .update_times(playback.bind().current_time, self.end_time, true); + } + } +} + #[godot_api] impl StandaloneReplayManager { #[signal] @@ -87,6 +107,7 @@ impl StandaloneReplayManager { .on_finished() .connect_other(self, Self::on_done); + self.end_time = replay.find_ending(); playback.bind_mut().replay = replay; playback.bind_mut().current_map = Some(map.clone()); playback.bind_mut().current_time = 0.; @@ -96,7 +117,7 @@ impl StandaloneReplayManager { self.base_mut().add_child(&playback); if let Some(scene) = &self.hud_scene { - let hud = scene.instantiate_as::(); + let mut hud = scene.instantiate_as::(); hud.signals().on_exit_to_lobby().connect_other(self, |s| { s.on_done(); @@ -109,6 +130,20 @@ impl StandaloneReplayManager { self.hud = Some(hud.clone()); playback.add_child(&hud); + + if let Some(scene) = &self.replay_hud_scene { + let mut replay_hud = scene.instantiate_as::(); + + replay_hud.bind_mut().update_times(0., self.end_time, true); + + replay_hud + .signals() + .on_time_changed() + .connect_other(self, |s, new_time| s.fast_forward_to(new_time)); + + self.replay_hud = Some(replay_hud.clone()); + hud.add_child(&replay_hud); + } } } } @@ -127,6 +162,9 @@ impl StandaloneReplayManager { if let Some(mut hud) = self.hud.take() { hud.queue_free(); } + if let Some(mut hud) = self.replay_hud.take() { + hud.queue_free(); + } if let Some(mut playback) = self.playback.take() { playback.queue_free(); } @@ -137,6 +175,14 @@ impl StandaloneReplayManager { self.base().get_tree().change_scene_to_packed(scene); } } + + pub fn fast_forward_to(&mut self, new_time: f64) { + if let Some(playback) = &mut self.playback { + playback.bind_mut().current_time = new_time; + playback.bind_mut().current_frame_idx = 0; + playback.bind_mut().fast_forwarding = true; + } + } } #[derive(GodotClass)] @@ -564,6 +610,14 @@ impl Replay { .collect::>(), } } + + pub fn find_ending(&self) -> f64 { + if let Some(frame) = self.frames.last() { + frame.time + } else { + 0. + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs index 99120d7..d344595 100644 --- a/rust/src/ui/mod.rs +++ b/rust/src/ui/mod.rs @@ -11,6 +11,7 @@ pub mod net_stats; pub mod player_listing; pub mod player_listings; pub mod replay_finished_screen; +pub mod replay_hud; pub mod settings; pub mod simple_score_view; pub mod slider; diff --git a/rust/src/ui/replay_hud.rs b/rust/src/ui/replay_hud.rs new file mode 100644 index 0000000..59795dd --- /dev/null +++ b/rust/src/ui/replay_hud.rs @@ -0,0 +1,70 @@ +use godot::{ + classes::{Control, IControl, Label, Slider}, + prelude::*, +}; + +#[derive(GodotClass)] +#[class(base=Control, init)] +pub struct ReplayHud { + #[export] + pub current_time: Option>, + #[export] + pub end_time: Option>, + #[export] + pub slider: Option>, + + dragging: bool, + + base: Base, +} + +#[godot_api] +impl IControl for ReplayHud { + fn ready(&mut self) { + if let Some(slider) = self.slider.clone() { + slider + .signals() + .drag_started() + .connect_other(self, |s| s.dragging = true); + slider + .signals() + .drag_ended() + .connect_other(self, |s, _| s.dragging = false); + slider + .signals() + .value_changed() + .connect_other(self, |s, new_value| s.on_change(new_value)); + } + } +} + +#[godot_api] +impl ReplayHud { + #[signal] + pub fn on_time_changed(new_time: f64); + + fn on_change(&mut self, new_value: f64) { + if self.dragging { + if let Some(current_time_label) = &mut self.current_time { + current_time_label.set_text(&format!("{:.2}", new_value)); + } + self.signals().on_time_changed().emit(new_value); + } + } + + pub fn update_times(&mut self, current_time: f64, end_time: f64, set_value: bool) { + if let Some(current_time_label) = &mut self.current_time { + current_time_label.set_text(&format!("{:.2}", current_time)); + } + if let Some(end_time_label) = &mut self.end_time { + end_time_label.set_text(&format!("{:.2}", end_time)); + } + if let Some(slider) = &mut self.slider { + slider.set_min(0.); + slider.set_max(end_time); + if set_value && !self.dragging { + slider.set_value(current_time); + } + } + } +}