Add replay finished screen
This commit is contained in:
parent
6cd8515449
commit
e75cb79c86
@ -1,3 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://djmb2jqcpkuep"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d3377ecff6nyn" path="res://scenes/replay_finished.tscn" id="1_q5ujf"]
|
||||
|
||||
[node name="standalone_replay_global" type="StandaloneReplayManager" unique_id=730657724]
|
||||
replay_finished_screen = ExtResource("1_q5ujf")
|
||||
|
||||
65
godot/scenes/replay_finished.tscn
Normal file
65
godot/scenes/replay_finished.tscn
Normal file
@ -0,0 +1,65 @@
|
||||
[gd_scene format=3 uid="uid://d3377ecff6nyn"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://2sfy0vq0fqt3" path="res://scenes/ui/player_listing.tscn" id="1_k8op7"]
|
||||
|
||||
[node name="replay_finished" type="ReplayFinishedScreen" unique_id=291257212 node_paths=PackedStringArray("title_container", "back_button", "player_listings")]
|
||||
title_container = NodePath("title_container")
|
||||
back_button = NodePath("button")
|
||||
player_listings = NodePath("player_listings")
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="title_container" type="HBoxContainer" parent="." unique_id=980404596]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -168.0
|
||||
offset_top = 55.0
|
||||
offset_right = 168.0
|
||||
offset_bottom = 110.0
|
||||
grow_horizontal = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="button" type="Button" parent="." unique_id=1264024304]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -40.0
|
||||
offset_top = -87.0
|
||||
offset_right = 42.0
|
||||
offset_bottom = -34.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_font_sizes/font_size = 32
|
||||
text = "Back"
|
||||
|
||||
[node name="player_listings" type="PlayerListings" parent="." unique_id=602626392]
|
||||
player_listing_scene = ExtResource("1_k8op7")
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -88.0
|
||||
offset_top = 150.0
|
||||
offset_right = 87.5
|
||||
offset_bottom = 167.0
|
||||
grow_horizontal = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="simple_score_view" type="SimpleScoreView" parent="." unique_id=1625972988]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = 93.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 133.0
|
||||
grow_horizontal = 2
|
||||
@ -15,6 +15,7 @@ use crate::{
|
||||
DamageSource, IPlayer, NetTransform, ball::Ball, remote_player::RemotePlayer,
|
||||
weapon::WeaponType,
|
||||
},
|
||||
team_resource::TeamResource,
|
||||
};
|
||||
|
||||
pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal";
|
||||
@ -22,9 +23,12 @@ pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal";
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Node, init)]
|
||||
pub struct StandaloneReplayManager {
|
||||
#[export]
|
||||
replay_finished_screen: Option<Gd<PackedScene>>,
|
||||
|
||||
loading_replay: bool,
|
||||
map: Option<Gd<Map>>,
|
||||
playback: Option<Gd<ReplayPlayback>>,
|
||||
pub playback: Option<Gd<ReplayPlayback>>,
|
||||
replay: Option<Replay>,
|
||||
|
||||
base: Base<Node>,
|
||||
@ -63,6 +67,12 @@ impl StandaloneReplayManager {
|
||||
|
||||
if let Some(replay) = self.replay.take() {
|
||||
let mut playback = ReplayPlayback::new_alloc();
|
||||
|
||||
playback
|
||||
.signals()
|
||||
.on_finished()
|
||||
.connect_other(self, Self::on_done);
|
||||
|
||||
playback.bind_mut().replay = replay;
|
||||
playback.bind_mut().current_map = Some(map);
|
||||
playback.bind_mut().current_time = 0.;
|
||||
@ -73,6 +83,20 @@ impl StandaloneReplayManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_done(&mut self) {
|
||||
if let Some(scene) = &self.replay_finished_screen {
|
||||
self.base().get_tree().change_scene_to_packed(scene);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.replay.take();
|
||||
self.map.take();
|
||||
if let Some(mut playback) = self.playback.take() {
|
||||
playback.queue_free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GodotClass)]
|
||||
@ -90,12 +114,22 @@ pub struct ReplayPlayback {
|
||||
|
||||
pub fast_forwarding: bool,
|
||||
|
||||
pub finished: bool,
|
||||
|
||||
base: Base<Node>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ReplayPlayback {
|
||||
#[signal]
|
||||
pub fn on_finished();
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode for ReplayPlayback {
|
||||
fn ready(&mut self) {}
|
||||
fn ready(&mut self) {
|
||||
self.finished = false;
|
||||
}
|
||||
|
||||
fn process(&mut self, _: f64) {
|
||||
if let Some(character) = self.player_characters.get_mut(&self.spectated_player_id) {
|
||||
@ -226,7 +260,8 @@ impl INode for ReplayPlayback {
|
||||
|
||||
self.fast_forwarding = false;
|
||||
|
||||
if self.current_frame_idx >= self.replay.frames.len() {
|
||||
if !self.finished && self.current_frame_idx >= self.replay.frames.len() {
|
||||
self.finished = true;
|
||||
for (id, mut character) in self.player_characters.clone() {
|
||||
character.queue_free();
|
||||
self.player_characters.remove(&id);
|
||||
@ -234,6 +269,7 @@ impl INode for ReplayPlayback {
|
||||
if let Some(mut ball) = self.ball.take() {
|
||||
ball.queue_free();
|
||||
}
|
||||
self.signals().on_finished().emit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,11 +313,21 @@ impl INode for ReplayRecorder {
|
||||
id: player.id(),
|
||||
name: player.data.name.clone(),
|
||||
color: team.bind().color.into(),
|
||||
team: player.data.team,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
s.replay.map_idx = game.bind().selected_map_idx;
|
||||
s.replay.teams = game
|
||||
.bind()
|
||||
.get_teams()
|
||||
.iter_shared()
|
||||
.map(|t| ReplayTeam {
|
||||
name: t.bind().name.to_string(),
|
||||
color: t.bind().color.into(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -324,6 +370,8 @@ impl ReplayRecorder {
|
||||
|
||||
let mut ball_data = ReplayBall::Held;
|
||||
|
||||
let mut goals = HashMap::new();
|
||||
|
||||
if let Some(game) = Game::singleton() {
|
||||
for player in &game.bind().players {
|
||||
if let Some(state) = player.get_state() {
|
||||
@ -338,10 +386,15 @@ impl ReplayRecorder {
|
||||
} else {
|
||||
ReplayBall::Held
|
||||
};
|
||||
|
||||
goals = game.bind().goals.clone();
|
||||
}
|
||||
|
||||
self.replay.goals = goals.clone();
|
||||
|
||||
ReplayFrame {
|
||||
time: self.time_elapsed,
|
||||
goals,
|
||||
player_data,
|
||||
ball_data,
|
||||
events: Vec::new(),
|
||||
@ -352,7 +405,9 @@ impl ReplayRecorder {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Replay {
|
||||
pub map_idx: u8,
|
||||
players: HashMap<u16, ReplayPlayer>,
|
||||
pub players: HashMap<u16, ReplayPlayer>,
|
||||
pub teams: Vec<ReplayTeam>,
|
||||
pub goals: HashMap<u8, u16>,
|
||||
frames: Vec<ReplayFrame>,
|
||||
}
|
||||
|
||||
@ -361,16 +416,25 @@ impl Default for Replay {
|
||||
Self {
|
||||
map_idx: Default::default(),
|
||||
players: Default::default(),
|
||||
goals: Default::default(),
|
||||
teams: Default::default(),
|
||||
frames: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplayTeam {
|
||||
pub name: String,
|
||||
pub color: ReplayColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplayPlayer {
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
pub color: ReplayColor,
|
||||
pub team: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -409,6 +473,7 @@ pub struct ReplayFrame {
|
||||
player_data: HashMap<u16, ReplayPlayerState>,
|
||||
ball_data: ReplayBall,
|
||||
events: Vec<ReplayEvent>,
|
||||
goals: HashMap<u8, u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@ -9,6 +9,7 @@ pub mod menu_player;
|
||||
pub mod net_stats;
|
||||
pub mod player_listing;
|
||||
pub mod player_listings;
|
||||
pub mod replay_finished_screen;
|
||||
pub mod settings;
|
||||
pub mod simple_score_view;
|
||||
pub mod slider;
|
||||
|
||||
@ -3,7 +3,7 @@ use godot::{
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::game_manager::Game;
|
||||
use crate::{game_manager::Game, replay::StandaloneReplayManager};
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=HBoxContainer, init)]
|
||||
@ -53,45 +53,64 @@ impl IHBoxContainer for PlayerListing {
|
||||
|
||||
impl PlayerListing {
|
||||
pub fn update(&mut self) {
|
||||
if let Some(game) = Game::singleton() {
|
||||
if let Some(player_id) = self.player_id
|
||||
&& let Some(player) = game.bind().find_player(player_id)
|
||||
{
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text(&player.data.name);
|
||||
}
|
||||
if let Some(kills_label) = &mut self.kills_label {
|
||||
kills_label.set_text(&player.data.kills.to_string());
|
||||
}
|
||||
if let Some(deaths_label) = &mut self.deaths_label {
|
||||
deaths_label.set_text(&player.data.deaths.to_string());
|
||||
}
|
||||
if let Some(ping_label) = &mut self.ping_label {
|
||||
ping_label.set_text(&player.data.ping.to_string());
|
||||
}
|
||||
if let Some(ping_icon) = &mut self.ping_icon {
|
||||
let (icon, color) = match player.data.ping {
|
||||
..100 => (self.ping_low.clone(), self.ping_low_color),
|
||||
100..200 => (self.ping_medium.clone(), self.ping_medium_color),
|
||||
200.. => (self.ping_high.clone(), self.ping_high_color),
|
||||
};
|
||||
if let Some(icon) = icon {
|
||||
ping_icon.set_texture(&icon);
|
||||
if let Some(player_id) = self.player_id {
|
||||
if let Some(game) = Game::singleton() {
|
||||
if let Some(player) = game.bind().find_player(player_id) {
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text(&player.data.name);
|
||||
}
|
||||
if let Some(kills_label) = &mut self.kills_label {
|
||||
kills_label.set_text(&player.data.kills.to_string());
|
||||
}
|
||||
if let Some(deaths_label) = &mut self.deaths_label {
|
||||
deaths_label.set_text(&player.data.deaths.to_string());
|
||||
}
|
||||
if let Some(ping_label) = &mut self.ping_label {
|
||||
ping_label.set_text(&player.data.ping.to_string());
|
||||
}
|
||||
if let Some(ping_icon) = &mut self.ping_icon {
|
||||
let (icon, color) = match player.data.ping {
|
||||
..100 => (self.ping_low.clone(), self.ping_low_color),
|
||||
100..200 => (self.ping_medium.clone(), self.ping_medium_color),
|
||||
200.. => (self.ping_high.clone(), self.ping_high_color),
|
||||
};
|
||||
if let Some(icon) = icon {
|
||||
ping_icon.set_texture(&icon);
|
||||
}
|
||||
ping_icon.set_modulate(color);
|
||||
}
|
||||
} else {
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text("Name");
|
||||
}
|
||||
if let Some(kills_label) = &mut self.kills_label {
|
||||
kills_label.set_text("Kills");
|
||||
}
|
||||
if let Some(deaths_label) = &mut self.deaths_label {
|
||||
deaths_label.set_text("Deaths");
|
||||
}
|
||||
if let Some(ping_label) = &mut self.ping_label {
|
||||
ping_label.set_text("Ping");
|
||||
}
|
||||
ping_icon.set_modulate(color);
|
||||
}
|
||||
} else {
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text("Name");
|
||||
}
|
||||
if let Some(kills_label) = &mut self.kills_label {
|
||||
kills_label.set_text("Kills");
|
||||
}
|
||||
if let Some(deaths_label) = &mut self.deaths_label {
|
||||
deaths_label.set_text("Deaths");
|
||||
}
|
||||
if let Some(ping_label) = &mut self.ping_label {
|
||||
ping_label.set_text("Ping");
|
||||
} else if let Some(playback) = &StandaloneReplayManager::singleton().bind().playback {
|
||||
if let Some(player) = playback.bind().replay.players.get(&player_id) {
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text(&player.name);
|
||||
}
|
||||
} else {
|
||||
if let Some(name_label) = &mut self.name_label {
|
||||
name_label.set_text("Name");
|
||||
}
|
||||
if let Some(kills_label) = &mut self.kills_label {
|
||||
kills_label.set_text("Kills");
|
||||
}
|
||||
if let Some(deaths_label) = &mut self.deaths_label {
|
||||
deaths_label.set_text("Deaths");
|
||||
}
|
||||
if let Some(ping_label) = &mut self.ping_label {
|
||||
ping_label.set_text("Ping");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@ use godot::{
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{game_manager::Game, ui::player_listing::PlayerListing};
|
||||
use crate::{
|
||||
game_manager::Game, replay::StandaloneReplayManager, ui::player_listing::PlayerListing,
|
||||
};
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=HBoxContainer, init)]
|
||||
@ -69,6 +71,40 @@ impl PlayerListings {
|
||||
}
|
||||
}
|
||||
|
||||
self.base_mut().add_child(&vbox);
|
||||
}
|
||||
} else if let Some(playback) = &StandaloneReplayManager::singleton().bind().playback {
|
||||
let replay = &playback.bind().replay;
|
||||
|
||||
for (id, team) in replay.teams.iter().enumerate() {
|
||||
let mut vbox = VBoxContainer::new_alloc();
|
||||
|
||||
vbox.set_alignment(AlignmentMode::BEGIN);
|
||||
vbox.set_custom_minimum_size(Vector2::new(300., 0.));
|
||||
|
||||
let mut team_label = Label::new_alloc();
|
||||
team_label.set_modulate(team.color.clone().into());
|
||||
team_label.set_text(&team.name);
|
||||
team_label.set_horizontal_alignment(HorizontalAlignment::CENTER);
|
||||
vbox.add_child(&team_label);
|
||||
|
||||
if let Some(listing_scene) = &self.player_listing_scene {
|
||||
let players = replay
|
||||
.players
|
||||
.iter()
|
||||
.filter(|(_, p)| p.team == id as u8)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let header = listing_scene.instantiate_as::<PlayerListing>();
|
||||
vbox.add_child(&header);
|
||||
|
||||
for (_, player) in players {
|
||||
let mut listing = listing_scene.instantiate_as::<PlayerListing>();
|
||||
listing.bind_mut().player_id = Some(player.id);
|
||||
vbox.add_child(&listing);
|
||||
}
|
||||
}
|
||||
|
||||
self.base_mut().add_child(&vbox);
|
||||
}
|
||||
}
|
||||
|
||||
97
rust/src/ui/replay_finished_screen.rs
Normal file
97
rust/src/ui/replay_finished_screen.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use godot::{
|
||||
classes::{Button, FileDialog, HBoxContainer, IPanel, Label, Panel},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
game_manager::{Game, GameManager},
|
||||
replay::StandaloneReplayManager,
|
||||
ui::player_listings::PlayerListings,
|
||||
};
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Panel, init)]
|
||||
pub struct ReplayFinishedScreen {
|
||||
#[export]
|
||||
title_container: Option<Gd<HBoxContainer>>,
|
||||
#[export]
|
||||
back_button: Option<Gd<Button>>,
|
||||
#[export]
|
||||
player_listings: Option<Gd<PlayerListings>>,
|
||||
|
||||
base: Base<Panel>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl IPanel for ReplayFinishedScreen {
|
||||
fn ready(&mut self) {
|
||||
self.produce_title();
|
||||
if let Some(listings) = &mut self.player_listings {
|
||||
listings.bind_mut().update_player_listings();
|
||||
}
|
||||
if let Some(back) = &self.back_button {
|
||||
back.signals().pressed().connect_other(self, |_| {
|
||||
StandaloneReplayManager::singleton().bind_mut().clear();
|
||||
GameManager::singleton().bind().go_to_main_menu();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ReplayFinishedScreen {
|
||||
fn produce_title(&mut self) {
|
||||
if let Some(playback) = &StandaloneReplayManager::singleton().bind().playback {
|
||||
let playback = playback.bind();
|
||||
godot_print!("{:?}", playback.replay.goals);
|
||||
let max_goals = playback
|
||||
.replay
|
||||
.goals
|
||||
.iter()
|
||||
.map(|(_, v)| v)
|
||||
.max()
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
let winning_team_ids = playback
|
||||
.replay
|
||||
.goals
|
||||
.iter()
|
||||
.filter(|(_, goals)| **goals == max_goals)
|
||||
.map(|(team, _)| *team)
|
||||
.collect::<Vec<_>>();
|
||||
let winning_teams = playback
|
||||
.replay
|
||||
.teams
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(id, _)| winning_team_ids.contains(&((*id) as u8)))
|
||||
.map(|(_, team)| team)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(container) = &mut self.title_container {
|
||||
for (idx, team) in winning_teams.iter().enumerate() {
|
||||
let mut label = Label::new_alloc();
|
||||
label.set_text(&team.name);
|
||||
label.set_modulate(team.color.clone().into());
|
||||
if idx > 0 {
|
||||
let mut and_label = Label::new_alloc();
|
||||
and_label.set_text(" and ");
|
||||
container.add_child(&and_label);
|
||||
}
|
||||
container.add_child(&label);
|
||||
}
|
||||
let mut end = Label::new_alloc();
|
||||
end.set_text(" victory!");
|
||||
container.add_child(&end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_replay(&mut self, path: GString) {
|
||||
if let Some(game) = &Game::singleton()
|
||||
&& let Some(recorder) = &game.bind().replay_recorder
|
||||
{
|
||||
recorder.bind().save_to(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user