use crate::{ GameState, cash::{Cash, CashCollectEvent}, character::{AnimatedCharacter, Character}, global_observer, head::ActiveHead, heads::HeadChanged, heads_database::{HeadControls, HeadsDatabase}, loading_assets::AudioAssets, sounds::PlaySound, }; use avian3d::prelude::*; use bevy::{ input::common_conditions::input_just_pressed, prelude::*, window::{CursorGrabMode, PrimaryWindow}, }; use serde::{Deserialize, Serialize}; #[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[require(Character)] pub struct Player; #[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[require(Transform, Visibility)] pub struct PlayerBodyMesh; pub fn plugin(app: &mut App) { app.add_systems(Startup, (toggle_cursor_system, cursor_recenter)); app.add_systems( Update, ( collect_cash, setup_animations_marker_for_player, toggle_cursor_system.run_if(input_just_pressed(KeyCode::Escape)), ) .run_if(in_state(GameState::Playing)), ); global_observer!(app, on_update_head_mesh); } fn cursor_recenter(q_windows: Single<&mut Window, With>) { let mut primary_window = q_windows; let center = Vec2::new( primary_window.resolution.width() / 2., primary_window.resolution.height() / 2., ); primary_window.set_cursor_position(Some(center)); } fn toggle_grab_cursor(window: &mut Window) { match window.cursor_options.grab_mode { CursorGrabMode::None => { window.cursor_options.grab_mode = CursorGrabMode::Confined; window.cursor_options.visible = false; } _ => { window.cursor_options.grab_mode = CursorGrabMode::None; window.cursor_options.visible = true; } } } fn toggle_cursor_system(mut window: Single<&mut Window, With>) { toggle_grab_cursor(&mut window); } fn collect_cash( mut commands: Commands, mut collision_event_reader: EventReader, query_player: Query<&Player>, query_cash: Query<&Cash>, ) { for CollisionStarted(e1, e2) in collision_event_reader.read() { let collect = if query_player.contains(*e1) && query_cash.contains(*e2) { Some(*e2) } else if query_player.contains(*e2) && query_cash.contains(*e1) { Some(*e1) } else { None }; if let Some(cash) = collect { commands.trigger(CashCollectEvent); commands.entity(cash).despawn(); } } } fn setup_animations_marker_for_player( mut commands: Commands, animation_handles: Query>, child_of: Query<&ChildOf>, player_rig: Query<&ChildOf, With>, ) { for animation_rig in animation_handles.iter() { for ancestor in child_of.iter_ancestors(animation_rig) { if let Ok(rig_child_of) = player_rig.get(ancestor) { commands.entity(rig_child_of.parent()); return; } } } } fn on_update_head_mesh( trigger: Trigger, mut commands: Commands, body_mesh: Single<(Entity, &Children), With>, animated_characters: Query<&AnimatedCharacter>, mut player: Single<&mut ActiveHead, With>, head_db: Res, audio_assets: Res, sfx: Query<&AudioPlayer>, ) -> Result { let (body_mesh, mesh_children) = *body_mesh; let animated_char = mesh_children .iter() .find(|child| animated_characters.contains(*child)) .ok_or("tried to update head mesh before AnimatedCharacter was readded")?; player.0 = trigger.0; let head_str = head_db.head_key(trigger.0); commands.trigger(PlaySound::Head(head_str.to_string())); commands .entity(animated_char) .insert(AnimatedCharacter::new(trigger.0)); //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(trigger.0).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(()) }