lifecycler 0.2.7

Bevy Game Jam #5 submission. Terminal aquarium.
Documentation
use std::f32::consts::PI;
use std::time::Duration;

use bevy::prelude::*;
use bevy::time::common_conditions::on_timer;
use rand::seq::SliceRandom;
use rand::RngCore;

use crate::pellets::Pellet;

use super::behavior::{CreatureBehavior, CreatureOperations, CreatureRng};
use super::fish_behavior::{FishOperations, FISH_MAX, FISH_SPAWN_INTERVAL_SECONDS};
use super::lifecycle::{FishMortality, FishSkeleton};

pub(super) fn plugin(app: &mut App) {
    app.add_systems(Startup, setup_fish_system)
        .add_systems(
            Update,
            (
                populate_fish_system
                    .run_if(on_timer(Duration::from_secs(FISH_SPAWN_INTERVAL_SECONDS))),
                fish_spawn_system,
                fish_behavior_system,
                fish_behavior_change_system,
                fish_pellet_detection_system.run_if(on_timer(Duration::from_secs_f32(0.5))),
            ),
        )
        .add_event::<FishSpawnEvent>();
}

#[derive(Component)]
pub struct Fish;

#[derive(Resource, Deref)]
pub struct FishMesh(Handle<Mesh>);

#[derive(Resource, Deref)]
pub struct FishMaterials(Vec<Handle<StandardMaterial>>);

#[derive(Event, Deref)]
pub struct FishSpawnEvent(pub Vec3);

fn setup_fish_system(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut spawn_events: EventWriter<FishSpawnEvent>,
    mut rng: ResMut<CreatureRng>,
) {
    let fish_mesh = asset_server.load(
        (GltfAssetLabel::Primitive {
            mesh: 0,
            primitive: 0,
        })
        .from_asset("embedded://lifecycler/../assets/fish.glb"),
    );
    commands.insert_resource(FishMesh(fish_mesh));

    let fish_materials = (0..36)
        .map(|_| {
            let base_color = Color::hsl(((rng.next_u32() % 180 + 165) % 360) as f32, 0.3, 0.4);
            let emissive = base_color.to_linear() * 0.3;

            materials.add(StandardMaterial {
                base_color,
                emissive,
                perceptual_roughness: 1.,
                ..default()
            })
        })
        .collect();

    commands.insert_resource(FishMaterials(fish_materials));

    spawn_events.send(FishSpawnEvent(
        FishOperations::valid_random_point(&mut rng).with_y(-1.7),
    ));
}

fn populate_fish_system(
    mut rng: ResMut<CreatureRng>,
    fishes: Query<Entity, With<Fish>>,
    skeletons: Query<Entity, With<FishSkeleton>>,
    mut spawn_events: EventWriter<FishSpawnEvent>,
) {
    let fish_count = fishes.iter().len() + skeletons.iter().len();

    if fish_count < FISH_MAX {
        spawn_events.send(FishSpawnEvent(
            FishOperations::valid_random_point(&mut rng).with_y(-1.7),
        ));
    }
}

fn fish_spawn_system(
    mut commands: Commands,
    mut spawn_events: EventReader<FishSpawnEvent>,
    fish_mesh: Res<FishMesh>,
    fish_materials: Res<FishMaterials>,
    mut rng: ResMut<CreatureRng>,
) {
    for spawn_location in spawn_events.read() {
        let transform = Transform::from_translation(**spawn_location)
            .with_rotation(Quat::from_euler(EulerRot::XYZ, PI / 2., PI, 0.))
            .with_scale(Vec3::new(0.1, 0.1, 0.1));

        commands.spawn((
            Fish,
            CreatureBehavior::default(),
            FishMortality::new(&mut rng),
            PbrBundle {
                transform,
                mesh: fish_mesh.clone(),
                material: fish_materials.choose(&mut rng.0).unwrap().clone(),
                ..default()
            },
        ));
    }
}

fn fish_behavior_system(
    mut commands: Commands,
    time: Res<Time>,
    mut fishes: Query<(&mut Transform, &mut CreatureBehavior, &mut FishMortality), With<Fish>>,
    pellets: Query<(Entity, &mut Transform), (With<Pellet>, Without<CreatureBehavior>)>,
    mut rng: ResMut<CreatureRng>,
) {
    for (mut transform, mut behavior, mut mortality) in fishes.iter_mut() {
        FishOperations::new(&mut transform, &mut behavior, &mut mortality).do_behavior(
            &mut commands,
            &mut rng,
            &time,
            &pellets,
        );
    }
}

fn fish_behavior_change_system(
    time: Res<Time>,
    mut fishes: Query<(&mut Transform, &mut CreatureBehavior, &mut FishMortality), With<Fish>>,
    mut rng: ResMut<CreatureRng>,
) {
    for (mut transform, mut behavior, mut mortality) in fishes.iter_mut() {
        FishOperations::new(&mut transform, &mut behavior, &mut mortality)
            .decide_behavior(&time, &mut rng);
    }
}

fn fish_pellet_detection_system(
    mut fishes: Query<(&mut Transform, &mut CreatureBehavior, &mut FishMortality), With<Fish>>,
    pellets: Query<(Entity, &Transform), (With<Pellet>, Without<CreatureBehavior>)>,
) {
    for (mut transform, mut behavior, mut mortality) in fishes.iter_mut() {
        FishOperations::new(&mut transform, &mut behavior, &mut mortality).detect_pellet(&pellets);
    }
}