de_behaviour 0.0.1

Digital Extinction unit level behaviour and AI.
Documentation
use bevy::prelude::*;
use de_core::{projection::ToFlat, stages::GameStage, state::GameState};
use de_pathing::{PathQueryProps, PathTarget, UpdateEntityPath};
use iyes_loopless::prelude::*;

pub(crate) struct ChasePlugin;

impl Plugin for ChasePlugin {
    fn build(&self, app: &mut App) {
        app.add_system_set_to_stage(
            GameStage::Update,
            SystemSet::new().with_system(chase.run_in_state(GameState::Playing)),
        );
    }
}

/// Units with this component will chase the target entity.
#[derive(Component)]
pub struct ChaseTarget {
    entity: Entity,
    min_distance: f32,
    max_distance: f32,
}

impl ChaseTarget {
    /// Creates a new chase target.
    ///
    /// # Arguments
    ///
    /// * `entity` - entity to chase.
    ///
    /// * `min_distance` - minimum distance between the chasing entity and the
    ///   cased entity. Elevation is ignored during the distance calculation.
    ///
    /// * `max_distance` - maximum distance between the chasing entity and the
    ///   cased entity. Elevation is ignored during the distance calculation.
    ///
    /// # Panics
    ///
    /// May panic if `min_distance` or `max_distance` is not non-negative
    /// finite number or when `min_distance` is greater or equal to
    /// `max_distance`.
    pub fn new(entity: Entity, min_distance: f32, max_distance: f32) -> Self {
        debug_assert!(min_distance.is_finite());
        debug_assert!(max_distance.is_finite());
        debug_assert!(min_distance >= 0.);
        debug_assert!(max_distance >= 0.);
        debug_assert!(min_distance < max_distance);

        Self {
            entity,
            min_distance,
            max_distance,
        }
    }

    pub fn entity(&self) -> Entity {
        self.entity
    }

    fn min_distance(&self) -> f32 {
        self.min_distance
    }

    fn max_distance(&self) -> f32 {
        self.max_distance
    }
}

fn chase(
    mut commands: Commands,
    mut path_events: EventWriter<UpdateEntityPath>,
    chasing: Query<(Entity, &Transform, &ChaseTarget, Option<&PathTarget>)>,
    targets: Query<&Transform>,
) {
    for (entity, transform, chase_target, path_target) in chasing.iter() {
        let target_position = match targets.get(chase_target.entity()) {
            Ok(transform) => transform.translation.to_flat(),
            Err(_) => {
                commands.entity(entity).remove::<ChaseTarget>();
                continue;
            }
        };

        let (path_target, distance) = path_target
            .map(|path_target| (path_target.location(), path_target.properties().distance()))
            .unwrap_or((transform.translation.to_flat(), 0.));

        if (target_position - path_target).length() + distance <= chase_target.max_distance() {
            continue;
        }

        path_events.send(UpdateEntityPath::new(
            entity,
            PathTarget::new(
                target_position,
                PathQueryProps::new(chase_target.min_distance(), chase_target.max_distance()),
                true,
            ),
        ));
    }
}