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
use bevy::prelude::*;
use de_core::{
    gamestate::GameState,
    objects::{MovableSolid, ObjectTypeComponent},
    schedule::{Movement, PreMovement},
    state::AppState,
};
use de_objects::SolidObjects;

use crate::{
    movement::DesiredVelocity,
    repulsion::{RepulsionLables, RepulsionVelocity},
    G_ACCELERATION, MAX_V_ACCELERATION, MAX_V_SPEED,
};

pub(crate) struct AltitudePlugin;

impl Plugin for AltitudePlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            PreMovement,
            setup_entities.run_if(in_state(AppState::InGame)),
        )
        .add_systems(
            Movement,
            update
                .run_if(in_state(GameState::Playing))
                .in_set(AltitudeSet::Update)
                .after(RepulsionLables::Apply),
        );
    }
}

#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
pub(crate) enum AltitudeSet {
    Update,
}

#[derive(Component, Default)]
pub(crate) struct DesiredClimbing(f32);

impl DesiredClimbing {
    pub(crate) fn speed(&self) -> f32 {
        self.0
    }

    pub(crate) fn set_speed(&mut self, speed: f32) {
        self.0 = speed;
    }
}

fn setup_entities(
    mut commands: Commands,
    objects: Query<Entity, (With<MovableSolid>, Without<DesiredClimbing>)>,
) {
    for entity in objects.iter() {
        commands.entity(entity).insert(DesiredClimbing::default());
    }
}

fn update(
    solids: SolidObjects,
    mut objects: Query<(
        &ObjectTypeComponent,
        &mut DesiredVelocity<RepulsionVelocity>,
        &mut DesiredClimbing,
        &Transform,
    )>,
) {
    objects
        .par_iter_mut()
        .for_each(|(object_type, mut horizontal, mut climbing, transform)| {
            let Some(flight) = solids.get(**object_type).flight() else {
                return;
            };
            let height = transform.translation.y;

            let desired_height = if horizontal.stationary() {
                0.
            } else {
                if height < flight.min_height() {
                    horizontal.stop();
                }
                flight.max_height()
            };

            let remaining = desired_height - height;
            let max_acceleration = if remaining > 0. {
                G_ACCELERATION
            } else {
                MAX_V_ACCELERATION
            };
            // Make sure that the object can slow down soon enough.
            let desired = remaining.signum()
                * MAX_V_SPEED.min((2. * remaining.abs() * max_acceleration).sqrt());

            // Avoid change detection when possible.
            if climbing.speed() != desired {
                climbing.set_speed(desired);
            }
        });
}