1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use bevy::prelude::*;
use de_core::gamestate::GameState;
use de_pathing::{PathQueryProps, PathTarget, UpdateEntityPathEvent};
use de_types::projection::ToFlat;

pub(crate) struct ChasePlugin;

impl Plugin for ChasePlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<ChaseTargetEvent>()
            .add_systems(
                PreUpdate,
                handle_chase_events
                    .run_if(in_state(GameState::Playing))
                    .in_set(ChaseSet::ChaseTargetEvent),
            )
            .add_systems(Update, chase.run_if(in_state(GameState::Playing)));
    }
}

#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
pub enum ChaseSet {
    ChaseTargetEvent,
}

/// Send this event to start or stop chasing of an entity (movable or static).
#[derive(Event)]
pub struct ChaseTargetEvent {
    entity: Entity,
    target: Option<ChaseTarget>,
}

impl ChaseTargetEvent {
    /// Creates a new chase event.
    ///
    /// # Arguments
    ///
    /// * `entity` - the chasing entity.
    ///
    /// * `target` - target to chase or None if chasing shall be stopped.
    pub fn new(entity: Entity, target: Option<ChaseTarget>) -> Self {
        Self { entity, target }
    }

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

    fn target(&self) -> Option<&ChaseTarget> {
        self.target.as_ref()
    }
}

/// Units with this component will chase the target entity.
#[derive(Component, Deref)]
struct ChaseTargetComponent(ChaseTarget);

impl ChaseTargetComponent {
    fn new(target: ChaseTarget) -> Self {
        Self(target)
    }
}

#[derive(Clone)]
pub struct ChaseTarget {
    target: Entity,
    min_distance: f32,
    max_distance: f32,
}

impl ChaseTarget {
    /// Creates a new chase target.
    ///
    /// # Arguments
    ///
    /// * `target` - 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(target: 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 {
            target,
            min_distance,
            max_distance,
        }
    }

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

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

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

fn handle_chase_events(mut commands: Commands, mut events: EventReader<ChaseTargetEvent>) {
    for event in events.read() {
        let mut entity_commands = commands.entity(event.entity());
        match event.target() {
            Some(target) => entity_commands.insert(ChaseTargetComponent::new(target.clone())),
            None => entity_commands.remove::<ChaseTargetComponent>(),
        };
    }
}

fn chase(
    mut commands: Commands,
    mut path_events: EventWriter<UpdateEntityPathEvent>,
    chasing: Query<(
        Entity,
        &Transform,
        &ChaseTargetComponent,
        Option<&PathTarget>,
    )>,
    targets: Query<&Transform>,
) {
    for (entity, transform, chase_target, path_target) in chasing.iter() {
        let target_position = match targets.get(chase_target.target()) {
            Ok(transform) => transform.translation.to_flat(),
            Err(_) => {
                commands.entity(entity).remove::<ChaseTargetComponent>();
                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(UpdateEntityPathEvent::new(
            entity,
            PathTarget::new(
                target_position,
                PathQueryProps::new(chase_target.min_distance(), chase_target.max_distance()),
                true,
            ),
        ));
    }
}