Add playing replays from file
This commit is contained in:
parent
5687adbf8a
commit
8c0d5b99ff
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ rust/target
|
|||||||
target
|
target
|
||||||
|
|
||||||
dist
|
dist
|
||||||
|
*.replay
|
||||||
@ -23,6 +23,7 @@ GameManagerGlobal="*uid://bii6018k3ip3t"
|
|||||||
CliParserGlobal="*uid://c52o7xa0fnr53"
|
CliParserGlobal="*uid://c52o7xa0fnr53"
|
||||||
GameSettingsGlobal="*uid://bjr15hjiojecw"
|
GameSettingsGlobal="*uid://bjr15hjiojecw"
|
||||||
AnnouncerGlobal="*uid://5gey4i80mmsa"
|
AnnouncerGlobal="*uid://5gey4i80mmsa"
|
||||||
|
StandaloneReplayGlobal="*uid://djmb2jqcpkuep"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://dsxu0y8pjbw3k" path="res://scenes/ui/credits.tscn" id="1_sugp2"]
|
[ext_resource type="PackedScene" uid="uid://dsxu0y8pjbw3k" path="res://scenes/ui/credits.tscn" id="1_sugp2"]
|
||||||
[ext_resource type="PackedScene" uid="uid://n3u0jr4wiib1" path="res://scenes/ui/settings.tscn" id="2_0wfyh"]
|
[ext_resource type="PackedScene" uid="uid://n3u0jr4wiib1" path="res://scenes/ui/settings.tscn" id="2_0wfyh"]
|
||||||
|
|
||||||
[node name="menu" type="MainMenu" unique_id=2036763876 node_paths=PackedStringArray("camera", "main_position", "host_position", "main_panel", "host_panel", "join_panel", "host_port", "join_addr", "recent_servers_button", "popup_panel", "popup_title", "popup_text", "popup_button")]
|
[node name="menu" type="MainMenu" unique_id=2036763876 node_paths=PackedStringArray("camera", "main_position", "host_position", "main_panel", "host_panel", "join_panel", "host_port", "join_addr", "recent_servers_button", "popup_panel", "popup_title", "popup_text", "popup_button", "open_replay_dialog")]
|
||||||
camera = NodePath("camera_3d")
|
camera = NodePath("camera_3d")
|
||||||
main_position = NodePath("main_pos")
|
main_position = NodePath("main_pos")
|
||||||
host_position = NodePath("host_pos")
|
host_position = NodePath("host_pos")
|
||||||
@ -19,6 +19,7 @@ popup_title = NodePath("popup_panel/v_box_container/title")
|
|||||||
popup_text = NodePath("popup_panel/v_box_container/text")
|
popup_text = NodePath("popup_panel/v_box_container/text")
|
||||||
popup_button = NodePath("popup_panel/v_box_container/ok")
|
popup_button = NodePath("popup_panel/v_box_container/ok")
|
||||||
credits = ExtResource("1_sugp2")
|
credits = ExtResource("1_sugp2")
|
||||||
|
open_replay_dialog = NodePath("open_replay_dialog")
|
||||||
|
|
||||||
[node name="camera_3d" type="Camera3D" parent="." unique_id=1513321931]
|
[node name="camera_3d" type="Camera3D" parent="." unique_id=1513321931]
|
||||||
transform = Transform3D(0.98629165, -0.031186381, 0.16203775, -1.4054481e-08, 0.981978, 0.1889952, -0.16501158, -0.18640438, 0.9685167, 1.0974052, 1.7166122, 1.4909106)
|
transform = Transform3D(0.98629165, -0.031186381, 0.16203775, -1.4054481e-08, 0.981978, 0.1889952, -0.16501158, -0.18640438, 0.9685167, 1.0974052, 1.7166122, 1.4909106)
|
||||||
@ -74,6 +75,11 @@ layout_mode = 2
|
|||||||
text = "Join Game"
|
text = "Join Game"
|
||||||
metadata/_edit_lock_ = true
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
|
[node name="play_replay" type="Button" parent="main_panel/v_box_container" unique_id=275552647]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Play Replay"
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
[node name="credits" type="Button" parent="main_panel/v_box_container" unique_id=953921422]
|
[node name="credits" type="Button" parent="main_panel/v_box_container" unique_id=953921422]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Credits"
|
text = "Credits"
|
||||||
@ -245,8 +251,14 @@ autowrap_mode = 3
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Ok"
|
text = "Ok"
|
||||||
|
|
||||||
|
[node name="open_replay_dialog" type="FileDialog" parent="." unique_id=925465558]
|
||||||
|
title = "Open a File"
|
||||||
|
file_mode = 0
|
||||||
|
filters = PackedStringArray("*.replay")
|
||||||
|
|
||||||
[connection signal="pressed" from="main_panel/v_box_container/host" to="." method="go_to_host_game"]
|
[connection signal="pressed" from="main_panel/v_box_container/host" to="." method="go_to_host_game"]
|
||||||
[connection signal="pressed" from="main_panel/v_box_container/join" to="." method="go_to_join_game"]
|
[connection signal="pressed" from="main_panel/v_box_container/join" to="." method="go_to_join_game"]
|
||||||
|
[connection signal="pressed" from="main_panel/v_box_container/play_replay" to="." method="open_replay_dialog"]
|
||||||
[connection signal="pressed" from="main_panel/v_box_container/credits" to="." method="open_credits"]
|
[connection signal="pressed" from="main_panel/v_box_container/credits" to="." method="open_credits"]
|
||||||
[connection signal="pressed" from="main_panel/v_box_container/settings" to="settings" method="open"]
|
[connection signal="pressed" from="main_panel/v_box_container/settings" to="settings" method="open"]
|
||||||
[connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"]
|
[connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"]
|
||||||
|
|||||||
3
godot/scenes/misc/standalone_replay_global.tscn
Normal file
3
godot/scenes/misc/standalone_replay_global.tscn
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[gd_scene format=3 uid="uid://djmb2jqcpkuep"]
|
||||||
|
|
||||||
|
[node name="standalone_replay_global" type="StandaloneReplayManager" unique_id=730657724]
|
||||||
@ -6,7 +6,7 @@ use crate::{
|
|||||||
player::{
|
player::{
|
||||||
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
|
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
|
||||||
},
|
},
|
||||||
replay::{ReplayPlayer, ReplayPlayerState},
|
replay::{ReplayPlayer, ReplayPlayerState, StandaloneReplayManager},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
@ -25,6 +25,10 @@ impl INode3D for Map {
|
|||||||
fn ready(&mut self) {
|
fn ready(&mut self) {
|
||||||
if let Some(game) = &mut Game::singleton() {
|
if let Some(game) = &mut Game::singleton() {
|
||||||
game.bind_mut().on_map_loaded(self.to_gd());
|
game.bind_mut().on_map_loaded(self.to_gd());
|
||||||
|
} else {
|
||||||
|
StandaloneReplayManager::singleton()
|
||||||
|
.bind_mut()
|
||||||
|
.on_map_loaded(self.to_gd());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,12 @@ use std::{collections::HashMap, io::Cursor};
|
|||||||
use godot::{
|
use godot::{
|
||||||
classes::{FileAccess, file_access::ModeFlags},
|
classes::{FileAccess, file_access::ModeFlags},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
tools::get_autoload_by_name,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
game_manager::{Game, Player},
|
game_manager::{Game, GameManager, Player},
|
||||||
map::Map,
|
map::Map,
|
||||||
net::{network_manager::BallSync, util::NetVector3},
|
net::{network_manager::BallSync, util::NetVector3},
|
||||||
player::{
|
player::{
|
||||||
@ -16,6 +17,64 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const STANDALONE_REPLAY_GLOBAL: &str = "StandaloneReplayGlobal";
|
||||||
|
|
||||||
|
#[derive(GodotClass)]
|
||||||
|
#[class(base=Node, init)]
|
||||||
|
pub struct StandaloneReplayManager {
|
||||||
|
loading_replay: bool,
|
||||||
|
map: Option<Gd<Map>>,
|
||||||
|
playback: Option<Gd<ReplayPlayback>>,
|
||||||
|
replay: Option<Replay>,
|
||||||
|
|
||||||
|
base: Base<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StandaloneReplayManager {
|
||||||
|
pub fn singleton() -> Gd<StandaloneReplayManager> {
|
||||||
|
get_autoload_by_name::<StandaloneReplayManager>(STANDALONE_REPLAY_GLOBAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_replay(&mut self, path: &GString) {
|
||||||
|
if let Some(mut file) = FileAccess::open(path, ModeFlags::READ) {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
while !file.eof_reached() {
|
||||||
|
bytes.push(file.get_8());
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = Cursor::new(&mut bytes);
|
||||||
|
if let Ok(res) = ciborium::from_reader::<Replay, _>(bytes) {
|
||||||
|
self.loading_replay = true;
|
||||||
|
let map_idx = res.map_idx;
|
||||||
|
self.replay = Some(res);
|
||||||
|
if let Some(map) = GameManager::singleton().bind().maps.get(map_idx as usize) {
|
||||||
|
if let Some(scene) = &map.bind().scene {
|
||||||
|
self.base().get_tree().change_scene_to_packed(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_map_loaded(&mut self, map: Gd<Map>) {
|
||||||
|
if self.loading_replay {
|
||||||
|
self.loading_replay = false;
|
||||||
|
self.map = Some(map.clone());
|
||||||
|
|
||||||
|
if let Some(replay) = self.replay.take() {
|
||||||
|
let mut playback = ReplayPlayback::new_alloc();
|
||||||
|
playback.bind_mut().replay = replay;
|
||||||
|
playback.bind_mut().current_map = Some(map);
|
||||||
|
playback.bind_mut().current_time = 0.;
|
||||||
|
playback.bind_mut().fast_forwarding = false;
|
||||||
|
playback.bind_mut().spectated_player_id = 0;
|
||||||
|
self.playback = Some(playback.clone());
|
||||||
|
self.base_mut().add_child(&playback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(base=Node, init)]
|
#[class(base=Node, init)]
|
||||||
pub struct ReplayPlayback {
|
pub struct ReplayPlayback {
|
||||||
@ -292,7 +351,7 @@ impl ReplayRecorder {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Replay {
|
pub struct Replay {
|
||||||
map_idx: u8,
|
pub map_idx: u8,
|
||||||
players: HashMap<u16, ReplayPlayer>,
|
players: HashMap<u16, ReplayPlayer>,
|
||||||
frames: Vec<ReplayFrame>,
|
frames: Vec<ReplayFrame>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,20 @@
|
|||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
use godot::{
|
use godot::{
|
||||||
classes::{Button, Camera3D, Label, LineEdit, OptionButton, Panel, SpinBox},
|
classes::{
|
||||||
|
Button, Camera3D, FileAccess, FileDialog, Label, LineEdit, OptionButton, Panel, SpinBox,
|
||||||
|
file_access::ModeFlags,
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
register::property::SimpleVar,
|
register::property::SimpleVar,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
game_manager::GameManager,
|
||||||
game_settings::{GameSettingsManager, RecentServer},
|
game_settings::{GameSettingsManager, RecentServer},
|
||||||
net::network_manager::NetworkManager,
|
net::network_manager::NetworkManager,
|
||||||
popup_queue::{Popup, PopupQueue},
|
popup_queue::{Popup, PopupQueue},
|
||||||
|
replay::{Replay, StandaloneReplayManager},
|
||||||
ui::credits::CreditsMenu,
|
ui::credits::CreditsMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,6 +68,9 @@ pub struct MainMenu {
|
|||||||
#[export]
|
#[export]
|
||||||
credits: Option<Gd<PackedScene>>,
|
credits: Option<Gd<PackedScene>>,
|
||||||
|
|
||||||
|
#[export]
|
||||||
|
open_replay_dialog: Option<Gd<FileDialog>>,
|
||||||
|
|
||||||
prev_panel: Option<Gd<Panel>>,
|
prev_panel: Option<Gd<Panel>>,
|
||||||
|
|
||||||
recent_servers: Vec<RecentServer>,
|
recent_servers: Vec<RecentServer>,
|
||||||
@ -87,6 +97,12 @@ impl INode3D for MainMenu {
|
|||||||
.item_selected()
|
.item_selected()
|
||||||
.connect_other(self, |s, idx| s.on_recent_server_selected(idx));
|
.connect_other(self, |s, idx| s.on_recent_server_selected(idx));
|
||||||
}
|
}
|
||||||
|
if let Some(dialog) = &self.open_replay_dialog {
|
||||||
|
dialog
|
||||||
|
.signals()
|
||||||
|
.file_selected()
|
||||||
|
.connect_other(self, Self::on_open_replay);
|
||||||
|
}
|
||||||
self.hide_popup();
|
self.hide_popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +178,19 @@ impl INode3D for MainMenu {
|
|||||||
|
|
||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl MainMenu {
|
impl MainMenu {
|
||||||
|
#[func]
|
||||||
|
pub fn open_replay_dialog(&mut self) {
|
||||||
|
if let Some(dialog) = &mut self.open_replay_dialog {
|
||||||
|
dialog.popup_file_dialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_open_replay(&mut self, path: GString) {
|
||||||
|
StandaloneReplayManager::singleton()
|
||||||
|
.bind_mut()
|
||||||
|
.open_replay(&path);
|
||||||
|
}
|
||||||
|
|
||||||
#[func]
|
#[func]
|
||||||
pub fn host_game(&mut self) {
|
pub fn host_game(&mut self) {
|
||||||
if let Some(host_port) = &self.host_port {
|
if let Some(host_port) = &self.host_port {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user