Split crate into shared logic library and binary crate (#52)
This commit is contained in:
216
crates/shared/src/player.rs
Normal file
216
crates/shared/src/player.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent},
|
||||
character::AnimatedCharacter,
|
||||
control::controller_common::CharacterControllerBundle,
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
heads::{ActiveHeads, HeadChanged, HeadState},
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
loading_assets::AudioAssets,
|
||||
npc::SpawnCharacter,
|
||||
physics_layers::GameLayer,
|
||||
sounds::PlaySound,
|
||||
tb_entities::SpawnPoint,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
input::common_conditions::input_just_pressed,
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, PrimaryWindow},
|
||||
};
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[require(Transform, Visibility)]
|
||||
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,
|
||||
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 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 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(),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Player.to_bits()),
|
||||
LayerMask::ALL & !GameLayer::CollectiblePhysics.to_bits(),
|
||||
),
|
||||
CharacterControllerBundle::new(collider, heads_db.head_stats(0).controls),
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
children![AnimatedCharacter::new(0)]
|
||||
)],
|
||||
))
|
||||
.observe(on_kill);
|
||||
|
||||
commands.spawn((
|
||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||
PlaybackSettings::DESPAWN,
|
||||
));
|
||||
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
}
|
||||
|
||||
fn on_kill(
|
||||
trigger: Trigger<Kill>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
|
||||
) {
|
||||
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.target()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
commands.trigger(HeadDrops::new(transform.translation, active.0));
|
||||
|
||||
if let Some(new_head) = heads.loose_current() {
|
||||
hp.set_health(heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(new_head));
|
||||
}
|
||||
}
|
||||
|
||||
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>>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_update_head_mesh(
|
||||
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::new(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()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user