use crate::{ global_observer, heads_database::{HeadControls, HeadsDatabase}, loading_assets::AudioAssets, }; use bevy::prelude::*; use shared::{ player::{LocalPlayer, PlayerBodyMesh}, protocol::{PlaySound, PlayerId, events::ClientHeadChanged, messages::AssignClientPlayer}, }; pub fn plugin(app: &mut App) { app.init_state::(); app.add_systems( Update, receive_player_id.run_if(not(in_state(PlayerAssignmentState::Confirmed))), ); global_observer!(app, on_update_head_mesh); } fn receive_player_id( mut commands: Commands, mut client_assignments: MessageReader, mut next: ResMut>, mut local_id: Local>, players: Query<(Entity, &PlayerId), Changed>, ) { for &AssignClientPlayer(id) in client_assignments.read() { info!("player id `{}` received", id.id); *local_id = Some(id); } if let Some(local_id) = *local_id { for (entity, player_id) in players.iter() { if *player_id == local_id { commands.entity(entity).insert(LocalPlayer); next.set(PlayerAssignmentState::Confirmed); info!( "player entity {entity:?} confirmed with id `{}`", player_id.id ); break; } } } } /// Various states while trying to assign and match an ID to the player character. /// Every client is given an ID (its player index in the match) and every character controller /// is given an ID matching the client controlling it. This way the client can easily see which /// controller it owns. #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, States)] pub enum PlayerAssignmentState { /// Waiting for the server to send an [`AssignClientPlayer`] message and replicate a [`PlayerId`] #[default] Waiting, /// Matching controller confirmed; a [`LocalPlayer`] exists Confirmed, } fn on_update_head_mesh( trigger: On, mut commands: Commands, body_mesh: Single<(Entity, &Children), With>, head_db: Res, audio_assets: Res, sfx: Query<&AudioPlayer>, ) -> Result { let head = trigger.0 as usize; let (body_mesh, mesh_children) = *body_mesh; let head_str = head_db.head_key(head); commands.trigger(PlaySound::Head(head_str.to_string())); //TODO: make part of full character mesh later for child in mesh_children.iter().filter(|child| sfx.contains(*child)) { commands.entity(child).despawn(); } if head_db.head_stats(head).controls == HeadControls::Plane { commands.entity(body_mesh).with_child(( Name::new("sfx"), AudioPlayer::new(audio_assets.jet.clone()), PlaybackSettings { mode: bevy::audio::PlaybackMode::Loop, ..Default::default() }, )); } Ok(()) }