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
use bevy::prelude::*;
use de_types::objects::{ActiveObjectType, BuildingType, UnitType};
use enum_map::{enum_map, EnumMap};

pub(crate) struct HealthPlugin;

impl Plugin for HealthPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<InitialHealths>();
    }
}

/// Initial health of spawned objects.
#[derive(Resource)]
pub struct InitialHealths {
    healths: EnumMap<ActiveObjectType, Health>,
}

impl InitialHealths {
    pub fn health(&self, object_type: ActiveObjectType) -> &Health {
        &self.healths[object_type]
    }
}

impl Default for InitialHealths {
    fn default() -> Self {
        Self {
            healths: enum_map! {
                ActiveObjectType::Building(BuildingType::Base) => Health::full(100.),
                ActiveObjectType::Building(BuildingType::PowerHub) => Health::full(40.),
                ActiveObjectType::Unit(UnitType::Attacker) => Health::full(10.),
            },
        }
    }
}

#[derive(Clone, Component)]
pub struct Health {
    max: f32,
    health: f32,
}

impl Health {
    /// Crates a new health object with a given maximum health. Current health
    /// is set to maximum.
    ///
    /// # Arguments
    ///
    /// * `health` - maximum & current health. Must be a positive finite
    ///   number.
    const fn full(health: f32) -> Self {
        Self {
            max: health,
            health,
        }
    }

    /// Returns the fraction of remaining health, i.e. ratio between current
    /// health and maximum health.
    pub fn fraction(&self) -> f32 {
        debug_assert!(self.health.is_finite());
        debug_assert!(self.max.is_finite());
        debug_assert!(self.max > 0.);
        self.health.clamp(0., self.max) / self.max
    }

    /// # Arguments
    ///
    /// * `delta` - amount of change, i.e. by how much is the health increased.
    ///
    /// # Panics
    ///
    /// This method might panic if `delta` is not a finite number.
    pub fn update(&mut self, delta: f32) {
        // Health may go beyond maximum or below 0. This is necessary due to
        // variable update ordering (timing) originating from different game
        // instances during a multiplayer game.
        //
        // The health is dynamically clamped during health fraction
        // computation.
        debug_assert!(delta.is_finite());
        self.health += delta;
    }

    pub fn destroyed(&self) -> bool {
        self.health <= 0.
    }
}