Lightyear setup (#55)

This commit is contained in:
extrawurst
2025-07-10 23:21:11 +02:00
committed by GitHub
parent 691b9eed33
commit 78b09b33d6
26 changed files with 2515 additions and 242 deletions

2280
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
[workspace]
resolver = "3"
members = ["crates/*"]
@@ -12,6 +11,7 @@ avian3d = { version = "0.3", default-features = false, features = [
"bevy_scene",
"bevy_picking", # todo: Consider if this one is necessary
"parallel",
"serialize",
] }
bevy = { version = "0.16.0", features = ["track_location"] }
bevy-inspector-egui = { version = "0.31" }
@@ -23,13 +23,23 @@ 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/rustunit/happy_feet.git", rev = "ecfecc6243862bc2bc64dcadfd0efd21c766ab5b" }
happy_feet = { git = "https://github.com/rustunit/happy_feet.git", rev = "e4e57c50ba5b5e0be5166e9e4eb629dc1d616a0d" }
lightyear = { git = "https://github.com/cBournhonesque/lightyear.git", rev = "03cbf419a2c0595261b64420bc0332fc3fe1cc3f", default-features = false, features = [
"interpolation",
"netcode",
"replication",
"std",
"steam",
"udp",
] }
lightyear_avian3d = { git = "https://github.com/cBournhonesque/lightyear.git", rev = "03cbf419a2c0595261b64420bc0332fc3fe1cc3f" }
nil = "0.14.0"
rand = "=0.8.5"
ron = "0.8"
serde = { version = "1.0.219", features = ["derive"] }
shared = { path = "crates/shared" }
steamworks = "0.11"
[profile.dev.package."*"]
opt-level = 3

View File

@@ -5,6 +5,7 @@ edition = "2024"
build = "build.rs"
[features]
default = ["lightyear/client"]
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui", "shared/dbg"]
[dependencies]
@@ -20,6 +21,7 @@ bevy_debug_log = { workspace = true }
bevy_sprite3d = { workspace = true }
bevy_trenchbroom = { workspace = true }
happy_feet = { workspace = true }
lightyear = { workspace = true }
nil = { workspace = true }
rand = { workspace = true }
ron = { workspace = true }

View File

@@ -0,0 +1,35 @@
use bevy::prelude::*;
use lightyear::{
netcode::Key,
prelude::{client::NetcodeConfig, *},
};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
pub fn plugin(app: &mut App) {
app.add_systems(Startup, temp_connect_on_startup);
}
fn temp_connect_on_startup(mut commands: Commands) -> Result {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25564);
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25565);
let auth = Authentication::Manual {
server_addr,
client_id: 0,
private_key: Key::default(),
protocol_id: 0,
};
commands
.spawn((
Name::from("Client"),
Client::default(),
LocalAddr(client_addr),
PeerAddr(server_addr),
Link::new(None),
ReplicationReceiver::default(),
client::NetcodeClient::new(auth, NetcodeConfig::default())?,
UdpIo::default(),
))
.trigger(Connect);
Ok(())
}

View File

