diff --git a/assets/models/head_drop.glb b/assets/models/head_drop.glb new file mode 100644 index 0000000..0cb6682 Binary files /dev/null and b/assets/models/head_drop.glb differ diff --git a/src/backpack/backpack_ui.rs b/src/backpack/backpack_ui.rs index ca6da64..4a6f386 100644 --- a/src/backpack/backpack_ui.rs +++ b/src/backpack/backpack_ui.rs @@ -181,7 +181,7 @@ fn spawn_head_ui( top: Val::Px(0.), left: Val::Px(0.), right: Val::Px(0.), - height: Val::Percent(25.), + height: Val::Percent(0.), ..default() }, )) diff --git a/src/backpack/mod.rs b/src/backpack/mod.rs index 0f09165..fbf9287 100644 --- a/src/backpack/mod.rs +++ b/src/backpack/mod.rs @@ -2,8 +2,7 @@ mod backpack_ui; mod ui_head_state; use crate::{ - GameState, - heads::{HEAD_COUNT, HeadState}, + cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState, heads_database::HeadsDatabase, }; use bevy::prelude::*; @@ -26,21 +25,38 @@ impl Backpack { false } + + pub fn contains(&self, head_id: usize) -> bool { + self.heads.iter().any(|head| head.head == head_id) + } + + pub fn insert(&mut self, head_id: usize, heads_db: &HeadsDatabase) { + self.heads.push(HeadState::new(head_id, heads_db)); + } } #[derive(Event)] pub struct BackbackSwapEvent(pub usize); pub fn plugin(app: &mut App) { + app.init_resource::(); + app.add_plugins(backpack_ui::plugin); - app.add_systems(OnEnter(GameState::Playing), setup); + global_observer!(app, on_head_collect); } -fn setup(mut commands: Commands, heads: Res) { - commands.insert_resource(Backpack { - heads: (0usize..HEAD_COUNT) - .map(|i| HeadState::new(i, heads.as_ref())) - .collect(), - }); +fn on_head_collect( + trigger: Trigger, + mut cmds: Commands, + mut backpack: ResMut, + heads_db: Res, +) { + let HeadCollected(head) = *trigger.event(); + + if backpack.contains(head) { + cmds.trigger(CashCollectEvent); + } else { + backpack.insert(head, heads_db.as_ref()); + } } diff --git a/src/head_drop.rs b/src/head_drop.rs new file mode 100644 index 0000000..ec5da1e --- /dev/null +++ b/src/head_drop.rs @@ -0,0 +1,71 @@ +use crate::{ + GameState, billboards::Billboard, global_observer, loading_assets::GameAssets, player::Player, + squish_animation::SquishAnimation, +}; +use avian3d::prelude::*; +use bevy::prelude::*; +use std::f32::consts::PI; + +#[derive(Event, Reflect)] +pub struct HeadDrops(pub Vec3, pub usize); + +#[derive(Component, Reflect)] +#[reflect(Component)] +struct HeadDrop(pub usize); + +#[derive(Event, Reflect)] +pub struct HeadCollected(pub usize); + +pub fn plugin(app: &mut App) { + app.add_systems(Update, collect_head.run_if(in_state(GameState::Playing))); + + global_observer!(app, on_head_drop); +} + +fn on_head_drop(trigger: Trigger, mut commands: Commands, assets: Res) { + let HeadDrops(position, id) = trigger.event(); + + let angle = rand::random::() * PI * 2.; + let spawn_dir = Quat::from_rotation_y(angle) * Vec3::new(0.5, 0.6, 0.).normalize(); + + commands + .spawn(( + Name::new("headdrop"), + HeadDrop(*id), + Transform::from_translation(*position), + Visibility::default(), + Collider::sphere(1.5), + ExternalImpulse::new(spawn_dir * 180.).with_persistence(false), + LockedAxes::ROTATION_LOCKED, + RigidBody::Dynamic, + Restitution::new(0.6), + )) + .with_child(( + Billboard, + SquishAnimation(2.6), + SceneRoot(assets.mesh_head_drop.clone()), + )); +} + +fn collect_head( + mut commands: Commands, + mut collision_event_reader: EventReader, + query_player: Query<&Player>, + query_collectable: Query<&HeadDrop>, +) { + for CollisionStarted(e1, e2) in collision_event_reader.read() { + let collectable = if query_player.contains(*e1) && query_collectable.contains(*e2) { + *e2 + } else if query_player.contains(*e2) && query_collectable.contains(*e1) { + *e1 + } else { + continue; + }; + + let key = query_collectable.get(collectable).unwrap(); + + // commands.trigger(PlaySound::KeyCollect); + commands.trigger(HeadCollected(key.0)); + commands.entity(collectable).despawn_recursive(); + } +} diff --git a/src/loading_assets.rs b/src/loading_assets.rs index abd7043..acc92ec 100644 --- a/src/loading_assets.rs +++ b/src/loading_assets.rs @@ -82,6 +82,9 @@ pub struct GameAssets { #[asset(path = "models/key.glb#Scene0")] pub mesh_key: Handle, + #[asset(path = "models/head_drop.glb#Scene0")] + pub mesh_head_drop: Handle, + #[asset(path = "models/spawn.glb#Scene0")] pub mesh_spawn: Handle, diff --git a/src/main.rs b/src/main.rs index 5d30980..de9a80a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod cutscene; mod debug; mod gates; mod head; +mod head_drop; mod heads; mod heads_database; mod hitpoints; @@ -151,6 +152,7 @@ fn main() { app.add_plugins(debug::plugin); app.add_plugins(utils::observers::plugin); app.add_plugins(water::plugin); + app.add_plugins(head_drop::plugin); app.init_state::(); diff --git a/src/npc.rs b/src/npc.rs index 44c5d7e..d40518f 100644 --- a/src/npc.rs +++ b/src/npc.rs @@ -2,6 +2,7 @@ use crate::{ GameState, ai::Ai, head::ActiveHead, + head_drop::HeadDrops, heads::{ActiveHeads, HEAD_COUNT, HeadState}, heads_database::HeadsDatabase, hitpoints::{Hitpoints, Kill}, @@ -50,12 +51,14 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: R fn on_kill( trigger: Trigger, mut commands: Commands, - query: Query<(&Transform, &EnemySpawn)>, + query: Query<(&Transform, &EnemySpawn, &ActiveHead)>, ) { - let Ok((transform, enemy)) = query.get(trigger.entity()) else { + let Ok((transform, enemy, head)) = query.get(trigger.entity()) else { return; }; + commands.trigger(HeadDrops(transform.translation, head.0)); + commands.entity(trigger.entity()).despawn_recursive(); if !enemy.key.is_empty() {