Files
HEDZReloaded/crates/shared/src/player.rs
2025-12-08 19:22:17 -05:00

119 lines
3.3 KiB
Rust

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<PrimaryWindow>>) {
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<PrimaryWindow>>) {
toggle_grab_cursor(&mut window);
}
fn collect_cash(
mut commands: Commands,
mut collision_message_reader: MessageReader<CollisionStart>,
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<Entity, Added<AnimationGraphHandle>>,
child_of: Query<&ChildOf>,
player_rig: Query<&ChildOf, With<PlayerBodyMesh>>,
) {
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;
}
}
}
}