Compare commits

..

No commits in common. "5b1f840e4023613084d706131c4895168a5fc1e4" and "b8578a0a95fe681c24b9b4f30636f46a692436bf" have entirely different histories.

26 changed files with 77 additions and 1301 deletions

3
.gitignore vendored
View File

@ -11,5 +11,4 @@ godot/maps/entity_models
rust/target
target
dist
*.replay
dist

1
Cargo.lock generated
View File

@ -200,7 +200,6 @@ dependencies = [
name = "quakeball"
version = "0.1.0"
dependencies = [
"ciborium",
"godot",
"serde",
"teanet",

View File

@ -2,10 +2,8 @@
[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/player/local_player.tscn" id="1_yjvui"]
[ext_resource type="PackedScene" uid="uid://kfxc0migw80o" path="res://scenes/player/remote_player.tscn" id="2_kcncr"]
[ext_resource type="PackedScene" uid="uid://hmh2lrrlo2wy" path="res://scenes/player/replay_player.tscn" id="3_8af6u"]
[ext_resource type="PackedScene" uid="uid://dfnbc8sbufa4j" path="res://scenes/misc/ball.tscn" id="3_e0nj3"]
[ext_resource type="PackedScene" uid="uid://c1r8nk58cci3a" path="res://scenes/misc/goal.tscn" id="4_w7til"]
[ext_resource type="PackedScene" uid="uid://dc17w5n6y1an" path="res://scenes/misc/replay_ball.tscn" id="5_oqkce"]
[sub_resource type="ImageTexture" id="ImageTexture_3vyb7"]
@ -26,9 +24,7 @@ spawners = [NodePath("team1_spawner"), NodePath("team2_spawner")]
ball_spawner = NodePath("ball_spawner")
local_player = ExtResource("1_yjvui")
remote_player = ExtResource("2_kcncr")
replay_player = ExtResource("3_8af6u")
ball = ExtResource("3_e0nj3")
replay_ball = ExtResource("5_oqkce")
[node name="terrain" type="MeshInstance3D" parent="." unique_id=1474127252]
material_override = SubResource("StandardMaterial3D_u8vuu")

View File

@ -23,7 +23,6 @@ GameManagerGlobal="*uid://bii6018k3ip3t"
CliParserGlobal="*uid://c52o7xa0fnr53"
GameSettingsGlobal="*uid://bjr15hjiojecw"
AnnouncerGlobal="*uid://5gey4i80mmsa"
StandaloneReplayGlobal="*uid://djmb2jqcpkuep"
[display]

View File

@ -2,11 +2,9 @@
[ext_resource type="PackedScene" uid="uid://2sfy0vq0fqt3" path="res://scenes/ui/player_listing.tscn" id="1_ou354"]
[node name="game_finished" type="GameFinishedScreen" unique_id=700430082 node_paths=PackedStringArray("title_container", "back_button", "player_listings", "save_file_dialog")]
[node name="game_finished" type="GameFinishedScreen" unique_id=700430082 node_paths=PackedStringArray("title_container", "back_button")]
title_container = NodePath("title_container")
back_button = NodePath("button")
player_listings = NodePath("player_listings")
save_file_dialog = NodePath("save_dialog")
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
@ -41,22 +39,6 @@ grow_vertical = 0
theme_override_font_sizes/font_size = 32
text = "Back"
[node name="save_replay" type="Button" parent="." unique_id=1199584793]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -534.0
offset_top = -87.0
offset_right = -342.0
offset_bottom = -34.0
grow_horizontal = 2
grow_vertical = 0
theme_override_font_sizes/font_size = 32
text = "Save Replay"
[node name="player_listings" type="PlayerListings" parent="." unique_id=602626392]
player_listing_scene = ExtResource("1_ou354")
layout_mode = 1
@ -80,8 +62,3 @@ offset_top = 93.0
offset_right = 20.0
offset_bottom = 133.0
grow_horizontal = 2
[node name="save_dialog" type="FileDialog" parent="." unique_id=1637714468]
filters = PackedStringArray("*.replay")
[connection signal="pressed" from="save_replay" to="." method="open_file_dialog"]

View File

@ -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://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", "open_replay_dialog")]
[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")]
camera = NodePath("camera_3d")
main_position = NodePath("main_pos")
host_position = NodePath("host_pos")
@ -19,7 +19,6 @@ popup_title = NodePath("popup_panel/v_box_container/title")
popup_text = NodePath("popup_panel/v_box_container/text")
popup_button = NodePath("popup_panel/v_box_container/ok")
credits = ExtResource("1_sugp2")
open_replay_dialog = NodePath("open_replay_dialog")
[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)
@ -75,11 +74,6 @@ layout_mode = 2
text = "Join Game"
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]
layout_mode = 2
text = "Credits"
@ -251,14 +245,8 @@ autowrap_mode = 3
layout_mode = 2
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/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/settings" to="settings" method="open"]
[connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"]

View File

@ -5,7 +5,7 @@
[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"]
[node name="ball" type="Ball" unique_id=816748600]
collision_layer = 8
collision_layer = 12
collision_mask = 7
contact_monitor = true
max_contacts_reported = 10

View File

@ -5,21 +5,11 @@
[ext_resource type="PackedScene" uid="uid://df84sctteh4kw" path="res://scenes/game_finished.tscn" id="1_qf2qp"]
[ext_resource type="MapResource" uid="uid://sc3507f75002" path="res://maps/diner.tres" id="2_glms7"]
[ext_resource type="PackedScene" uid="uid://lahirbqfppvw" path="res://scenes/main.tscn" id="3_7ele1"]
[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/player/local_player.tscn" id="4_3b0rc"]
[ext_resource type="PackedScene" uid="uid://kfxc0migw80o" path="res://scenes/player/remote_player.tscn" id="5_nxoss"]
[ext_resource type="MapResource" uid="uid://bsjoandgu476y" path="res://maps/two_towers.tres" id="5_rotn8"]
[ext_resource type="PackedScene" uid="uid://hmh2lrrlo2wy" path="res://scenes/player/replay_player.tscn" id="6_5fjlq"]
[ext_resource type="PackedScene" uid="uid://dfnbc8sbufa4j" path="res://scenes/misc/ball.tscn" id="7_cv0ql"]
[ext_resource type="PackedScene" uid="uid://dc17w5n6y1an" path="res://scenes/misc/replay_ball.tscn" id="8_m1inn"]
[node name="game_manager" type="GameManager" unique_id=1442044769]
respawn_timer = 10.0
lobby_screen = ExtResource("1_3ama4")
game_finished_screen = ExtResource("1_qf2qp")
main_menu = ExtResource("3_7ele1")
local_player = ExtResource("4_3b0rc")
remote_player = ExtResource("5_nxoss")
replay_player = ExtResource("6_5fjlq")
ball = ExtResource("7_cv0ql")
replay_ball = ExtResource("8_m1inn")
maps = Array[MapResource]([ExtResource("1_glms7"), ExtResource("2_glms7"), ExtResource("5_rotn8")])

View File

@ -1,16 +0,0 @@
[gd_scene format=3 uid="uid://dc17w5n6y1an"]
[ext_resource type="PackedScene" uid="uid://r13poamopmnv" path="res://mesh/ball_mesh.tscn" id="1_ishne"]
[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"]
[node name="replay_ball" type="Ball" unique_id=816748600]
collision_layer = 8
collision_mask = 4
contact_monitor = true
max_contacts_reported = 10
[node name="mesh_instance_3d" parent="." unique_id=37904686 instance=ExtResource("1_ishne")]
[node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1408432914]
shape = SubResource("SphereShape3D_1cgct")

View File

@ -1,6 +0,0 @@
[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")

View File

@ -1,98 +0,0 @@
[gd_scene format=3 uid="uid://hmh2lrrlo2wy"]
[ext_resource type="PackedScene" uid="uid://bod6b3befmyt5" path="res://scenes/player/soldier_m.tscn" id="1_bfcd3"]
[ext_resource type="PackedScene" uid="uid://cl66unms2gim0" path="res://scenes/player/effects.tscn" id="2_rejks"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1cgct"]
[sub_resource type="ViewportTexture" id="ViewportTexture_1cgct"]
viewport_path = NodePath("health_bar_sprite/health_bar_viewport")
[sub_resource type="Curve" id="Curve_yli5f"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_3g557"]
width = 70
curve = SubResource("Curve_yli5f")
[sub_resource type="Curve" id="Curve_dmbod"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_1cgct"]
width = 70
curve = SubResource("Curve_dmbod")
[sub_resource type="Curve" id="Curve_qn18y"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_840kl"]
width = 70
curve = SubResource("Curve_qn18y")
[node name="player" type="RemotePlayer" unique_id=845968872 node_paths=PackedStringArray("soldier_mesh", "collider", "effects", "spectator_camera", "camera_origin", "name_label", "health_bar", "health_bar_sprite")]
move_speed = 5.0
soldier_mesh = NodePath("soldier_m")
collider = NodePath("collision_shape_3d")
effects = NodePath("effects")
health = 100
punch_cooldown = 1.0
spectator_camera = NodePath("spectate_root/spectate_camera")
camera_origin = NodePath("camera_origin")
name_label = NodePath("name_label")
health_bar = NodePath("health_bar_sprite/health_bar_viewport/health_bar")
health_bar_sprite = NodePath("health_bar_sprite")
collision_layer = 16
collision_mask = 4
[node name="soldier_m" parent="." unique_id=281995630 instance=ExtResource("1_bfcd3")]
transform = Transform3D(-0.5, 0, -4.371139e-08, 0, 0.5, 0, 4.371139e-08, 0, -0.5, 0, 0, 0)
[node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1110995513]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CapsuleShape3D_1cgct")
[node name="effects" parent="." unique_id=986677517 instance=ExtResource("2_rejks")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4540546, 0)
[node name="spectate_root" type="Node3D" parent="." unique_id=624336254]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.7611488, 0)
[node name="spectate_camera" type="Camera3D" parent="spectate_root" unique_id=1408688016]
transform = Transform3D(1, 0, 0, 0, 0.89379096, 0.44848388, 0, -0.44848388, 0.89379096, 0, 0.6601318, 1.0407685)
[node name="camera_origin" type="Node3D" parent="." unique_id=1445680156]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8724979, -0.2644562)
[node name="name_label" type="Label3D" parent="." unique_id=142179925]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.3304863, 0)
pixel_size = 0.002
billboard = 1
text = "asd"
font_size = 64
[node name="health_bar_sprite" type="Sprite3D" parent="." unique_id=1478315510]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.1606798, 0)
billboard = 1
texture = SubResource("ViewportTexture_1cgct")
[node name="health_bar_viewport" type="SubViewport" parent="health_bar_sprite" unique_id=1819065918]
disable_3d = true
transparent_bg = true
size = Vector2i(70, 10)
size_2d_override = Vector2i(70, 10)
size_2d_override_stretch = true
[node name="health_bar" type="TextureProgressBar" parent="health_bar_sprite/health_bar_viewport" unique_id=121411216]
offset_right = 70.0
offset_bottom = 1.0
scale = Vector2(1, 5)
value = 100.0
texture_under = SubResource("CurveTexture_3g557")
texture_over = SubResource("CurveTexture_1cgct")
texture_progress = SubResource("CurveTexture_840kl")
tint_under = Color(0, 0, 0, 0.3647059)
tint_over = Color(1, 1, 1, 0)
tint_progress = Color(0.5660922, 1.7688051e-06, 3.3691526e-07, 1)

View File

@ -1,65 +0,0 @@
[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

View File

@ -7,7 +7,6 @@ edition = "2024"
crate-type = ["cdylib"] # Compile this crate to a dynamic C library.
[dependencies]
ciborium = "0.2.2"
godot = "0.5.4"
serde = "1.0.228"
teanet = { path = "../teanet-rust" }

View File

@ -23,7 +23,6 @@ use crate::{
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer,
remote_player::RemotePlayer, weapon::WeaponType,
},
replay::{ReplayEvent, ReplayPlayback, ReplayRecorder},
team_resource::TeamResource,
ui::cli::{Command, CommandLinePanel},
};
@ -42,22 +41,6 @@ pub struct GameManager {
#[export]
pub main_menu: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub local_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub remote_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub replay_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub ball: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub replay_ball: Option<Gd<PackedScene>>,
pub game: Option<Gd<Game>>,
base: Base<Node>,
@ -246,9 +229,6 @@ pub struct Game {
pub goals: HashMap<u8, u16>,
pub replay_recorder: Option<Gd<ReplayRecorder>>,
pub current_replay_playback: Option<Gd<ReplayPlayback>>,
base: Base<Node>,
}
@ -332,12 +312,6 @@ impl INode for Game {
player.killing_spree = 0;
}
}
if let Some(playback) = &mut self.current_replay_playback
&& let Some(recorder) = &self.replay_recorder
{
playback.bind_mut().replay = recorder.bind().replay.clone();
}
}
}
@ -363,49 +337,9 @@ impl Game {
pub fn on_player_killed(player: u16);
pub fn singleton() -> Option<Gd<Game>> {
if GameManager::singleton().get_children().len() > 0 {
GameManager::singleton()
.get_child(0)
.map(|g| g.cast::<Game>())
} else {
None
}
}
pub fn start_playback(&mut self, time_delta: f64, spectated_id: u16) {
if let Some(recorder) = &self.replay_recorder {
let mut playback = ReplayPlayback::new_alloc();
playback.bind_mut().replay = recorder.bind().replay.clone();
playback.bind_mut().current_map = self.current_map.clone();
playback.bind_mut().current_time = (recorder.bind().time_elapsed + time_delta).max(0.);
playback.bind_mut().fast_forwarding = true;
playback.bind_mut().spectated_player_id = spectated_id;
self.current_replay_playback = Some(playback.clone());
self.base_mut().add_child(&playback);
}
for player in &mut self.players {
if let Some(character) = &mut player.character {
character.set_visible(false);
}
}
if let Some(ball) = &mut self.world_ball {
ball.set_visible(false);
}
}
pub fn stop_playback(&mut self) {
if let Some(mut playback) = self.current_replay_playback.take() {
playback.queue_free();
}
for player in &mut self.players {
if let Some(character) = &mut player.character {
character.set_visible(true);
}
}
if let Some(ball) = &mut self.world_ball {
ball.set_visible(true);
}
GameManager::singleton()
.get_child(0)
.map(|g| g.cast::<Game>())
}
pub fn start_game(&mut self) {
@ -428,17 +362,9 @@ impl Game {
}
}
});
let recorder = ReplayRecorder::new_alloc();
self.replay_recorder = Some(recorder.clone());
self.base_mut().add_child(&recorder);
}
pub fn reset_game(&mut self) {
if let Some(mut recorder) = self.replay_recorder.take() {
recorder.queue_free();
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
&& let Some(player) = self.find_player_mut(self.self_id.unwrap_or(0))
@ -454,10 +380,6 @@ impl Game {
}
pub fn finish_game(&mut self) {
if let Some(recorder) = &mut self.replay_recorder {
recorder.bind_mut().stop_recording();
}
self.game_started = false;
for player in &mut self.players {
@ -827,30 +749,18 @@ impl Game {
}
pub fn spawn_player_server(&mut self, id: u16) -> Option<DynGd<CharacterBody3D, dyn IPlayer>> {
if id == self.self_id.unwrap_or(0) {
self.stop_playback();
}
if let Some(player) = self.find_player(id)
&& let Some(mut character) = player.character.clone()
if let Some(player) = self.find_player_mut(id)
&& let Some(character) = &mut player.character
{
// This is a respawn, remove old character
character.queue_free();
if let Some(mut recorder) = self.replay_recorder.clone()
&& let Some(state) = player.get_state()
{
recorder
.bind_mut()
.record_event(ReplayEvent::Respawn(id, state));
}
}
if let Some(mut map) = self.current_map.clone() {
let team = self.find_player(id).map(|p| p.data.team).unwrap_or(0);
let mut character =
map.bind_mut()
.spawn_player(id, team, id == self.self_id.unwrap_or(0), None, true);
.spawn_player(id, team, id == self.self_id.unwrap_or(0), None);
if let Some(character) = &mut character {
if let Some(player) = self.find_player(id) {
@ -908,23 +818,11 @@ impl Game {
// Method called on the client-side whenever a player has spawned
pub fn spawn_player_client(&mut self, id: u16, transform: NetTransform) {
if id == self.self_id.unwrap_or(0) {
self.stop_playback();
}
if let Some(map) = &mut self.current_map {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == id) {
if let Some(character) = &mut player.character {
// This is a respawn, delete old character
character.queue_free();
if let Some(mut recorder) = self.replay_recorder.clone()
&& let Some(state) = player.get_state()
{
recorder
.bind_mut()
.record_event(ReplayEvent::Respawn(id, state));
}
}
let mut character = map.bind_mut().spawn_player(
@ -932,7 +830,6 @@ impl Game {
player.data.team,
player.id() == self.self_id.unwrap_or(0),
Some(transform),
true,
);
if let Some(character) = &mut character {
character
@ -989,11 +886,6 @@ impl Game {
// Handle a jump, either from the server or from a client
pub fn handle_jump(&mut self, player_id: u16) {
if player_id != self.self_id.unwrap_or(0) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Jump(player_id));
}
if let Some(player) = self.find_player_mut(player_id) {
player.apply_jump();
}
@ -1010,13 +902,8 @@ impl Game {
deal_damage: bool,
) -> bool {
let self_id = self.self_id.clone().unwrap_or(0);
if player_id != self_id {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Shoot(player_id, to.into()));
}
if let Some(player) = self.find_player_mut(player_id) {
if let Some(player) = self.find_player_mut(player_id) {
if player.id() != self_id {
player.try_shoot(to, transform, look_up, deal_damage)
} else {
false
@ -1036,13 +923,8 @@ impl Game {
deal_damage: bool,
) -> bool {
let self_id = self.self_id.clone().unwrap_or(0);
if player_id != self_id {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Punch(player_id, to.into()));
}
if let Some(player) = self.find_player_mut(player_id) {
if let Some(player) = self.find_player_mut(player_id) {
if player.id() != self_id {
player.try_punch(to, transform, look_up, deal_damage)
} else {
false
@ -1054,12 +936,6 @@ impl Game {
// Handle for when a player is killed, run both on client and server
pub fn on_kill(&mut self, source: DamageSource, player_id: u16) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Death(source, player_id));
}
if let DamageSource::Player(source_player_id) = source
&& let Some(player) = self.find_player(source_player_id)
&& let Some(character) = player.character.clone()
@ -1150,12 +1026,6 @@ impl Game {
// Handle taking damage, from server to client
pub fn handle_take_damage(&mut self, source: DamageSource, player_id: u16, damage: i32) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::TakeDamage(player_id, source, damage));
}
if let Some(player) = self.find_player_mut(player_id) {
if let Some(character) = &mut player.character {
character.dyn_bind_mut().take_damage(source, damage, false);
@ -1175,14 +1045,6 @@ impl Game {
// Called whenever a player takes damage on the server-side
pub fn on_take_damage(&mut self, source: DamageSource, target_player_id: u16, damage: i32) {
if let Some(recorder) = &mut self.replay_recorder {
recorder.bind_mut().record_event(ReplayEvent::TakeDamage(
target_player_id,
source,
damage,
));
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(TakeDamage(source, target_player_id, damage));
@ -1199,10 +1061,6 @@ impl Game {
}
}
}
pub fn recorder(&mut self) -> &mut Option<Gd<ReplayRecorder>> {
&mut self.replay_recorder
}
}
#[derive(Clone, Debug)]

View File

@ -13,7 +13,6 @@ pub mod map_resource;
pub mod net;
pub mod player;
pub mod popup_queue;
pub mod replay;
pub mod team_resource;
pub mod ui;

View File

@ -1,12 +1,11 @@
use godot::{classes::CharacterBody3D, prelude::*};
use crate::{
game_manager::{Game, GameManager},
game_manager::Game,
net::util::NetVector3,
player::{
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
},
replay::{ReplayPlayer, ReplayPlayerState, StandaloneReplayManager},
};
#[derive(GodotClass)]
@ -16,6 +15,12 @@ pub struct Map {
pub spawners: Array<Gd<Node3D>>,
#[export]
pub ball_spawner: Option<Gd<Node3D>>,
#[export]
pub local_player: Option<Gd<PackedScene>>,
#[export]
pub remote_player: Option<Gd<PackedScene>>,
#[export]
pub ball: Option<Gd<PackedScene>>,
base: Base<Node3D>,
}
@ -25,10 +30,6 @@ impl INode3D for Map {
fn ready(&mut self) {
if let Some(game) = &mut Game::singleton() {
game.bind_mut().on_map_loaded(self.to_gd());
} else {
StandaloneReplayManager::singleton()
.bind_mut()
.on_map_loaded(self.to_gd());
}
}
}
@ -44,10 +45,9 @@ impl Map {
team: u8,
is_local: bool,
transform: Option<NetTransform>,
get_remote_data: bool,
) -> Option<DynGd<CharacterBody3D, dyn IPlayer + 'static>> {
if is_local {
if let Some(local_player) = &GameManager::singleton().bind().local_player {
if let Some(local_player) = &self.local_player {
let mut node = local_player.instantiate_as::<LocalPlayer>();
node.bind_mut().player_id = Some(player_id);
self.base_mut().add_child(&node);
@ -69,22 +69,9 @@ impl Map {
} else {
None
}
} else if let Some(remote_player) = &GameManager::singleton().bind().remote_player {
} else if let Some(remote_player) = &self.remote_player {
let mut node = remote_player.instantiate_as::<RemotePlayer>();
node.bind_mut().player_id = Some(player_id);
if get_remote_data {
let mut node_clone = node.clone();
self.run_deferred(move |_| {
if let Some(game) = &mut Game::singleton()
&& let Some(player) = game.bind().find_player(player_id)
&& let Some(team) = game.bind().get_team(player.data.team)
{
node_clone.bind_mut().player_name = player.data.name.clone();
node_clone.bind_mut().player_color = team.bind().color.clone();
}
});
}
self.base_mut().add_child(&node);
if let Some(transform) = transform {
node.bind_mut()
@ -106,34 +93,6 @@ impl Map {
}
}
pub fn spawn_replay_player(
&mut self,
player_id: u16,
player: &ReplayPlayer,
state: ReplayPlayerState,
) -> Option<Gd<RemotePlayer>> {
if let Some(replay_player) = &GameManager::singleton().bind().replay_player {
let mut node: Gd<RemotePlayer> = replay_player.instantiate_as();
node.bind_mut().player_id = Some(player_id);
node.bind_mut().player_name = player.name.clone();
node.bind_mut().player_color = player.color.clone().into();
self.base_mut().add_child(&node);
node.bind_mut()
.base_mut()
.set_global_position(state.transform.location.into());
node.bind_mut()
.base_mut()
.set_global_rotation(state.transform.rotation.into());
node.bind_mut().set_movement_dir(state.movement_dir.into());
node.bind_mut().apply_look_up(state.look_up);
Some(node)
} else {
None
}
}
pub fn spawn_ball(
&mut self,
transform: Option<NetTransform>,
@ -141,34 +100,7 @@ impl Map {
player_id: Option<u16>,
) -> Option<Gd<Ball>> {
if let Some(ball_spawner) = self.ball_spawner.clone()
&& let Some(ball_prefab) = &GameManager::singleton().bind().ball
{
let mut ball = ball_prefab.instantiate_as::<Ball>();
ball.bind_mut().dropper = player_id;
self.base_mut().add_child(&ball);
if let Some(transform) = transform {
ball.set_global_position(transform.location.into());
ball.set_global_rotation(transform.rotation.into());
} else {
ball.set_global_position(ball_spawner.get_global_position());
}
if let Some(velocity) = velocity {
ball.set_linear_velocity(velocity.into());
}
Some(ball)
} else {
None
}
}
pub fn spawn_replay_ball(
&mut self,
transform: Option<NetTransform>,
velocity: Option<NetVector3>,
player_id: Option<u16>,
) -> Option<Gd<Ball>> {
if let Some(ball_spawner) = self.ball_spawner.clone()
&& let Some(ball_prefab) = &GameManager::singleton().bind().replay_ball
&& let Some(ball_prefab) = &self.ball
{
let mut ball = ball_prefab.instantiate_as::<Ball>();
ball.bind_mut().dropper = player_id;

View File

@ -29,7 +29,6 @@ use crate::{
soldier_mesh::SoldierMesh,
weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType},
},
replay::ReplayEvent,
ui::{chatbox::ChatBox, cli::CommandLinePanel, settings::SettingsPanel},
};
@ -143,13 +142,6 @@ impl IPlayer for LocalPlayer {
fn jump(&mut self) {
if self.try_jump() {
if let Some(game) = &mut Game::singleton()
&& let Some(recorder) = game.bind_mut().recorder()
{
recorder
.bind_mut()
.record_event(ReplayEvent::Jump(self.player_id.unwrap_or(0)));
}
if let Some(effects) = &mut self.effects {
effects.bind_mut().jump();
}
@ -171,18 +163,7 @@ impl IPlayer for LocalPlayer {
fn apply_look_up(&mut self, _look: f32) {}
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool {
if self.try_shoot_common(to, deal_damage) {
if let Some(game) = &mut Game::singleton()
&& let Some(recorder) = game.bind_mut().recorder()
{
recorder
.bind_mut()
.record_event(ReplayEvent::Shoot(self.player_id.unwrap_or(0), to.into()));
}
true
} else {
false
}
self.try_shoot_common(to, deal_damage)
}
fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool {
@ -192,18 +173,7 @@ impl IPlayer for LocalPlayer {
weapon.set_rotation(Vector3::UP);
}
self.punch_cd_remaining = self.punch_cooldown;
if self.try_punch_common(to, deal_damage) {
if let Some(game) = &mut Game::singleton()
&& let Some(recorder) = game.bind_mut().recorder()
{
recorder
.bind_mut()
.record_event(ReplayEvent::Punch(self.player_id.unwrap_or(0), to.into()));
}
true
} else {
false
}
self.try_punch_common(to, deal_damage)
} else {
false
}
@ -380,10 +350,10 @@ impl ICharacterBody3D for LocalPlayer {
if let Some(camera) = &self.camera {
if camera.is_current() && self.dead_for > 3. {
if let Some(mut game) = Game::singleton()
if let Some(game) = Game::singleton()
&& let Some(killer_id) = self.killer_id
{
let mut game = game.bind_mut();
let game = game.bind();
let remote_player = game.find_player(killer_id);
if let Some(remote_player) = &remote_player
&& let Some(remote_character) = remote_player.character.clone()
@ -393,8 +363,6 @@ impl ICharacterBody3D for LocalPlayer {
{
camera.set_current(true);
}
game.start_playback(-5., killer_id);
}
}
}

View File

@ -46,8 +46,6 @@ pub struct RemotePlayer {
health_bar_sprite: Option<Gd<Sprite3D>>,
pub player_id: Option<u16>,
pub player_name: String,
pub player_color: Color,
movement_dir: Vector3,
y_speed: f32,
@ -229,9 +227,14 @@ impl IPlayer for RemotePlayer {
impl ICharacterBody3D for RemotePlayer {
fn ready(&mut self) {
self.run_deferred(|s| {
if let Some(name_label) = &mut s.name_label {
name_label.set_text(&s.player_name);
name_label.set_modulate(s.player_color);
if let Some(player_id) = s.player_id
&& let Some(game) = Game::singleton()
&& let Some(player) = game.bind().find_player(player_id)
&& let Some(team) = game.bind().get_team(player.data.team)
&& let Some(name_label) = &mut s.name_label
{
name_label.set_text(&player.data.name);
name_label.set_modulate(team.bind().color);
}
});

View File

@ -11,7 +11,6 @@ use crate::{
game_settings::{GameSettingsManager, VolumeSettings},
net::util::{cast_ray, shotgun_ray},
player::{DamageSource, shootable::Shootable},
replay::StandaloneReplayManager,
};
pub trait Weapon {
@ -95,8 +94,6 @@ impl Weapon for Raygun {
.map(|r| r.position)
.unwrap_or(source.get_global_position() + dir * 100.);
godot_print!("from {} to {}", source.get_global_position(), beam_end);
if let Some(prefab) = &self.bullet_prefab {
let mut bullet = prefab.instantiate_as::<Beam>();
@ -108,11 +105,6 @@ impl Weapon for Raygun {
&& let Some(map) = &mut game.bind_mut().current_map
{
map.add_child(&bullet);
} else if let Some(playback) =
&mut StandaloneReplayManager::singleton().bind_mut().playback
&& let Some(map) = &mut playback.bind_mut().current_map
{
map.add_child(&bullet);
}
});
}

View File

@ -1,530 +0,0 @@
use std::{collections::HashMap, io::Cursor};
use godot::{
classes::{FileAccess, file_access::ModeFlags},
prelude::*,
tools::get_autoload_by_name,
};
use serde::{Deserialize, Serialize};
use crate::{
game_manager::{Game, GameManager, Player},
map::Map,
net::{network_manager::BallSync, util::NetVector3},
player::{
DamageSource, IPlayer, NetTransform, ball::Ball, remote_player::RemotePlayer,
weapon::WeaponType,
},
team_resource::TeamResource,
};
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>>,
pub 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
.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.;
playback.bind_mut().fast_forwarding = false;
playback.bind_mut().spectated_player_id = 0;
self.playback = Some(playback.clone());
self.base_mut().add_child(&playback);
}
}
}
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)]
#[class(base=Node, init)]
pub struct ReplayPlayback {
pub replay: Replay,
pub current_map: Option<Gd<Map>>,
current_frame_idx: usize,
player_characters: HashMap<u16, Gd<RemotePlayer>>,
ball: Option<Gd<Ball>>,
pub current_time: f64,
pub spectated_player_id: u16,
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) {
self.finished = false;
}
fn process(&mut self, _: f64) {
if let Some(character) = self.player_characters.get_mut(&self.spectated_player_id) {
if let Some(camera) = &mut character.bind_mut().spectator_camera {
if !camera.is_current() {
camera.make_current();
}
}
}
}
fn physics_process(&mut self, delta: f64) {
self.current_time += delta;
while let Some(frame) = self.replay.frames.get(self.current_frame_idx)
&& frame.time <= self.current_time
{
if let ReplayBall::World(ball_state) = &frame.ball_data {
if let Some(ball) = &mut self.ball {
ball.bind_mut().apply_sync(BallSync {
transform: ball_state.transform,
velocity: ball_state.velocity,
});
} else if let Some(map) = &mut self.current_map {
if let Some(ball) = map.bind_mut().spawn_replay_ball(
ball_state.transform.into(),
ball_state.velocity.into(),
None,
) {
self.ball = Some(ball);
}
}
} else {
if let Some(mut ball) = self.ball.take() {
ball.queue_free();
}
}
if !self.fast_forwarding {
if let Some(map) = &mut self.current_map {
for (id, data) in &frame.player_data {
if !self.player_characters.contains_key(id)
&& !data.is_dead
&& let Some(player) = self.replay.players.get(id)
{
if let Some(player) =
map.bind_mut()
.spawn_replay_player(*id, player, data.clone())
{
self.player_characters.insert(*id, player);
}
}
}
}
for event in &frame.events {
match event {
ReplayEvent::Death(damage_source, player_id) => {
if let Some(character) = self.player_characters.get_mut(player_id) {
character.bind_mut().kill(*damage_source);
}
}
ReplayEvent::Shoot(player_id, to) => {
if let Some(character) = self.player_characters.get_mut(player_id) {
character.bind_mut().try_shoot((*to).into(), false);
}
}
ReplayEvent::Respawn(player_id, data) => {
if let Some(mut character) = self.player_characters.remove(player_id) {
character.queue_free();
}
if let Some(map) = &mut self.current_map
&& let Some(player) = self.replay.players.get(player_id)
{
if let Some(player) = map.bind_mut().spawn_replay_player(
*player_id,
player,
data.clone(),
) {
self.player_characters.insert(*player_id, player);
}
}
}
ReplayEvent::Jump(player_id) => {
if let Some(character) = self.player_characters.get_mut(player_id) {
character.bind_mut().jump();
}
}
ReplayEvent::Punch(player_id, to) => {
if let Some(character) = self.player_characters.get_mut(player_id) {
character.bind_mut().try_punch((*to).into(), false);
}
}
ReplayEvent::TakeDamage(player_id, damage_source, damage) => {
if let Some(character) = self.player_characters.get_mut(player_id) {
character
.bind_mut()
.take_damage(*damage_source, *damage, false);
}
}
}
}
}
for (id, mut character) in self.player_characters.clone() {
if let Some(state) = frame.player_data.get(&id) {
character.bind_mut().apply_transform(state.transform);
character.bind_mut().apply_look_up(state.look_up);
character
.bind_mut()
.set_movement_dir(state.movement_dir.into());
let weapon = character.bind().get_weapon().clone();
if let Some(weapon) = weapon
&& weapon.dyn_bind().get_type() != state.current_weapon
{
character
.bind_mut()
.swap_weapon(state.current_weapon, false);
}
} else {
character.queue_free();
self.player_characters.remove(&id);
}
}
self.current_frame_idx += 1;
}
self.fast_forwarding = false;
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);
}
if let Some(mut ball) = self.ball.take() {
ball.queue_free();
}
self.signals().on_finished().emit();
}
}
fn exit_tree(&mut self) {
for (_, character) in &mut self.player_characters {
character.queue_free();
}
self.player_characters.clear();
if let Some(mut ball) = self.ball.take() {
ball.queue_free();
}
}
}
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct ReplayRecorder {
pub time_elapsed: f64,
pub replay: Replay,
current_frame: ReplayFrame,
recording: bool,
base: Base<Node>,
}
#[godot_api]
impl INode for ReplayRecorder {
fn ready(&mut self) {
self.recording = true;
self.run_deferred(|s| {
s.current_frame = s.record_frame();
if let Some(game) = &Game::singleton() {
for player in &game.bind().players {
if let Some(team) = game.bind().get_team(player.data.team) {
s.replay.players.insert(
player.id(),
ReplayPlayer {
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();
}
});
}
fn physics_process(&mut self, delta: f64) {
if !self.recording {
return;
}
self.time_elapsed += delta;
if self.time_elapsed > (self.current_frame.time + (1. / 60.)) {
self.replay.frames.push(self.current_frame.clone());
self.current_frame = self.record_frame();
}
}
}
impl ReplayRecorder {
pub fn save_to(&self, path: &GString) {
if let Some(mut file) = FileAccess::open(path, ModeFlags::WRITE) {
let mut buf = Vec::new();
let cursor = Cursor::new(&mut buf);
ciborium::into_writer(&self.replay, cursor).ok();
for byte in buf {
file.store_8(byte);
}
}
}
pub fn record_event(&mut self, event: ReplayEvent) {
self.current_frame.events.push(event);
}
pub fn stop_recording(&mut self) {
self.replay.frames.push(self.current_frame.clone());
self.recording = false;
}
fn record_frame(&mut self) -> ReplayFrame {
let mut player_data = HashMap::new();
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() {
player_data.insert(player.id(), state);
}
}
ball_data = if let Some(ball) = &game.bind().world_ball {
ReplayBall::World(ReplayBallData {
transform: ball.bind().get_transform(),
velocity: ball.bind().get_velocity(),
})
} 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(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Replay {
pub map_idx: u8,
pub players: HashMap<u16, ReplayPlayer>,
pub teams: Vec<ReplayTeam>,
pub goals: HashMap<u8, u16>,
frames: Vec<ReplayFrame>,
}
impl Default for Replay {
fn default() -> Self {
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)]
pub struct ReplayColor {
r: f32,
g: f32,
b: f32,
a: f32,
}
impl From<Color> for ReplayColor {
fn from(value: Color) -> Self {
ReplayColor {
r: value.r,
g: value.g,
b: value.b,
a: value.a,
}
}
}
impl From<ReplayColor> for Color {
fn from(value: ReplayColor) -> Self {
Color {
r: value.r,
g: value.g,
b: value.b,
a: value.a,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ReplayFrame {
time: f64,
player_data: HashMap<u16, ReplayPlayerState>,
ball_data: ReplayBall,
events: Vec<ReplayEvent>,
goals: HashMap<u8, u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReplayEvent {
Death(DamageSource, u16),
Shoot(u16, NetVector3),
Respawn(u16, ReplayPlayerState),
Jump(u16),
Punch(u16, NetVector3),
TakeDamage(u16, DamageSource, i32),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub enum ReplayBall {
World(ReplayBallData),
#[default]
Held,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayPlayerState {
pub transform: NetTransform,
pub movement_dir: NetVector3,
pub look_up: f32,
pub current_weapon: WeaponType,
pub is_dead: bool,
}
impl Player {
pub fn get_state(&self) -> Option<ReplayPlayerState> {
if let Some(character) = &self.character {
Some(ReplayPlayerState {
transform: character.dyn_bind().get_transform(),
movement_dir: character.dyn_bind().get_movement_dir().into(),
look_up: character.dyn_bind().get_look_up(),
current_weapon: character
.dyn_bind()
.get_weapon()
.map(|w| w.dyn_bind().get_type())
.unwrap_or(WeaponType::Raygun),
is_dead: character.dyn_bind().is_dead(),
})
} else {
None
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayBallData {
transform: NetTransform,
velocity: NetVector3,
}

View File

@ -1,5 +1,5 @@
use godot::{
classes::{Button, FileDialog, HBoxContainer, IPanel, Label, Panel},
classes::{Button, HBoxContainer, IPanel, Label, Panel},
prelude::*,
};
@ -18,9 +18,6 @@ pub struct GameFinishedScreen {
#[export]
player_listings: Option<Gd<PlayerListings>>,
#[export]
save_file_dialog: Option<Gd<FileDialog>>,
base: Base<Panel>,
}
@ -39,24 +36,10 @@ impl IPanel for GameFinishedScreen {
GameManager::singleton().bind().go_to_lobby();
});
}
if let Some(file_dialog) = self.save_file_dialog.clone() {
file_dialog
.signals()
.file_selected()
.connect_other(self, Self::save_replay);
}
}
}
#[godot_api]
impl GameFinishedScreen {
#[func]
pub fn open_file_dialog(&mut self) {
if let Some(file_dialog) = &mut self.save_file_dialog {
file_dialog.popup_file_dialog();
}
}
fn produce_title(&mut self) {
if let Some(game) = Game::singleton() {
let max_goals = game
@ -101,12 +84,4 @@ impl GameFinishedScreen {
}
}
}
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);
}
}
}

View File

@ -1,20 +1,13 @@
use std::io::Cursor;
use godot::{
classes::{
Button, Camera3D, FileAccess, FileDialog, Label, LineEdit, OptionButton, Panel, SpinBox,
file_access::ModeFlags,
},
classes::{Button, Camera3D, Label, LineEdit, OptionButton, Panel, SpinBox},
prelude::*,
register::property::SimpleVar,
};
use crate::{
game_manager::GameManager,
game_settings::{GameSettingsManager, RecentServer},
net::network_manager::NetworkManager,
popup_queue::{Popup, PopupQueue},
replay::{Replay, StandaloneReplayManager},
ui::credits::CreditsMenu,
};
@ -68,9 +61,6 @@ pub struct MainMenu {
#[export]
credits: Option<Gd<PackedScene>>,
#[export]
open_replay_dialog: Option<Gd<FileDialog>>,
prev_panel: Option<Gd<Panel>>,
recent_servers: Vec<RecentServer>,
@ -97,12 +87,6 @@ impl INode3D for MainMenu {
.item_selected()
.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();
}
@ -178,21 +162,6 @@ impl INode3D for MainMenu {
#[godot_api]
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) {
self.run_deferred(move |_| {
StandaloneReplayManager::singleton()
.bind_mut()
.open_replay(&path);
});
}
#[func]
pub fn host_game(&mut self) {
if let Some(host_port) = &self.host_port {

View File

@ -9,7 +9,6 @@ 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;

View File

@ -3,7 +3,7 @@ use godot::{
prelude::*,
};
use crate::{game_manager::Game, replay::StandaloneReplayManager};
use crate::game_manager::Game;
#[derive(GodotClass)]
#[class(base=HBoxContainer, init)]
@ -53,64 +53,45 @@ impl IHBoxContainer for PlayerListing {
impl PlayerListing {
pub fn update(&mut self) {
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");
}
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);
}
} 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");
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");
}
}
}

View File

@ -4,9 +4,7 @@ use godot::{
prelude::*,
};
use crate::{
game_manager::Game, replay::StandaloneReplayManager, ui::player_listing::PlayerListing,
};
use crate::{game_manager::Game, ui::player_listing::PlayerListing};
#[derive(GodotClass)]
#[class(base=HBoxContainer, init)]
@ -71,40 +69,6 @@ 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);
}
}

View File

@ -1,96 +0,0 @@
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();
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);
}
}
}