Files
HEDZReloaded/crates/client/src/player.rs
PROMETHIA-27 668ed93475 Simpler + Better Inputs (#82)
* switch to events for instantaneous inputs

* input simplification + improvements

* fix trail crash

* fix clippy warnings

* qualify `Trail` in fn signature
2025-12-10 14:41:21 -05:00

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