Files
HEDZReloaded/src/player.rs
2025-04-29 00:14:25 +02:00

229 lines
6.6 KiB
Rust

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<AssetServer>,
query: Query<&Transform, With<SpawnPoint>>,
heads_db: Res<HeadsDatabase>,
) {
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),
))
.with_children(|parent| {
parent
.spawn((
Name::new("player-rig"),
Transform::default(),
Visibility::default(),
PlayerBodyMesh,
CameraArmRotation,
))
.with_child(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<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(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<PrimaryWindow>>) {
toggle_grab_cursor(&mut window);
}
fn collect_cash(
mut commands: Commands,
mut collision_event_reader: EventReader<CollisionStarted>,
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<Entity, Added<AnimationGraphHandle>>,
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<PlayerAnimations>,
>,
movement: Res<PlayerMovement>,
) {
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<HeadChanged>,
mut commands: Commands,
body_mesh: Single<Entity, With<PlayerBodyMesh>>,
mut player: Single<&mut ActiveHead, With<Player>>,
head_db: Res<HeadsDatabase>,
audio_assets: Res<AudioAssets>,
) {
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::<Children>();
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()
},
));
}
}