use crate::{ global_observer, heads_database::{HeadControls, HeadsDatabase}, loading_assets::AudioAssets, }; use bevy::prelude::*; use lightyear::prelude::MessageReceiver; use shared::{ player::PlayerBodyMesh, protocol::{PlaySound, PlayerId, events::ClientHeadChanged, messages::AssignClientPlayer}, }; pub fn plugin(app: &mut App) { app.register_type::(); app.register_type::(); app.init_state::(); app.add_systems( Update, receive_player_id.run_if(in_state(PlayerAssignmentState::Waiting)), ); app.add_systems( Update, match_player_id.run_if(in_state(PlayerAssignmentState::IdReceived)), ); global_observer!(app, on_update_head_mesh); } #[derive(Resource, Reflect)] #[reflect(Resource)] pub struct ClientPlayerId { id: u8, } fn receive_player_id( mut commands: Commands, mut recv: Single<&mut MessageReceiver>, mut next: ResMut>, ) { for AssignClientPlayer(id) in recv.receive() { commands.insert_resource(ClientPlayerId { id }); next.set(PlayerAssignmentState::IdReceived); info!("player id `{id}` received"); } } fn match_player_id( mut commands: Commands, players: Query<(Entity, &PlayerId), Changed>, client: Res, mut next: ResMut>, ) { for (entity, player) in players.iter() { if player.id == client.id { commands.entity(entity).insert(LocalPlayer); next.set(PlayerAssignmentState::Confirmed); info!("player entity {entity:?} confirmed with id `{}`", player.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 #[default] Waiting, /// Received an [`AssignClientPlayer`], querying for a matching controller IdReceived, /// Matching controller confirmed; a [`LocalPlayer`] exists Confirmed, } #[derive(Component, Debug, Reflect)] #[reflect(Component)] pub struct LocalPlayer; fn on_update_head_mesh( trigger: Trigger, 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(()) }