190 lines
5.1 KiB
Rust
190 lines
5.1 KiB
Rust
use crate::{
|
|
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
|
|
loading_assets::HeadDropAssets, physics_layers::GameLayer, player::Player, sounds::PlaySound,
|
|
squish_animation::SquishAnimation, tb_entities::SecretHead,
|
|
};
|
|
use avian3d::prelude::*;
|
|
use bevy::{
|
|
ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
|
|
prelude::*,
|
|
};
|
|
use std::f32::consts::PI;
|
|
|
|
#[derive(Event, Reflect)]
|
|
pub struct HeadDrops {
|
|
pos: Vec3,
|
|
head_id: usize,
|
|
impulse: bool,
|
|
}
|
|
|
|
impl HeadDrops {
|
|
pub fn new(pos: Vec3, head_id: usize) -> Self {
|
|
Self {
|
|
pos,
|
|
head_id,
|
|
impulse: true,
|
|
}
|
|
}
|
|
|
|
fn new_static(pos: Vec3, head_id: usize) -> Self {
|
|
Self {
|
|
pos,
|
|
head_id,
|
|
impulse: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
struct HeadDrop {
|
|
pub head_id: usize,
|
|
}
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
struct HeadDropEnableTime(f32);
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
struct SecretHeadMarker;
|
|
|
|
#[derive(Event, Reflect)]
|
|
pub struct HeadCollected(pub usize);
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.add_systems(
|
|
Update,
|
|
enable_collectible.run_if(in_state(GameState::Playing)),
|
|
);
|
|
|
|
app.add_systems(OnEnter(GameState::Playing), spawn);
|
|
|
|
global_observer!(app, on_head_drop);
|
|
}
|
|
|
|
fn spawn(mut commands: Commands, query: Query<(Entity, &GlobalTransform, &SecretHead)>) {
|
|
for (e, t, head) in query {
|
|
commands.trigger(HeadDrops::new_static(
|
|
t.translation() + Vec3::new(0., 2., 0.),
|
|
head.head_id,
|
|
));
|
|
|
|
commands.entity(e).despawn();
|
|
}
|
|
}
|
|
|
|
fn on_head_drop(
|
|
trigger: Trigger<HeadDrops>,
|
|
mut commands: Commands,
|
|
assets: Res<HeadDropAssets>,
|
|
heads_db: Res<HeadsDatabase>,
|
|
gltf_assets: Res<Assets<Gltf>>,
|
|
time: Res<Time>,
|
|
) -> Result<(), BevyError> {
|
|
let drop = trigger.event();
|
|
|
|
let angle = rand::random::<f32>() * PI * 2.;
|
|
let spawn_dir = Quat::from_rotation_y(angle) * Vec3::new(0.5, 0.6, 0.).normalize();
|
|
|
|
if drop.impulse {
|
|
commands.trigger(PlaySound::HeadDrop);
|
|
}
|
|
|
|
let ability = format!("{:?}.glb", heads_db.head_stats(drop.head_id).ability).to_lowercase();
|
|
let mesh = if let Some(handle) = assets.meshes.get(ability.as_str()) {
|
|
gltf_assets.get(handle)
|
|
} else {
|
|
gltf_assets.get(&assets.meshes["none.glb"])
|
|
}
|
|
.ok_or("asset not found")?;
|
|
|
|
commands
|
|
.spawn((
|
|
Name::new("headdrop"),
|
|
Transform::from_translation(drop.pos),
|
|
Visibility::default(),
|
|
Collider::sphere(1.5),
|
|
LockedAxes::ROTATION_LOCKED,
|
|
RigidBody::Dynamic,
|
|
CollisionLayers::new(
|
|
GameLayer::CollectiblePhysics,
|
|
LayerMask::ALL & !GameLayer::Player.to_bits(),
|
|
),
|
|
Restitution::new(0.6),
|
|
Children::spawn(SpawnWith({
|
|
let head_id = drop.head_id;
|
|
let now = time.elapsed_secs();
|
|
move |parent: &mut RelatedSpawner<ChildOf>| {
|
|
parent
|
|
.spawn((
|
|
Collider::sphere(1.5),
|
|
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::NONE),
|
|
Sensor,
|
|
CollisionEventsEnabled,
|
|
HeadDrop { head_id },
|
|
HeadDropEnableTime(now + 1.2),
|
|
))
|
|
.observe(on_collect_head);
|
|
}
|
|
})),
|
|
))
|
|
.insert_if(
|
|
ExternalImpulse::new(spawn_dir * 180.).with_persistence(false),
|
|
|| drop.impulse,
|
|
)
|
|
.with_child((
|
|
Billboard::All,
|
|
SquishAnimation(2.6),
|
|
SceneRoot(mesh.scenes[0].clone()),
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn enable_collectible(
|
|
mut commands: Commands,
|
|
query: Query<(Entity, &HeadDropEnableTime)>,
|
|
time: Res<Time>,
|
|
) {
|
|
let now = time.elapsed_secs();
|
|
for (e, enable_time) in query.iter() {
|
|
if now > enable_time.0 {
|
|
commands
|
|
.entity(e)
|
|
.insert(CollisionLayers::new(
|
|
LayerMask(GameLayer::CollectibleSensors.to_bits()),
|
|
LayerMask::ALL,
|
|
))
|
|
.remove::<HeadDropEnableTime>();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_collect_head(
|
|
trigger: Trigger<OnCollisionStart>,
|
|
|
|
mut commands: Commands,
|
|
query_player: Query<&Player>,
|
|
query_collectable: Query<(&HeadDrop, &ChildOf)>,
|
|
query_secret: Query<&SecretHeadMarker>,
|
|
) {
|
|
let collectable = trigger.target();
|
|
let collider = trigger.collider;
|
|
|
|
if query_player.contains(collider) {
|
|
let (drop, child_of) = query_collectable.get(collectable).unwrap();
|
|
|
|
let is_secret = query_secret.contains(collectable);
|
|
|
|
if is_secret {
|
|
commands.trigger(PlaySound::SecretHeadCollect);
|
|
} else {
|
|
commands.trigger(PlaySound::HeadCollect);
|
|
}
|
|
|
|
commands.trigger(HeadCollected(drop.head_id));
|
|
commands.entity(child_of.parent()).despawn();
|
|
}
|
|
}
|