@@ -1,3 +1,4 @@
mod client;
mod debug;
mod steam;
mod ui;
@@ -16,8 +17,10 @@ use bevy_trenchbroom::prelude::*;
use bevy_ui_gradients::UiGradientsPlugin;
use camera::MainCamera;
use heads_database::HeadDatabaseAsset;
use lightyear::prelude::client::ClientPlugins;
use loading_assets::AudioAssets;
use shared::*;
use std::time::Duration;
use utils::{billboards, sprite_3d_animation, squish_animation, trail};
fn main() {
@@ -58,6 +61,9 @@ fn main() {
.auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
);
app.add_plugins(PhysicsPlugins::default());
app.add_plugins(ClientPlugins {
tick_duration: Duration::from_secs_f64(1.0 / 60.0),
});
app.add_plugins(Sprite3dPlugin);
app.add_plugins(TrenchBroomPlugins(
TrenchBroomConfig::new("hedz").icon(None),
@@ -87,9 +93,11 @@ fn main() {
app.add_plugins(player::plugin);
app.add_plugins(gates::plugin);
app.add_plugins(platforms::plugin);
app.add_plugins(protocol::plugin);
app.add_plugins(movables::plugin);
app.add_plugins(billboards::plugin);
app.add_plugins(aim::plugin);
app.add_plugins(client::plugin);
app.add_plugins(npc::plugin);
app.add_plugins(keys::plugin);
app.add_plugins(squish_animation::plugin);
@@ -106,7 +114,7 @@ fn main() {
app.add_plugins(hitpoints::plugin);
app.add_plugins(cash_heal::plugin);
app.add_plugins(debug::plugin);
app.add_plugins(utils::observers::plugin);
app.add_plugins(utils::plugin);
app.add_plugins(water::plugin);
app.add_plugins(head_drop::plugin);
app.add_plugins(trail::plugin);

View File

@@ -1,8 +1,7 @@
use crate::GameState;
use bevy::{color::palettes::css::BLACK, prelude::*};
use shared::{HEDZ_GREEN, HEDZ_PURPLE, control::CharacterInputEnabled, loading_assets::UIAssets};
use crate::GameState;
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
#[states(scoped_entities)]
enum PauseMenuState {

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024"
[features]
default = ["lightyear/server"]
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui", "shared/dbg"]
[dependencies]
@@ -19,6 +20,8 @@ bevy_debug_log = { workspace = true }
bevy_sprite3d = { workspace = true }
bevy_trenchbroom = { workspace = true }
happy_feet = { workspace = true }
lightyear = { workspace = true }
lightyear_avian3d = { workspace = true }
nil = { workspace = true }
rand = { workspace = true }
ron = { workspace = true }

View File

@@ -6,9 +6,13 @@ use bevy_sprite3d::Sprite3dPlugin;
use bevy_trenchbroom::prelude::*;
use bevy_ui_gradients::UiGradientsPlugin;
use heads_database::HeadDatabaseAsset;
use lightyear::prelude::server::ServerPlugins;
use shared::*;
use std::time::Duration;
use utils::{billboards, sprite_3d_animation, squish_animation, trail};
mod server;
fn main() {
let mut app = App::new();
@@ -44,6 +48,9 @@ fn main() {
bevy_debug_log::LogViewerPlugin::default()
.auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
);
app.add_plugins(ServerPlugins {
tick_duration: Duration::from_secs_f32(1.0 / 60.0),
});
app.add_plugins(PhysicsPlugins::default());
app.add_plugins(Sprite3dPlugin);
app.add_plugins(TrenchBroomPlugins(
@@ -77,6 +84,8 @@ fn main() {
app.add_plugins(movables::plugin);
app.add_plugins(billboards::plugin);
app.add_plugins(aim::plugin);
app.add_plugins(protocol::plugin);
app.add_plugins(server::plugin);
app.add_plugins(npc::plugin);
app.add_plugins(keys::plugin);
app.add_plugins(squish_animation::plugin);
@@ -92,7 +101,7 @@ fn main() {
app.add_plugins(heads::plugin);
app.add_plugins(hitpoints::plugin);
app.add_plugins(cash_heal::plugin);
app.add_plugins(utils::observers::plugin);
app.add_plugins(utils::plugin);
app.add_plugins(water::plugin);
app.add_plugins(head_drop::plugin);
app.add_plugins(trail::plugin);

View File

@@ -0,0 +1,45 @@
use bevy::prelude::*;
use lightyear::prelude::{
server::{NetcodeConfig, NetcodeServer, ServerUdpIo},
*,
};
use shared::utils::commands::IsServer;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
pub fn plugin(app: &mut App) {
app.init_resource::<IsServer>();
app.add_systems(Startup, start_server);
app.add_observer(handle_new_client);
}
fn handle_new_client(
trigger: Trigger<OnAdd, Connected>,
mut commands: Commands,
id: Query<&PeerAddr>,
) -> Result {
let id = id.get(trigger.target())?;
info!("Client connected on IP: {}", id.ip());
commands
.entity(trigger.target())
.insert(ReplicationSender::default());
Ok(())
}
fn start_server(mut commands: Commands) -> Result {
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25565);
commands
.spawn((
Name::from("Server"),
LocalAddr(server_addr),
ServerUdpIo::default(),
NetcodeServer::new(NetcodeConfig::default()),
))
.trigger(server::Start);
Ok(())
}

View File

@@ -19,6 +19,7 @@ bevy_debug_log = { workspace = true }
bevy_sprite3d = { workspace = true }
bevy_trenchbroom = { workspace = true }
happy_feet = { workspace = true }
lightyear = { workspace = true }
nil = { workspace = true }
rand = { workspace = true }
ron = { workspace = true }

View File

@@ -58,11 +58,15 @@ fn on_trigger_arrow(
state.rot.mul_quat(Quat::from_rotation_y(PI))
};
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.;
let damage = heads_db.head_stats(state.head).damage;
commands.spawn((Name::new("projectile-arrow"), ArrowProjectile { damage }, t));
commands.spawn((
Name::new("projectile-arrow"),
ArrowProjectile { damage },
transform,
));
}
fn update(

View File

@@ -6,12 +6,17 @@ use crate::{
hitpoints::Hit,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
tb_entities::EnemySpawn,
utils::{auto_rotate::AutoRotation, global_observer, sprite_3d_animation::AnimationTimer},
utils::{
auto_rotate::AutoRotation, commands::CommandExt, global_observer,
sprite_3d_animation::AnimationTimer,
},
};
use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.;
@@ -58,8 +63,6 @@ fn on_trigger_missile(
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
) {
let state = trigger.event().0;
@@ -76,33 +79,32 @@ fn on_trigger_missile(
let head = heads_db.head_stats(state.head);
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let mesh = assets.projectiles[format!("{}.glb", head.projectile).as_str()].clone();
let asset = gltf_assets.get(&mesh).unwrap();
commands.spawn((
Name::new("projectile-missile"),
CurverProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
children![(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
SceneRoot(asset.scenes[0].clone()),
),],
));
commands
.spawn((
Name::new("projectile-missile"),
CurverProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
transform,
children![(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
GltfSceneRoot::Projectile(head.projectile.clone()),
),],
))
.insert_server(Replicate::to_clients(NetworkTarget::All));
}
fn enemy_hit(

View File

@@ -97,8 +97,8 @@ fn on_trigger_gun(
state.rot.mul_quat(Quat::from_rotation_y(PI))
};
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
commands.spawn((
Name::new("projectile-gun"),
@@ -114,7 +114,7 @@ fn on_trigger_gun(
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
transform,
Children::spawn(Spawn(Gizmo {
handle: gizmo_assets.add({
let mut g = GizmoAsset::default();

View File

@@ -5,14 +5,17 @@ use crate::{
heads_database::HeadsDatabase,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
sounds::PlaySound,
utils::{
explosions::Explosion, global_observer, sprite_3d_animation::AnimationTimer, trail::Trail,
commands::CommandExt, explosions::Explosion, global_observer,
sprite_3d_animation::AnimationTimer, trail::Trail,
},
};
use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.;
@@ -56,8 +59,6 @@ fn on_trigger_missile(
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
let state = trigger.event().0;
@@ -75,51 +76,50 @@ fn on_trigger_missile(
let head = heads_db.head_stats(state.head);
let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
t.translation += t.forward().as_vec3() * 2.0;
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let mesh = assets.projectiles["missile.glb"].clone();
let asset = gltf_assets.get(&mesh).unwrap();
commands.spawn((
Name::new("projectile-missile"),
MissileProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
CollisionEventsEnabled,
Visibility::default(),
t,
children![
(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse())
.with_scale(Vec3::splat(0.04)),
SceneRoot(asset.scenes[0].clone()),
commands
.spawn((
Name::new("projectile-missile"),
MissileProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
(
Trail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.)
)
.with_pos(t.translation),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig {
width: 10.,
Sensor,
CollisionEventsEnabled,
Visibility::default(),
transform,
children![
(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse())
.with_scale(Vec3::splat(0.04)),
GltfSceneRoot::Projectile("missile".to_string()),
),
(
Trail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.)
)
.with_pos(transform.translation),
Gizmo {
handle: gizmo_assets.add(GizmoAsset::default()),
line_config: GizmoLineConfig {
width: 10.,
..default()
},
..default()
},
..default()
},
)
],
));
)
],
))
.insert_server(Replicate::to_clients(NetworkTarget::All));
}
fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) {

View File

@@ -5,9 +5,10 @@ use crate::{
heads_database::HeadsDatabase,
loading_assets::GameAssets,
physics_layers::GameLayer,
protocol::GltfSceneRoot,
sounds::PlaySound,
utils::{
auto_rotate::AutoRotation, explosions::Explosion, global_observer,
auto_rotate::AutoRotation, commands::CommandExt, explosions::Explosion, global_observer,
sprite_3d_animation::AnimationTimer,
},
};
@@ -15,10 +16,12 @@ use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_ballistic::launch_velocity;
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use lightyear::prelude::{NetworkTarget, Replicate};
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
#[derive(Component)]
struct ThrownProjectile {
#[derive(Component, Serialize, Deserialize, PartialEq)]
pub struct ThrownProjectile {
impact_animation: bool,
damage: u32,
}
@@ -50,8 +53,6 @@ fn on_trigger_thrown(
trigger: Trigger<TriggerThrow>,
mut commands: Commands,
query_transform: Query<&Transform>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
heads_db: Res<HeadsDatabase>,
) {
let state = trigger.event().0;
@@ -76,8 +77,6 @@ fn on_trigger_thrown(
};
let head = heads_db.head_stats(state.head);
let mesh = assets.projectiles[format!("{}.glb", head.projectile).as_str()].clone();
let asset = gltf_assets.get(&mesh).unwrap();
//TODO: projectile db?
let explosion_animation = !matches!(state.head, 8 | 16);
@@ -102,9 +101,10 @@ fn on_trigger_thrown(
Visibility::default(),
Sensor,
))
.insert_server(Replicate::to_clients(NetworkTarget::All))
.with_child((
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
SceneRoot(asset.scenes[0].clone()),
GltfSceneRoot::Projectile(head.projectile.clone()),
));
}

View File

@@ -114,11 +114,11 @@ fn update_player_aim(
}
}
if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_err() {
aim_target.0 = None;
return;
}
if let Some(e) = &aim_target.0
&& commands.get_entity(*e).is_err()
{
aim_target.0 = None;
return;
}
if new_target != aim_target.0 {
@@ -166,11 +166,11 @@ fn update_npc_aim(
}
}
if let Some(e) = &aim_target.0 {
if commands.get_entity(*e).is_err() {
aim_target.0 = None;
return;
}
if let Some(e) = &aim_target.0
&& commands.get_entity(*e).is_err()
{
aim_target.0 = None;
return;
}
if new_target != aim_target.0 {

View File

@@ -140,16 +140,16 @@ fn sync(
target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
) {
let mut new_state = None;
if let Some(e) = player_target.iter().next().and_then(|target| target.0) {
if let Ok((hp, heads)) = target_data.get(e) {
let head = heads.current().expect("target must have a head on");
new_state = Some(UiHeadState {
head: head.head,
health: hp.health(),
ammo: 1.,
reloading: None,
});
}
if let Some(e) = player_target.iter().next().and_then(|target| target.0)
&& let Ok((hp, heads)) = target_data.get(e)
{
let head = heads.current().expect("target must have a head on");
new_state = Some(UiHeadState {
head: head.head,
health: hp.health(),
ammo: 1.,
reloading: None,
});
}
if new_state != target.head {

View File

@@ -10,17 +10,14 @@ use crate::{
};
use avian3d::{math::*, prelude::*};
use bevy::prelude::*;
use happy_feet::{
KinematicVelocity,
ground::{Grounding, GroundingConfig},
prelude::{
Character, CharacterDrag, CharacterFriction, CharacterGravity, CharacterMovement,
CharacterPlugin, MoveInput, SteppingBehaviour, SteppingConfig,
},
use happy_feet::prelude::{
Character, CharacterDrag, CharacterGravity, CharacterMovement, CharacterPlugins,
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
SteppingConfig,
};
pub fn plugin(app: &mut App) {
app.add_plugins(CharacterPlugin::default());
app.add_plugins(CharacterPlugins::default());
app.register_type::<MovementSpeedFactor>();
@@ -180,7 +177,7 @@ impl CharacterControllerBundle {
};
Self {
character_controller: Character { up: Dir3::Y },
character_controller: Character,
collider,
move_input: MoveInput::default(),
movement_factor: MovementSpeedFactor(1.0),
@@ -197,7 +194,7 @@ struct MovementConfig {
step: SteppingConfig,
ground: GroundingConfig,
gravity: CharacterGravity,
friction: CharacterFriction,
friction: GroundFriction,
drag: CharacterDrag,
settings: ControllerSettings,
}
@@ -208,16 +205,19 @@ const RUNNING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
acceleration: 40.0,
},
step: SteppingConfig {
max_height: 0.25,
max_vertical: 0.25,
max_horizontal: 0.4,
behaviour: SteppingBehaviour::Grounded,
max_substeps: 8,
},
ground: GroundingConfig {
max_angle: PI / 4.0,
max_distance: 0.2,
snap_to_surface: true,
up_direction: Dir3::Y,
},
gravity: CharacterGravity(vec3(0.0, -60.0, 0.0)),
friction: CharacterFriction(10.0),
gravity: CharacterGravity(Some(vec3(0.0, -60.0, 0.0))),
friction: GroundFriction(10.0),
drag: CharacterDrag(0.0),
settings: ControllerSettings {
jump_force: 25.0,
@@ -231,16 +231,19 @@ const FLYING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
acceleration: 300.0,
},
step: SteppingConfig {
max_height: 0.25,
max_vertical: 0.25,
max_horizontal: 0.4,
behaviour: SteppingBehaviour::Never,
max_substeps: 8,
},
ground: GroundingConfig {
max_angle: 0.0,
max_distance: -1.0,
snap_to_surface: false,
up_direction: Dir3::Y,
},
gravity: CharacterGravity(Vec3::ZERO),
friction: CharacterFriction(0.0),
gravity: CharacterGravity(Some(Vec3::ZERO)),
friction: GroundFriction(0.0),
drag: CharacterDrag(10.0),
settings: ControllerSettings {
jump_force: 0.0,

View File

@@ -89,14 +89,14 @@ impl ActiveHeads {
continue;
}
if let Some(head) = head {
if head.health < head.health_max {
head.health = head
.health
.saturating_add(heal_amount)
.clamp(0, head.health_max);
healed = true;
}
if let Some(head) = head
&& head.health < head.health_max
{
head.health = head
.health
.saturating_add(heal_amount)
.clamp(0, head.health_max);
healed = true;
}
}

View File

@@ -24,6 +24,7 @@ pub mod npc;
pub mod physics_layers;
pub mod platforms;
pub mod player;
pub mod protocol;
pub mod sounds;
pub mod steam;
pub mod tb_entities;

View File

@@ -79,7 +79,8 @@ fn move_active(
let t = (elapsed - active.start_time) / active.duration;
transform.rotation = active.start.rotation.lerp(active.target.rotation, t);
} else {
*transform = active.target;
transform.translation = active.target.translation;
transform.rotation = active.target.rotation;
commands.entity(e).remove::<(ActiveMovable, Movable)>();
}
}

View File

@@ -65,10 +65,10 @@ fn on_spawn_check(
}
for (e, spawn, transform) in query.iter() {
if let Some(order) = spawn.spawn_order {
if order > spawning.spawn_index {
continue;
}
if let Some(order) = spawn.spawn_order
&& order > spawning.spawn_index
{
continue;
}
let id = names[&spawn.head];

View File

@@ -0,0 +1,41 @@
use crate::{global_observer, loading_assets::GameAssets};
use bevy::prelude::*;
use lightyear::prelude::AppComponentExt;
use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) {
app.register_component::<Transform>();
app.register_component::<GltfSceneRoot>();
global_observer!(app, spawn_gltf_scene_roots);
}
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
pub enum GltfSceneRoot {
Projectile(String),
}
fn spawn_gltf_scene_roots(
trigger: Trigger<OnAdd, GltfSceneRoot>,
mut commands: Commands,
gltf_roots: Query<&GltfSceneRoot>,
assets: Res<GameAssets>,
gltfs: Res<Assets<Gltf>>,
) -> Result {
let root = gltf_roots.get(trigger.target())?;
let (gltf, index) = match root {
GltfSceneRoot::Projectile(addr) => (
assets.projectiles[format!("{addr}.glb").as_str()].clone(),
0,
),
};
let gltf = gltfs.get(&gltf).unwrap();
let scene = gltf.scenes[index].clone();
commands.entity(trigger.target()).insert(SceneRoot(scene));
Ok(())
}

View File

@@ -0,0 +1,21 @@
use bevy::ecs::{
bundle::Bundle, resource::Resource, system::EntityCommands, world::EntityWorldMut,
};
#[derive(Default, Resource)]
pub struct IsServer;
pub trait CommandExt {
fn insert_server(&mut self, bundle: impl Bundle) -> &mut Self;
}
impl<'w> CommandExt for EntityCommands<'w> {
fn insert_server(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue(|mut entity: EntityWorldMut| {
if entity.world().contains_resource::<IsServer>() {
entity.insert(bundle);
}
});
self
}
}

View File

@@ -1,9 +1,15 @@
pub mod auto_rotate;
pub mod billboards;
pub mod commands;
pub mod explosions;
pub mod observers;
pub mod sprite_3d_animation;
pub mod squish_animation;
pub mod trail;
use bevy::prelude::*;
pub(crate) use observers::global_observer;
pub fn plugin(app: &mut App) {
app.add_plugins(observers::plugin);
}

View File

@@ -18,7 +18,7 @@ dbg-server:
RUST_BACKTRACE=1 cargo r --bin server --features dbg
sort:
cargo sort
cargo sort --workspace
check:
cargo sort --check --workspace