From 2dcc396666a949f8ef0bdb16caf19bab4764d431 Mon Sep 17 00:00:00 2001 From: extrawurst <776816+extrawurst@users.noreply.github.com> Date: Sun, 20 Apr 2025 11:26:06 +0200 Subject: [PATCH] Stephan/hez 2 npc should loose head if 0 health (#34) --- assets/models/head_drop.glb | Bin 0 -> 14032 bytes src/backpack/backpack_ui.rs | 2 +- src/backpack/mod.rs | 34 ++++++++++++----- src/head_drop.rs | 71 ++++++++++++++++++++++++++++++++++++ src/loading_assets.rs | 3 ++ src/main.rs | 2 + src/npc.rs | 7 +++- 7 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 assets/models/head_drop.glb create mode 100644 src/head_drop.rs diff --git a/assets/models/head_drop.glb b/assets/models/head_drop.glb new file mode 100644 index 0000000000000000000000000000000000000000..0cb6682a501c94e160c6fcf9a432bedde7a932fa GIT binary patch literal 14032 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 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() {