use crate::{ GameState, camera::{CameraArmRotation, CameraTarget}, cash::{Cash, CashCollectEvent}, character::{AnimatedCharacter, CharacterAnimations}, control::controller_common::{CharacterControllerBundle, PlayerMovement}, global_observer, head::ActiveHead, heads::{ActiveHeads, HeadChanged, HeadState}, heads_database::{HeadControls, HeadsDatabase}, hitpoints::Hitpoints, loading_assets::AudioAssets, physics_layers::GameLayer, sounds::PlaySound, tb_entities::SpawnPoint, }; use avian3d::{math::Vector, prelude::*}; use bevy::{ input::common_conditions::input_just_pressed, prelude::*, window::{CursorGrabMode, PrimaryWindow}, }; use std::time::Duration; #[derive(Component, Default)] pub struct Player; #[derive(Component, Default)] struct PlayerAnimations; #[derive(Component, Default)] pub struct PlayerBodyMesh; pub fn plugin(app: &mut App) { app.add_systems(Startup, (toggle_cursor_system, cursor_recenter)); app.add_systems(OnEnter(GameState::Playing), spawn); app.add_systems( Update, ( collect_cash, toggle_animation, 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); } fn spawn( mut commands: Commands, asset_server: Res, query: Query<&Transform, With>, heads_db: Res, ) { let Some(spawn) = query.iter().next() else { return; }; let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.)); let gravity = Vector::NEG_Y * 40.0; let collider = Collider::capsule(0.9, 1.2); commands.spawn(( Name::from("player"), Player, ActiveHead(0), ActiveHeads::new([ Some(HeadState::new(0, heads_db.as_ref())), Some(HeadState::new(3, heads_db.as_ref())), Some(HeadState::new(6, heads_db.as_ref())), Some(HeadState::new(10, heads_db.as_ref())), Some(HeadState::new(9, heads_db.as_ref())), ]), Hitpoints::new(100), CameraTarget, transform, Visibility::default(), // LockedAxes::ROTATION_LOCKED, todo CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL), CharacterControllerBundle::new(collider, gravity), children![( Name::new("player-rig"), Transform::default(), Visibility::default(), PlayerBodyMesh, CameraArmRotation, children![AnimatedCharacter(0)] )], )); commands.spawn(( AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")), PlaybackSettings::DESPAWN, )); } 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>, children: Query<&ChildOf>, player: Query<&PlayerBodyMesh>, ) { for entity in animation_handles.iter() { for ancestor in children.iter_ancestors(entity) { if player.contains(ancestor) { commands.entity(entity).insert(PlayerAnimations); return; } } } } fn toggle_animation( mut transitions: Query< ( &mut AnimationTransitions, &mut AnimationPlayer, &CharacterAnimations, ), With, >, movement: Res, ) { if movement.is_changed() { for (mut transition, mut player, animations) in &mut transitions { let index = if movement.any_direction { animations.run } else { animations.idle }; transition .play(&mut player, index, Duration::from_millis(100)) .repeat(); } } } fn on_update_head( trigger: Trigger, mut commands: Commands, body_mesh: Single>, mut player: Single<&mut ActiveHead, With>, head_db: Res, audio_assets: Res, ) { let body_mesh = *body_mesh; player.0 = trigger.0; let head_str = head_db.head_key(trigger.0); commands.trigger(PlaySound::Head(head_str.to_string())); commands.entity(body_mesh).despawn_related::(); commands .entity(body_mesh) .with_child(AnimatedCharacter(trigger.0)); //TODO: make part of full character mesh later 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() }, )); } }