Bevy 0.17 Migration Final PR (#76)
* Get bevy 0.17 compiling and running (#72) * get bevy 0.17 compiling and running * try to fix CI breaking from const assertion for client/server features * fix `bin` -> `lib` for `shared` in CI * typo * fix some collider issues (#73) * Physics/controller improvements (#74) * trying to fix physics prediction * fixed prediction desync * substantial controller improvements * Finish off main bevy 0.17 migration (#75) * fix lookdir issues - airplane moving backwards - player model facing backwards - camera was technically backwards the whole time, and player models were facing the right way; camera is now facing forwards - firing without a target now respects lookdir * fix aim targeting * migrate to bevy_trenchbroom 0.10 crates release * fixed colliders not being adjusted out of worldspace * predict platforms to stop constant rollbacks while riding them * fix key/head drop visuals not working * Fix key/head drop random initial force * fixed static head drops duplicating * fix platform velocity inheritance * fix thrown projectiles not autorotating * fix inconsistent explosion animations * update avian3d to 0.4.1 * fix controller snapping to fixed angle upon switching heads * clean up commented code * fix broken physics positions * Clean comments, fix warnings (#77) * clean comments, fix warnings * fix missing import * steamworks 162 libs * fix mouselook --------- Co-authored-by: extrawurst <mail@rusticorn.com>
This commit is contained in:
@@ -11,14 +11,14 @@ dbg = ["avian3d/debug-plugin", "shared/dbg"]
|
||||
avian3d = { workspace = true }
|
||||
bevy = { workspace = true, default-features = false }
|
||||
bevy-steamworks = { workspace = true }
|
||||
bevy-ui-gradients = { workspace = true }
|
||||
bevy_common_assets = { workspace = true }
|
||||
bevy_sprite3d = { workspace = true }
|
||||
bevy_trenchbroom = { workspace = true }
|
||||
bevy_trenchbroom_avian = { workspace = true }
|
||||
clap = { version = "=4.5.47", features = ["derive"] }
|
||||
happy_feet = { workspace = true }
|
||||
lightyear = { workspace = true }
|
||||
lightyear_avian3d = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
ron = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
shared = { workspace = true }
|
||||
|
||||
115
crates/server/src/head_drop.rs
Normal file
115
crates/server/src/head_drop.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
|
||||
prelude::*,
|
||||
};
|
||||
use lightyear::prelude::{NetworkTarget, Replicate};
|
||||
use shared::{
|
||||
global_observer,
|
||||
head_drop::{HeadCollected, HeadDrop, HeadDropEnableTime, HeadDrops, SecretHeadMarker},
|
||||
heads_database::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
player::Player,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
utils::{
|
||||
billboards::Billboard, one_shot_force::OneShotForce, squish_animation::SquishAnimation,
|
||||
},
|
||||
};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_head_drop);
|
||||
}
|
||||
|
||||
fn on_head_drop(
|
||||
trigger: On<HeadDrops>,
|
||||
mut commands: Commands,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
time: Res<Time>,
|
||||
) -> Result<(), BevyError> {
|
||||
let drop = trigger.event();
|
||||
let should_impulse = drop.impulse;
|
||||
|
||||
let angle = rand::random::<f32>() * PI * 2.;
|
||||
let spawn_dir = Quat::from_rotation_y(angle) * Vec3::new(0.5, 0.6, 0.).normalize();
|
||||
let spawn_force = if should_impulse {
|
||||
spawn_dir * 180.0 / time.delta_secs()
|
||||
} else {
|
||||
Vec3::ZERO
|
||||
};
|
||||
|
||||
if drop.impulse {
|
||||
commands.trigger(PlaySound::HeadDrop);
|
||||
}
|
||||
|
||||
let mesh_addr = format!("{:?}", heads_db.head_stats(drop.head_id).ability).to_lowercase();
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Name::new("headdrop"),
|
||||
Transform::from_translation(drop.pos),
|
||||
Visibility::default(),
|
||||
Collider::sphere(1.5),
|
||||
LockedAxes::ROTATION_LOCKED,
|
||||
RigidBody::Dynamic,
|
||||
OneShotForce(spawn_force),
|
||||
CollisionLayers::new(
|
||||
GameLayer::CollectiblePhysics,
|
||||
LayerMask::ALL & !GameLayer::Player.to_bits(),
|
||||
),
|
||||
Restitution::new(0.6),
|
||||
Children::spawn(SpawnWith({
|
||||
let head_id = drop.head_id;
|
||||
let now = time.elapsed_secs();
|
||||
move |parent: &mut RelatedSpawner<ChildOf>| {
|
||||
parent
|
||||
.spawn((
|
||||
Collider::sphere(1.5),
|
||||
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::NONE),
|
||||
Sensor,
|
||||
CollisionEventsEnabled,
|
||||
HeadDrop { head_id },
|
||||
HeadDropEnableTime(now + 1.2),
|
||||
))
|
||||
.observe(on_collect_head);
|
||||
}
|
||||
})),
|
||||
Replicate::to_clients(NetworkTarget::All),
|
||||
))
|
||||
.with_child((
|
||||
Billboard::All,
|
||||
SquishAnimation(2.6),
|
||||
GltfSceneRoot::HeadDrop(mesh_addr),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_collect_head(
|
||||
trigger: On<CollisionStart>,
|
||||
mut commands: Commands,
|
||||
query_player: Query<&Player>,
|
||||
query_collectable: Query<(&HeadDrop, &ChildOf)>,
|
||||
query_secret: Query<&SecretHeadMarker>,
|
||||
) {
|
||||
let collectable = trigger.event().collider1;
|
||||
let collider = trigger.event().collider2;
|
||||
|
||||
if query_player.contains(collider) {
|
||||
let (drop, child_of) = query_collectable.get(collectable).unwrap();
|
||||
|
||||
let is_secret = query_secret.contains(collectable);
|
||||
|
||||
if is_secret {
|
||||
commands.trigger(PlaySound::SecretHeadCollect);
|
||||
} else {
|
||||
commands.trigger(PlaySound::HeadCollect);
|
||||
}
|
||||
|
||||
commands.entity(collider).trigger(|entity| HeadCollected {
|
||||
head: drop.head_id,
|
||||
entity,
|
||||
});
|
||||
commands.entity(child_of.parent()).despawn();
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,15 @@ use bevy::{app::plugin_group, core_pipeline::tonemapping::Tonemapping, prelude::
|
||||
use bevy_common_assets::ron::RonAssetPlugin;
|
||||
use bevy_sprite3d::Sprite3dPlugin;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
use bevy_ui_gradients::UiGradientsPlugin;
|
||||
use bevy_trenchbroom_avian::AvianPhysicsBackend;
|
||||
use lightyear::prelude::server::ServerPlugins;
|
||||
use shared::{DebugVisuals, GameState, heads_database::HeadDatabaseAsset};
|
||||
use std::time::Duration;
|
||||
|
||||
mod backpack;
|
||||
mod config;
|
||||
mod head_drop;
|
||||
mod platforms;
|
||||
mod player;
|
||||
mod server;
|
||||
mod tb_entities;
|
||||
@@ -31,8 +33,11 @@ plugin_group! {
|
||||
bevy::app:::TerminalCtrlCHandlerPlugin,
|
||||
bevy::asset:::AssetPlugin,
|
||||
bevy::scene:::ScenePlugin,
|
||||
bevy::mesh:::MeshPlugin,
|
||||
bevy::light:::LightPlugin,
|
||||
bevy::camera:::CameraPlugin,
|
||||
bevy::render:::RenderPlugin,
|
||||
bevy::render::texture:::ImagePlugin,
|
||||
bevy::image:::ImagePlugin,
|
||||
bevy::render::pipelined_rendering:::PipelinedRenderingPlugin,
|
||||
bevy::core_pipeline:::CorePipelinePlugin,
|
||||
bevy::sprite:::SpritePlugin,
|
||||
@@ -69,15 +74,20 @@ fn main() {
|
||||
..default()
|
||||
}));
|
||||
|
||||
app.add_plugins(
|
||||
PhysicsPlugins::default()
|
||||
.build()
|
||||
// FrameInterpolation handles interpolating Position and Rotation
|
||||
.disable::<PhysicsInterpolationPlugin>(),
|
||||
);
|
||||
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(
|
||||
TrenchBroomConfig::new("hedz").icon(None),
|
||||
));
|
||||
app.add_plugins(UiGradientsPlugin);
|
||||
app.add_plugins(TrenchBroomPhysicsPlugin::new(AvianPhysicsBackend));
|
||||
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
|
||||
|
||||
app.add_plugins(shared::abilities::plugin);
|
||||
@@ -116,6 +126,8 @@ fn main() {
|
||||
|
||||
app.add_plugins(backpack::plugin);
|
||||
app.add_plugins(config::plugin);
|
||||
app.add_plugins(head_drop::plugin);
|
||||
app.add_plugins(platforms::plugin);
|
||||
app.add_plugins(player::plugin);
|
||||
app.add_plugins(server::plugin);
|
||||
app.add_plugins(tb_entities::plugin);
|
||||
|
||||
41
crates/server/src/platforms.rs
Normal file
41
crates/server/src/platforms.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_trenchbroom::prelude::Target;
|
||||
use lightyear::prelude::{NetworkTarget, PredictionTarget};
|
||||
use shared::{
|
||||
GameState,
|
||||
platforms::ActivePlatform,
|
||||
tb_entities::{Platform, PlatformTarget},
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), init);
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn init(
|
||||
mut commands: Commands,
|
||||
uninit_platforms: Query<
|
||||
(Entity, &Target, &Transform),
|
||||
(Without<ActivePlatform>, With<Platform>),
|
||||
>,
|
||||
targets: Query<(&PlatformTarget, &Transform)>,
|
||||
) {
|
||||
for (e, target, transform) in uninit_platforms.iter() {
|
||||
let Some(target) = targets
|
||||
.iter()
|
||||
.find(|(t, _)| t.targetname == target.target.clone().unwrap_or_default())
|
||||
.map(|(_, t)| t.translation)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let platform = ActivePlatform {
|
||||
start: transform.translation,
|
||||
target,
|
||||
};
|
||||
|
||||
commands
|
||||
.entity(e)
|
||||
.insert((platform, PredictionTarget::to_clients(NetworkTarget::All)));
|
||||
}
|
||||
}
|
||||
@@ -82,11 +82,11 @@ pub fn spawn(
|
||||
}
|
||||
|
||||
fn on_kill(
|
||||
trigger: Trigger<Kill>,
|
||||
trigger: On<Kill>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
|
||||
) {
|
||||
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.target()) else {
|
||||
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -100,10 +100,10 @@ fn on_kill(
|
||||
}
|
||||
|
||||
fn on_update_head_mesh(
|
||||
trigger: Trigger<HeadChanged>,
|
||||
trigger: On<HeadChanged>,
|
||||
mut commands: Commands,
|
||||
mesh_children: Single<&Children, With<PlayerBodyMesh>>,
|
||||
mut sender: Single<&mut TriggerSender<ClientHeadChanged>>,
|
||||
mut sender: Single<&mut EventSender<ClientHeadChanged>>,
|
||||
animated_characters: Query<&AnimatedCharacter>,
|
||||
mut player: Single<&mut ActiveHead, With<Player>>,
|
||||
) -> Result {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::config::ServerConfig;
|
||||
use bevy::prelude::*;
|
||||
use bevy::{ecs::component::Components, prelude::*};
|
||||
use lightyear::{
|
||||
connection::client::PeerMetadata,
|
||||
link::LinkConditioner,
|
||||
@@ -42,11 +42,11 @@ pub fn plugin(app: &mut App) {
|
||||
struct ClientPlayerId(u8);
|
||||
|
||||
fn handle_new_client(
|
||||
trigger: Trigger<OnAdd, Linked>,
|
||||
trigger: On<Add, Linked>,
|
||||
mut commands: Commands,
|
||||
id: Query<&PeerAddr>,
|
||||
) -> Result {
|
||||
let Ok(id) = id.get(trigger.target()) else {
|
||||
let Ok(id) = id.get(trigger.event().entity) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ fn handle_new_client(
|
||||
incoming_loss: 0.0,
|
||||
});
|
||||
|
||||
commands.entity(trigger.target()).insert((
|
||||
commands.entity(trigger.event().entity).insert((
|
||||
ReplicationSender::default(),
|
||||
Link::new(Some(conditioner)),
|
||||
ClientPlayerId(0),
|
||||
@@ -68,18 +68,18 @@ fn handle_new_client(
|
||||
}
|
||||
|
||||
fn on_client_connected(
|
||||
trigger: Trigger<OnAdd, ClientOf>,
|
||||
trigger: On<Add, ClientOf>,
|
||||
mut assign_player: Query<(&ClientPlayerId, &mut MessageSender<AssignClientPlayer>)>,
|
||||
) -> Result {
|
||||
// `Linked` happens before the `ClientOf` and `MessageSender` components are added, so the server can't
|
||||
// send the client player id until now.
|
||||
let (id, mut sender) = assign_player.get_mut(trigger.target())?;
|
||||
let (id, mut sender) = assign_player.get_mut(trigger.event().entity)?;
|
||||
sender.send::<UnorderedReliableChannel>(AssignClientPlayer(id.0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_client_playing(
|
||||
trigger: Trigger<RemoteTrigger<ClientEnteredPlaying>>,
|
||||
trigger: On<RemoteEvent<ClientEnteredPlaying>>,
|
||||
commands: Commands,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
@@ -95,9 +95,9 @@ fn on_client_playing(
|
||||
}
|
||||
|
||||
fn close_on_disconnect(
|
||||
_trigger: Trigger<OnRemove, Connected>,
|
||||
_trigger: On<Remove, Connected>,
|
||||
config: Res<ServerConfig>,
|
||||
mut writer: EventWriter<AppExit>,
|
||||
mut writer: MessageWriter<AppExit>,
|
||||
) {
|
||||
if config.close_on_client_disconnect {
|
||||
info!("client disconnected, exiting");
|
||||
@@ -123,7 +123,13 @@ fn start_server(mut commands: Commands) -> Result {
|
||||
}),
|
||||
Link::new(Some(conditioner)),
|
||||
));
|
||||
commands.trigger(server::Start);
|
||||
commands
|
||||
.observe(|on: On<Add>, components: &Components| {
|
||||
for &comp in on.trigger().components {
|
||||
info!("Added {} to server", components.get_name(comp).unwrap());
|
||||
}
|
||||
})
|
||||
.trigger(|entity| server::Start { entity });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -142,7 +148,11 @@ fn setup_timeout_timer(mut commands: Commands, config: Res<ServerConfig>) {
|
||||
commands.insert_resource(TimeoutTimer(config.timeout));
|
||||
}
|
||||
|
||||
fn run_timeout(mut timer: ResMut<TimeoutTimer>, mut writer: EventWriter<AppExit>, time: Res<Time>) {
|
||||
fn run_timeout(
|
||||
mut timer: ResMut<TimeoutTimer>,
|
||||
mut writer: MessageWriter<AppExit>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
timer.0 -= time.delta_secs();
|
||||
|
||||
if timer.0 <= 0.0 {
|
||||
@@ -151,7 +161,7 @@ fn run_timeout(mut timer: ResMut<TimeoutTimer>, mut writer: EventWriter<AppExit>
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_timeout(_trigger: Trigger<OnAdd, Connected>, mut timer: ResMut<TimeoutTimer>) {
|
||||
fn cancel_timeout(_trigger: On<Add, Connected>, mut timer: ResMut<TimeoutTimer>) {
|
||||
info!("client connected, cancelling timeout");
|
||||
timer.0 = f32::INFINITY;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ pub fn plugin(app: &mut App) {
|
||||
}
|
||||
|
||||
fn add_despawned_entities_to_cache(
|
||||
trigger: Trigger<OnRemove, TbMapEntityId>,
|
||||
trigger: On<Remove, TbMapEntityId>,
|
||||
id: Query<&TbMapEntityId>,
|
||||
mut cache: ResMut<DespawnedTbEntityCache>,
|
||||
) {
|
||||
cache.0.push(id.get(trigger.target()).unwrap().id);
|
||||
cache.0.push(id.get(trigger.event().entity).unwrap().id);
|
||||
}
|
||||
|
||||
#[derive(Default, Resource, Reflect)]
|
||||
@@ -32,11 +32,11 @@ fn add_despawned_entities_to_cache(
|
||||
pub struct DespawnedTbEntityCache(pub Vec<u64>);
|
||||
|
||||
fn send_new_client_despawned_cache(
|
||||
trigger: Trigger<OnAdd, Connected>,
|
||||
trigger: On<Add, Connected>,
|
||||
cache: Res<DespawnedTbEntityCache>,
|
||||
mut send: Query<&mut MessageSender<DespawnTbMapEntity>>,
|
||||
) {
|
||||
let mut send = send.get_mut(trigger.target()).unwrap();
|
||||
let mut send = send.get_mut(trigger.event().entity).unwrap();
|
||||
for &id in cache.0.iter() {
|
||||
send.send::<UnorderedReliableChannel>(DespawnTbMapEntity(id));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn plugin(app: &mut App) {
|
||||
pub struct ReportEntityComponents(pub Entity);
|
||||
|
||||
fn report_entity_components(
|
||||
trigger: Trigger<ReportEntityComponents>,
|
||||
trigger: On<ReportEntityComponents>,
|
||||
entities: &Entities,
|
||||
components: &Components,
|
||||
archetypes: &Archetypes,
|
||||
@@ -27,7 +27,7 @@ fn report_entity_components(
|
||||
};
|
||||
|
||||
let mut output = format!("Entity {:?} Components: ", trigger.event().0);
|
||||
for component in archetype.components() {
|
||||
for &component in archetype.components() {
|
||||
if let Some(name) = components.get_name(component) {
|
||||
output.push_str(&format!("{name}, "));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user