use crate::{ GameState, cash::{Cash, CashCollectEvent}, character::HedzCharacter, protocol::PlayerId, }; use avian3d::prelude::*; use bevy::{ input::common_conditions::input_just_pressed, prelude::*, window::{CursorGrabMode, CursorOptions, PrimaryWindow}, }; use happy_feet::debug::DebugInput; use serde::{Deserialize, Serialize}; #[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[require(HedzCharacter, DebugInput = DebugInput)] pub struct Player; #[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[require(Transform, Visibility)] pub struct PlayerBodyMesh; /// Server-side only; inserted on each `client` (not the controller) to track player ids. #[derive(Component, Clone, Copy)] pub struct ClientPlayerId(pub PlayerId); /// Client-side only; stores this client's id #[derive(Resource, Reflect)] #[reflect(Resource)] pub struct LocalPlayerId { pub id: PlayerId, } pub fn plugin(app: &mut App) { app.add_systems( OnEnter(GameState::Playing), (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)), ); } 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(options: &mut CursorOptions) { match options.grab_mode { CursorGrabMode::None => { options.grab_mode = CursorGrabMode::Confined; options.visible = false; } _ => { options.grab_mode = CursorGrabMode::None; options.visible = true; } } } fn toggle_cursor_system(mut window: Single<&mut CursorOptions, With>) { toggle_grab_cursor(&mut window); } fn collect_cash( mut commands: Commands, mut collision_message_reader: MessageReader, query_player: Query<&Player>, query_cash: Query<&Cash>, ) { for CollisionStart { collider1: e1, collider2: e2, .. } in collision_message_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; } } } }