Start adding replays
This commit is contained in:
parent
b8578a0a95
commit
7a9292242f
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/player/local_player.tscn" id="1_yjvui"]
|
[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://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://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://c1r8nk58cci3a" path="res://scenes/misc/goal.tscn" id="4_w7til"]
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ spawners = [NodePath("team1_spawner"), NodePath("team2_spawner")]
|
|||||||
ball_spawner = NodePath("ball_spawner")
|
ball_spawner = NodePath("ball_spawner")
|
||||||
local_player = ExtResource("1_yjvui")
|
local_player = ExtResource("1_yjvui")
|
||||||
remote_player = ExtResource("2_kcncr")
|
remote_player = ExtResource("2_kcncr")
|
||||||
|
replay_player = ExtResource("3_8af6u")
|
||||||
ball = ExtResource("3_e0nj3")
|
ball = ExtResource("3_e0nj3")
|
||||||
|
|
||||||
[node name="terrain" type="MeshInstance3D" parent="." unique_id=1474127252]
|
[node name="terrain" type="MeshInstance3D" parent="." unique_id=1474127252]
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"]
|
||||||
|
|
||||||
[node name="ball" type="Ball" unique_id=816748600]
|
[node name="ball" type="Ball" unique_id=816748600]
|
||||||
collision_layer = 12
|
collision_layer = 8
|
||||||
collision_mask = 7
|
collision_mask = 7
|
||||||
contact_monitor = true
|
contact_monitor = true
|
||||||
max_contacts_reported = 10
|
max_contacts_reported = 10
|
||||||
|
|||||||
98
godot/scenes/player/replay_player.tscn
Normal file
98
godot/scenes/player/replay_player.tscn
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
[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)
|
||||||
@ -23,6 +23,7 @@ use crate::{
|
|||||||
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer,
|
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer,
|
||||||
remote_player::RemotePlayer, weapon::WeaponType,
|
remote_player::RemotePlayer, weapon::WeaponType,
|
||||||
},
|
},
|
||||||
|
replay::{ReplayPlayback, ReplayRecorder},
|
||||||
team_resource::TeamResource,
|
team_resource::TeamResource,
|
||||||
ui::cli::{Command, CommandLinePanel},
|
ui::cli::{Command, CommandLinePanel},
|
||||||
};
|
};
|
||||||
@ -229,6 +230,9 @@ pub struct Game {
|
|||||||
|
|
||||||
pub goals: HashMap<u8, u16>,
|
pub goals: HashMap<u8, u16>,
|
||||||
|
|
||||||
|
pub replay_recorder: Option<Gd<ReplayRecorder>>,
|
||||||
|
pub current_replay_playback: Option<Gd<ReplayPlayback>>,
|
||||||
|
|
||||||
base: Base<Node>,
|
base: Base<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,6 +346,17 @@ impl Game {
|
|||||||
.map(|g| g.cast::<Game>())
|
.map(|g| g.cast::<Game>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_playback(&mut self, time_delta: f64) {
|
||||||
|
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;
|
||||||
|
self.current_replay_playback = Some(playback.clone());
|
||||||
|
self.base_mut().add_child(&playback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_game(&mut self) {
|
pub fn start_game(&mut self) {
|
||||||
self.game_started = true;
|
self.game_started = true;
|
||||||
if let Some(map) = &self.selected_map {
|
if let Some(map) = &self.selected_map {
|
||||||
@ -362,6 +377,10 @@ 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) {
|
pub fn reset_game(&mut self) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ pub mod map_resource;
|
|||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod popup_queue;
|
pub mod popup_queue;
|
||||||
|
pub mod replay;
|
||||||
pub mod team_resource;
|
pub mod team_resource;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +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::ReplayPlayerState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
@ -20,6 +21,8 @@ pub struct Map {
|
|||||||
#[export]
|
#[export]
|
||||||
pub remote_player: Option<Gd<PackedScene>>,
|
pub remote_player: Option<Gd<PackedScene>>,
|
||||||
#[export]
|
#[export]
|
||||||
|
pub replay_player: Option<Gd<PackedScene>>,
|
||||||
|
#[export]
|
||||||
pub ball: Option<Gd<PackedScene>>,
|
pub ball: Option<Gd<PackedScene>>,
|
||||||
|
|
||||||
base: Base<Node3D>,
|
base: Base<Node3D>,
|
||||||
@ -93,6 +96,31 @@ impl Map {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spawn_replay_player(
|
||||||
|
&mut self,
|
||||||
|
player_id: u16,
|
||||||
|
state: ReplayPlayerState,
|
||||||
|
) -> Option<Gd<RemotePlayer>> {
|
||||||
|
if let Some(replay_player) = &self.replay_player {
|
||||||
|
let mut node: Gd<RemotePlayer> = replay_player.instantiate_as();
|
||||||
|
node.bind_mut().player_id = Some(player_id);
|
||||||
|
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(
|
pub fn spawn_ball(
|
||||||
&mut self,
|
&mut self,
|
||||||
transform: Option<NetTransform>,
|
transform: Option<NetTransform>,
|
||||||
|
|||||||
@ -350,10 +350,10 @@ impl ICharacterBody3D for LocalPlayer {
|
|||||||
|
|
||||||
if let Some(camera) = &self.camera {
|
if let Some(camera) = &self.camera {
|
||||||
if camera.is_current() && self.dead_for > 3. {
|
if camera.is_current() && self.dead_for > 3. {
|
||||||
if let Some(game) = Game::singleton()
|
if let Some(mut game) = Game::singleton()
|
||||||
&& let Some(killer_id) = self.killer_id
|
&& let Some(killer_id) = self.killer_id
|
||||||
{
|
{
|
||||||
let game = game.bind();
|
let mut game = game.bind_mut();
|
||||||
let remote_player = game.find_player(killer_id);
|
let remote_player = game.find_player(killer_id);
|
||||||
if let Some(remote_player) = &remote_player
|
if let Some(remote_player) = &remote_player
|
||||||
&& let Some(remote_character) = remote_player.character.clone()
|
&& let Some(remote_character) = remote_player.character.clone()
|
||||||
@ -363,6 +363,8 @@ impl ICharacterBody3D for LocalPlayer {
|
|||||||
{
|
{
|
||||||
camera.set_current(true);
|
camera.set_current(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game.start_playback(-5.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
181
rust/src/replay.rs
Normal file
181
rust/src/replay.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use godot::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
game_manager::{Game, Player},
|
||||||
|
map::Map,
|
||||||
|
net::util::NetVector3,
|
||||||
|
player::{IPlayer, NetTransform, remote_player::RemotePlayer, weapon::WeaponType},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[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>>,
|
||||||
|
|
||||||
|
pub current_time: f64,
|
||||||
|
|
||||||
|
base: Base<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl INode for ReplayPlayback {
|
||||||
|
fn ready(&mut self) {}
|
||||||
|
|
||||||
|
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 Some(map) = &mut self.current_map {
|
||||||
|
for (id, data) in &frame.player_data {
|
||||||
|
if !self.player_characters.contains_key(id) {
|
||||||
|
if let Some(player) = map.bind_mut().spawn_replay_player(*id, data.clone())
|
||||||
|
{
|
||||||
|
self.player_characters.insert(*id, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
} else {
|
||||||
|
character.queue_free();
|
||||||
|
self.player_characters.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.current_frame_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GodotClass)]
|
||||||
|
#[class(base=Node, init)]
|
||||||
|
pub struct ReplayRecorder {
|
||||||
|
pub time_elapsed: f64,
|
||||||
|
|
||||||
|
pub replay: Replay,
|
||||||
|
current_frame: ReplayFrame,
|
||||||
|
|
||||||
|
base: Base<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl INode for ReplayRecorder {
|
||||||
|
fn ready(&mut self) {
|
||||||
|
self.run_deferred(|s| {
|
||||||
|
s.current_frame = s.record_frame();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physics_process(&mut self, delta: f64) {
|
||||||
|
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 {
|
||||||
|
fn record_frame(&mut self) -> ReplayFrame {
|
||||||
|
let mut player_data = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplayFrame {
|
||||||
|
time: self.time_elapsed,
|
||||||
|
player_data,
|
||||||
|
events: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Replay {
|
||||||
|
players: HashMap<u16, ReplayPlayer>,
|
||||||
|
frames: Vec<ReplayFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Replay {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
players: Default::default(),
|
||||||
|
frames: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReplayPlayer {
|
||||||
|
id: u16,
|
||||||
|
name: String,
|
||||||
|
initial_state: ReplayPlayerState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct ReplayFrame {
|
||||||
|
time: f64,
|
||||||
|
player_data: HashMap<u16, ReplayPlayerState>,
|
||||||
|
events: Vec<ReplayEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ReplayEvent {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReplayBall {
|
||||||
|
World(ReplayBallData),
|
||||||
|
Held,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReplayPlayerState {
|
||||||
|
pub transform: NetTransform,
|
||||||
|
pub movement_dir: NetVector3,
|
||||||
|
pub look_up: f32,
|
||||||
|
pub current_weapon: WeaponType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ReplayBallData {
|
||||||
|
transform: NetTransform,
|
||||||
|
velocity: NetVector3,
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user