Compare commits

...

2 Commits

Author SHA1 Message Date
0685b83555 Fix errors 2026-07-28 23:11:54 +03:00
620ed980a2 Implement videoplayer feature for replay 2026-07-28 23:02:43 +03:00
8 changed files with 210 additions and 2 deletions

View File

@ -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")

View File

@ -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://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://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://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] [node name="standalone_replay_global" type="StandaloneReplayManager" unique_id=730657724]
replay_finished_screen = ExtResource("1_q5ujf") replay_finished_screen = ExtResource("1_q5ujf")
main_menu = ExtResource("2_r7g00") main_menu = ExtResource("2_r7g00")
hud_scene = ExtResource("2_a451s") hud_scene = ExtResource("2_a451s")
replay_hud_scene = ExtResource("4_v7405")

View File

@ -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

View File

@ -18,7 +18,7 @@ use crate::{
DamageSource, IPlayer, NetTransform, ball::Ball, remote_player::RemotePlayer, DamageSource, IPlayer, NetTransform, ball::Ball, remote_player::RemotePlayer,
telegrenade::Telegrenade, weapon::WeaponType, telegrenade::Telegrenade, weapon::WeaponType,
}, },
ui::hud::Hud, ui::{hud::Hud, replay_hud::ReplayHud},
}; };
pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal"; pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal";
@ -32,16 +32,36 @@ pub struct StandaloneReplayManager {
main_menu: Option<Gd<PackedScene>>, main_menu: Option<Gd<PackedScene>>,
#[export] #[export]
hud_scene: Option<Gd<PackedScene>>, hud_scene: Option<Gd<PackedScene>>,
#[export]
replay_hud_scene: Option<Gd<PackedScene>>,
end_time: f64,
loading_replay: bool, loading_replay: bool,
map: Option<Gd<Map>>, map: Option<Gd<Map>>,
pub playback: Option<Gd<ReplayPlayback>>, pub playback: Option<Gd<ReplayPlayback>>,
replay: Option<Replay>, replay: Option<Replay>,
hud: Option<Gd<Hud>>, hud: Option<Gd<Hud>>,
replay_hud: Option<Gd<ReplayHud>>,
since_last_update: f64,
base: Base<Node>, base: Base<Node>,
} }
#[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] #[godot_api]
impl StandaloneReplayManager { impl StandaloneReplayManager {
#[signal] #[signal]
@ -87,6 +107,7 @@ impl StandaloneReplayManager {
.on_finished() .on_finished()
.connect_other(self, Self::on_done); .connect_other(self, Self::on_done);
self.end_time = replay.find_ending();
playback.bind_mut().replay = replay; playback.bind_mut().replay = replay;
playback.bind_mut().current_map = Some(map.clone()); playback.bind_mut().current_map = Some(map.clone());
playback.bind_mut().current_time = 0.; playback.bind_mut().current_time = 0.;
@ -96,7 +117,7 @@ impl StandaloneReplayManager {
self.base_mut().add_child(&playback); self.base_mut().add_child(&playback);
if let Some(scene) = &self.hud_scene { if let Some(scene) = &self.hud_scene {
let hud = scene.instantiate_as::<Hud>(); let mut hud = scene.instantiate_as::<Hud>();
hud.signals().on_exit_to_lobby().connect_other(self, |s| { hud.signals().on_exit_to_lobby().connect_other(self, |s| {
s.on_done(); s.on_done();
@ -109,6 +130,20 @@ impl StandaloneReplayManager {
self.hud = Some(hud.clone()); self.hud = Some(hud.clone());
playback.add_child(&hud); playback.add_child(&hud);
if let Some(scene) = &self.replay_hud_scene {
let mut replay_hud = scene.instantiate_as::<ReplayHud>();
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() { if let Some(mut hud) = self.hud.take() {
hud.queue_free(); hud.queue_free();
} }
if let Some(mut hud) = self.replay_hud.take() {
hud.queue_free();
}
if let Some(mut playback) = self.playback.take() { if let Some(mut playback) = self.playback.take() {
playback.queue_free(); playback.queue_free();
} }
@ -137,6 +175,16 @@ impl StandaloneReplayManager {
self.base().get_tree().change_scene_to_packed(scene); self.base().get_tree().change_scene_to_packed(scene);
} }
} }
pub fn fast_forward_to(&mut self, new_time: f64) {
self.run_deferred(move |s| {
if let Some(playback) = &mut s.playback {
playback.bind_mut().current_time = new_time;
playback.bind_mut().current_frame_idx = 0;
playback.bind_mut().fast_forwarding = true;
}
});
}
} }
#[derive(GodotClass)] #[derive(GodotClass)]
@ -564,6 +612,14 @@ impl Replay {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
} }
} }
pub fn find_ending(&self) -> f64 {
if let Some(frame) = self.frames.last() {
frame.time
} else {
0.
}
}
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -11,6 +11,7 @@ pub mod net_stats;
pub mod player_listing; pub mod player_listing;
pub mod player_listings; pub mod player_listings;
pub mod replay_finished_screen; pub mod replay_finished_screen;
pub mod replay_hud;
pub mod settings; pub mod settings;
pub mod simple_score_view; pub mod simple_score_view;
pub mod slider; pub mod slider;

70
rust/src/ui/replay_hud.rs Normal file
View File

@ -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<Gd<Label>>,
#[export]
pub end_time: Option<Gd<Label>>,
#[export]
pub slider: Option<Gd<Slider>>,
dragging: bool,
base: Base<Control>,
}
#[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_no_signal(current_time);
}
}
}
}