* switch to events for instantaneous inputs * input simplification + improvements * fix trail crash * fix clippy warnings * qualify `Trail` in fn signature
96 lines
3.0 KiB
Rust
96 lines
3.0 KiB
Rust
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::<PlayerAssignmentState>();
|
|
|
|
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<AssignClientPlayer>,
|
|
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
|
mut local_id: Local<Option<PlayerId>>,
|
|
players: Query<(Entity, &PlayerId), Changed<PlayerId>>,
|
|
) {
|
|
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<ClientHeadChanged>,
|
|
mut commands: Commands,
|
|
body_mesh: Single<(Entity, &Children), With<PlayerBodyMesh>>,
|
|
head_db: Res<HeadsDatabase>,
|
|
audio_assets: Res<AudioAssets>,
|
|
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(())
|
|
}
|