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:
PROMETHIA-27
2025-11-15 09:16:38 -05:00
committed by GitHub
parent ad1b7446e1
commit b83e506a4d
75 changed files with 2514 additions and 1831 deletions

View File

@@ -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 }

View 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();
}
}

View File

@@ -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);

View 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)));
}
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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}, "));
}