Controller Replication (#57)
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -3665,10 +3665,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "happy_feet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/PROMETHIA-27/happy_feet.git?rev=5a87760d8a7970c74e07f30bc31ceaafad9a69b6#5a87760d8a7970c74e07f30bc31ceaafad9a69b6"
|
||||
source = "git+https://github.com/atornity/happy_feet.git?rev=1b24ed95f166e63af35e7b6f9f0053d6d28e1f1a#1b24ed95f166e63af35e7b6f9f0053d6d28e1f1a"
|
||||
dependencies = [
|
||||
"avian3d",
|
||||
"bevy",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4411,6 +4412,7 @@ dependencies = [
|
||||
"lightyear_interpolation",
|
||||
"lightyear_link",
|
||||
"lightyear_messages",
|
||||
"lightyear_prediction",
|
||||
"lightyear_replication",
|
||||
"lightyear_sync",
|
||||
"lightyear_transport",
|
||||
|
||||
@@ -23,10 +23,13 @@ bevy_common_assets = { version = "0.13.0", features = ["ron"] }
|
||||
bevy_debug_log = "0.6.0"
|
||||
bevy_sprite3d = "5.0.0"
|
||||
bevy_trenchbroom = { version = "0.8.1", features = ["avian"] }
|
||||
happy_feet = { git = "https://github.com/PROMETHIA-27/happy_feet.git", rev = "5a87760d8a7970c74e07f30bc31ceaafad9a69b6" }
|
||||
happy_feet = { git = "https://github.com/atornity/happy_feet.git", rev = "1b24ed95f166e63af35e7b6f9f0053d6d28e1f1a", features = [
|
||||
"serde",
|
||||
] }
|
||||
lightyear = { git = "https://github.com/cBournhonesque/lightyear.git", rev = "03cbf419a2c0595261b64420bc0332fc3fe1cc3f", default-features = false, features = [
|
||||
"interpolation",
|
||||
"netcode",
|
||||
"prediction",
|
||||
"replication",
|
||||
"std",
|
||||
"steam",
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
use bevy::prelude::*;
|
||||
use lightyear::{
|
||||
connection::client::ClientState,
|
||||
netcode::Key,
|
||||
prelude::{client::NetcodeConfig, *},
|
||||
};
|
||||
use shared::{GameState, heads_database::HeadsDatabase, tb_entities::SpawnPoint};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Startup, temp_connect_on_startup);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
spawn_disconnected_player.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
fn temp_connect_on_startup(mut commands: Commands) -> Result {
|
||||
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25564);
|
||||
let mut args = std::env::args();
|
||||
let client_port = loop {
|
||||
match args.next().as_deref() {
|
||||
Some("--port") => {
|
||||
break args.next().unwrap().parse::<u16>().unwrap();
|
||||
}
|
||||
Some(_) => (),
|
||||
None => break 25564,
|
||||
}
|
||||
};
|
||||
|
||||
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), client_port);
|
||||
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25565);
|
||||
let auth = Authentication::Manual {
|
||||
server_addr,
|
||||
client_id: 0,
|
||||
client_id: client_port as u64,
|
||||
private_key: Key::default(),
|
||||
protocol_id: 0,
|
||||
};
|
||||
@@ -26,10 +43,28 @@ fn temp_connect_on_startup(mut commands: Commands) -> Result {
|
||||
PeerAddr(server_addr),
|
||||
Link::new(None),
|
||||
ReplicationReceiver::default(),
|
||||
client::NetcodeClient::new(auth, NetcodeConfig::default())?,
|
||||
client::NetcodeClient::new(
|
||||
auth,
|
||||
NetcodeConfig {
|
||||
client_timeout_secs: 1,
|
||||
..default()
|
||||
},
|
||||
)?,
|
||||
UdpIo::default(),
|
||||
))
|
||||
.trigger(Connect);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_disconnected_player(
|
||||
disconnected: Single<&Client, Changed<Client>>,
|
||||
commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
if disconnected.state == ClientState::Disconnected {
|
||||
shared::player::spawn(commands, query, asset_server, heads_db)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use lightyear::prelude::{
|
||||
server::{NetcodeConfig, NetcodeServer, ServerUdpIo},
|
||||
*,
|
||||
};
|
||||
use shared::utils::commands::IsServer;
|
||||
use shared::{heads_database::HeadsDatabase, tb_entities::SpawnPoint, utils::commands::IsServer};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -17,6 +17,9 @@ fn handle_new_client(
|
||||
trigger: Trigger<OnAdd, Connected>,
|
||||
mut commands: Commands,
|
||||
id: Query<&PeerAddr>,
|
||||
asset_server: Res<AssetServer>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) -> Result {
|
||||
let id = id.get(trigger.target())?;
|
||||
|
||||
@@ -26,6 +29,8 @@ fn handle_new_client(
|
||||
.entity(trigger.target())
|
||||
.insert(ReplicationSender::default());
|
||||
|
||||
shared::player::spawn(commands, query, asset_server, heads_db);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ use crate::{
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CameraTarget;
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CameraArmRotation;
|
||||
|
||||
/// Requested camera rotation based on various input sources (keyboard, gamepad)
|
||||
|
||||
@@ -10,12 +10,13 @@ use bevy::{
|
||||
animation::RepeatAnimation, ecs::system::SystemParam, platform::collections::HashMap,
|
||||
prelude::*, scene::SceneInstanceReady,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{f32::consts::PI, time::Duration};
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct ProjectileOrigin;
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedCharacter {
|
||||
head: usize,
|
||||
}
|
||||
|
||||
@@ -5,16 +5,22 @@ use crate::{
|
||||
animation::AnimationFlags,
|
||||
character::HasCharacterAnimations,
|
||||
control::{Controls, SelectedController, controls::ControllerSettings},
|
||||
heads_database::HeadControls,
|
||||
head::ActiveHead,
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
};
|
||||
use avian3d::{math::*, prelude::*};
|
||||
use bevy::prelude::*;
|
||||
use bevy::{
|
||||
ecs::{component::HookContext, world::DeferredWorld},
|
||||
prelude::*,
|
||||
};
|
||||
use happy_feet::prelude::{
|
||||
Character, CharacterDrag, CharacterGravity, CharacterMovement, CharacterPlugins,
|
||||
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
|
||||
SteppingConfig,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(CharacterPlugins::default());
|
||||
@@ -148,10 +154,28 @@ fn decelerate(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub struct MovementSpeedFactor(pub f32);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
#[component(on_add = add_controller_bundle)]
|
||||
pub struct PlayerCharacterController;
|
||||
|
||||
fn add_controller_bundle(mut world: DeferredWorld, ctx: HookContext) {
|
||||
let head = world
|
||||
.entity(ctx.entity)
|
||||
.get::<ActiveHead>()
|
||||
.expect("player must be spawned with an `ActiveHead`")
|
||||
.0;
|
||||
let controls = world.resource::<HeadsDatabase>().head_stats(head).controls;
|
||||
world
|
||||
.commands()
|
||||
.entity(ctx.entity)
|
||||
.insert(CharacterControllerBundle::new(controls));
|
||||
}
|
||||
|
||||
/// A bundle that contains the components needed for a basic
|
||||
/// kinematic character controller.
|
||||
#[derive(Bundle)]
|
||||
@@ -163,11 +187,13 @@ pub struct CharacterControllerBundle {
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: MovementConfig,
|
||||
interpolation: TransformInterpolation,
|
||||
layers: CollisionLayers,
|
||||
}
|
||||
|
||||
impl CharacterControllerBundle {
|
||||
pub fn new(collider: Collider, controls: HeadControls) -> Self {
|
||||
pub fn new(controls: HeadControls) -> Self {
|
||||
// Create shape caster as a slightly smaller version of collider
|
||||
let collider = Collider::capsule(0.9, 1.2);
|
||||
let mut caster_shape = collider.clone();
|
||||
caster_shape.set_scale(Vector::ONE * 0.98, 10);
|
||||
|
||||
@@ -184,6 +210,10 @@ impl CharacterControllerBundle {
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: config,
|
||||
interpolation: TransformInterpolation,
|
||||
layers: CollisionLayers::new(
|
||||
LayerMask(GameLayer::Player.to_bits()),
|
||||
LayerMask::ALL & !GameLayer::CollectiblePhysics.to_bits(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use bevy::{
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<Controls>();
|
||||
@@ -43,7 +44,7 @@ pub fn plugin(app: &mut App) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Reflect)]
|
||||
#[derive(Component, Clone, PartialEq, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub struct ControllerSettings {
|
||||
pub deceleration_factor: f32,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ActiveHead(pub usize);
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub static HEAD_COUNT: usize = 18;
|
||||
pub static HEAD_SLOTS: usize = 5;
|
||||
@@ -21,7 +22,7 @@ pub struct HeadsImages {
|
||||
pub heads: Vec<Handle<Image>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Serialize, Deserialize)]
|
||||
pub struct HeadState {
|
||||
pub head: usize,
|
||||
pub health: u32,
|
||||
@@ -51,7 +52,7 @@ impl HeadState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, Debug)]
|
||||
#[derive(Component, Default, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub struct ActiveHeads {
|
||||
heads: [Option<HeadState>; 5],
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent},
|
||||
character::AnimatedCharacter,
|
||||
control::controller_common::CharacterControllerBundle,
|
||||
control::controller_common::PlayerCharacterController,
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
@@ -12,9 +12,9 @@ use crate::{
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
loading_assets::AudioAssets,
|
||||
npc::SpawnCharacter,
|
||||
physics_layers::GameLayer,
|
||||
sounds::PlaySound,
|
||||
tb_entities::SpawnPoint,
|
||||
utils::commands::EntityCommandExt,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
@@ -22,17 +22,18 @@ use bevy::{
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, PrimaryWindow},
|
||||
};
|
||||
use lightyear::prelude::{NetworkTarget, PredictionTarget, Replicate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct PlayerBodyMesh;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Startup, (toggle_cursor_system, cursor_recenter));
|
||||
app.add_systems(OnEnter(GameState::Playing), spawn);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -46,10 +47,10 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_update_head_mesh);
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
pub fn spawn(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
let Some(spawn) = query.iter().next() else {
|
||||
@@ -58,10 +59,24 @@ fn spawn(
|
||||
|
||||
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
||||
|
||||
let collider = Collider::capsule(0.9, 1.2);
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
.spawn(player_bundle(transform, &heads_db))
|
||||
.insert_server((
|
||||
Replicate::to_clients(NetworkTarget::All),
|
||||
PredictionTarget::to_clients(NetworkTarget::All),
|
||||
))
|
||||
.observe(on_kill);
|
||||
|
||||
commands.spawn((
|
||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||
PlaybackSettings::DESPAWN,
|
||||
));
|
||||
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
}
|
||||
|
||||
fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bundle {
|
||||
(
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
@@ -76,26 +91,14 @@ fn spawn(
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Player.to_bits()),
|
||||
LayerMask::ALL & !GameLayer::CollectiblePhysics.to_bits(),
|
||||
),
|
||||
CharacterControllerBundle::new(collider, heads_db.head_stats(0).controls),
|
||||
PlayerCharacterController,
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
children![AnimatedCharacter::new(0)]
|
||||
)],
|
||||
))
|
||||
.observe(on_kill);
|
||||
|
||||
commands.spawn((
|
||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||
PlaybackSettings::DESPAWN,
|
||||
));
|
||||
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
)
|
||||
}
|
||||
|
||||
fn on_kill(
|
||||
|
||||
@@ -1,20 +1,72 @@
|
||||
use crate::{
|
||||
abilities::BuildExplosionSprite, global_observer, loading_assets::GameAssets,
|
||||
abilities::BuildExplosionSprite,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
character::AnimatedCharacter,
|
||||
control::{
|
||||
controller_common::{MovementSpeedFactor, PlayerCharacterController},
|
||||
controls::ControllerSettings,
|
||||
},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
heads::ActiveHeads,
|
||||
loading_assets::GameAssets,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
utils::triggers::TriggerAppExt,
|
||||
};
|
||||
use avian3d::prelude::{AngularVelocity, CollisionLayers, LinearVelocity};
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::{ActionsChannel, AppComponentExt};
|
||||
use happy_feet::{
|
||||
grounding::GroundingState,
|
||||
prelude::{
|
||||
Character, CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding,
|
||||
GroundingConfig, KinematicVelocity, MoveInput, SteppingConfig,
|
||||
},
|
||||
};
|
||||
use lightyear::prelude::{
|
||||
ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_component::<Transform>();
|
||||
app.register_component::<Transform>()
|
||||
.add_prediction(PredictionMode::Full)
|
||||
.add_should_rollback(transform_should_rollback);
|
||||
app.register_component::<GltfSceneRoot>();
|
||||
app.register_component::<Player>();
|
||||
app.register_component::<PlayerBodyMesh>();
|
||||
app.register_component::<Name>();
|
||||
app.register_component::<ActiveHead>();
|
||||
app.register_component::<ActiveHeads>();
|
||||
app.register_component::<CameraTarget>();
|
||||
app.register_component::<CameraArmRotation>();
|
||||
app.register_component::<CollisionLayers>();
|
||||
app.register_component::<AnimatedCharacter>();
|
||||
app.register_component::<PlayerCharacterController>();
|
||||
app.register_component::<LinearVelocity>();
|
||||
app.register_component::<AngularVelocity>();
|
||||
app.register_component::<KinematicVelocity>();
|
||||
app.register_component::<Character>();
|
||||
app.register_component::<MoveInput>();
|
||||
app.register_component::<MovementSpeedFactor>();
|
||||
app.register_component::<CharacterMovement>();
|
||||
app.register_component::<SteppingConfig>();
|
||||
app.register_component::<GroundingConfig>();
|
||||
app.register_component::<Grounding>();
|
||||
app.register_component::<GroundingState>();
|
||||
app.register_component::<CharacterGravity>();
|
||||
app.register_component::<GroundFriction>();
|
||||
app.register_component::<CharacterDrag>();
|
||||
app.register_component::<ControllerSettings>();
|
||||
|
||||
app.replicate_trigger::<BuildExplosionSprite, ActionsChannel>();
|
||||
|
||||
global_observer!(app, spawn_gltf_scene_roots);
|
||||
}
|
||||
|
||||
fn transform_should_rollback(this: &Transform, that: &Transform) -> bool {
|
||||
this.translation.distance_squared(that.translation) >= 0.01f32.powf(2.)
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub enum GltfSceneRoot {
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod billboards;
|
||||
pub mod commands;
|
||||
pub mod explosions;
|
||||
pub mod observers;
|
||||
pub mod run_conditions;
|
||||
pub mod sprite_3d_animation;
|
||||
pub mod squish_animation;
|
||||
pub mod trail;
|
||||
|
||||
5
crates/shared/src/utils/run_conditions.rs
Normal file
5
crates/shared/src/utils/run_conditions.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use bevy::ecs::{resource::Resource, system::Res};
|
||||
|
||||
pub fn resource_absent<R: Resource>(res: Option<Res<R>>) -> bool {
|
||||
res.is_none()
|
||||
}
|
||||
4
justfile
4
justfile
@@ -5,8 +5,8 @@ tb_setup_mac:
|
||||
ln -s $(pwd)/trenchbroom/hedz/hedz.fgd "$HOME/Library/Application Support/TrenchBroom/games/hedz/hedz.fgd" | true
|
||||
ln -s $(pwd)/trenchbroom/hedz/GameConfig.cfg "$HOME/Library/Application Support/TrenchBroom/games/hedz/GameConfig.cfg" | true
|
||||
|
||||
run:
|
||||
RUST_BACKTRACE=1 cargo r --bin hedz_reloaded
|
||||
run *args:
|
||||
RUST_BACKTRACE=1 cargo r --bin hedz_reloaded -- {{args}}
|
||||
|
||||
server:
|
||||
RUST_BACKTRACE=1 cargo r --bin server
|
||||
|
||||
Reference in New Issue
Block a user