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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
//! Cooldowns tick down until actions are ready to be used.

use crate::{
    charges::{ChargeState, Charges},
    Abilitylike, CannotUseAbility,
};

use bevy::ecs::prelude::{Component, Resource};
use bevy::utils::Duration;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, marker::PhantomData};

/// The time until each action of type `A` can be used again.
///
/// Each action may be associated with a [`Cooldown`].
/// If it is not, it always be treated as being ready.
///
/// This is typically paired with an [`ActionState`](crate::action_state::ActionState):
/// if the action state is just-pressed (or another triggering condition is met),
/// and the cooldown is ready, then perform the action and trigger the cooldown.
///
/// This type is included as part of the [`InputManagerBundle`](crate::InputManagerBundle),
/// but can also be used as a resource for singleton game objects.
///
///     
/// ```rust
/// use bevy::{utils::Duration, reflect::Reflect};
/// use leafwing_abilities::prelude::*;
/// use leafwing_input_manager::prelude::*;
///
/// #[derive(Actionlike, Abilitylike, Clone, Copy, Hash, PartialEq, Eq, Debug, Reflect)]
/// enum Action {
///     Run,
///     Jump,
/// }
///
/// let mut action_state = ActionState::<Action>::default();
/// let mut cooldowns = CooldownState::new([(Action::Jump, Cooldown::from_secs(1.))]);
///
/// action_state.press(&Action::Jump);
///
/// // This will only perform a limited check; consider using the `Abilitylike::ready` method instead
/// if action_state.just_pressed(&Action::Jump) && cooldowns.ready(Action::Jump).is_ok() {
///    // Actually do the jumping thing here
///    // Remember to actually begin the cooldown if you jumped!
///    cooldowns.trigger(Action::Jump);
/// }
///
/// // We just jumped, so the cooldown isn't ready yet
/// assert_eq!(cooldowns.ready(Action::Jump), Err(CannotUseAbility::OnCooldown));
/// ```
#[derive(Resource, Component, Debug, Clone, PartialEq, Eq)]
pub struct CooldownState<A: Abilitylike> {
    /// The [`Cooldown`] of each action
    ///
    /// If [`None`], the action can always be used
    cooldown_map: HashMap<A, Cooldown>,
    /// A shared cooldown between all actions of type `A`.
    ///
    /// No action of type `A` will be ready unless this is ready.
    /// Whenever any cooldown for an action of type `A` is triggered,
    /// this global cooldown is triggered.
    pub global_cooldown: Option<Cooldown>,
    _phantom: PhantomData<A>,
}

impl<A: Abilitylike> Default for CooldownState<A> {
    /// By default, cooldowns are not set.
    fn default() -> Self {
        CooldownState {
            cooldown_map: HashMap::new(),
            global_cooldown: None,
            _phantom: PhantomData,
        }
    }
}

impl<A: Abilitylike> CooldownState<A> {
    /// Creates a new [`CooldownState`] from an iterator of `(cooldown, action)` pairs
    ///
    /// If a [`Cooldown`] is not provided for an action, that action will be treated as if its cooldown is always available.
    ///
    /// To create an empty [`CooldownState`] struct, use the [`Default::default`] method instead.
    ///
    /// # Example
    /// ```rust
    /// use bevy::{input::keyboard::KeyCode, reflect::Reflect};
    /// use leafwing_abilities::cooldown::{Cooldown, CooldownState};
    /// use leafwing_abilities::Abilitylike;
    /// use leafwing_input_manager::Actionlike;
    ///
    /// #[derive(Actionlike, Abilitylike, Clone, Copy, Hash, PartialEq, Eq, Debug, Reflect)]
    /// enum Action {
    ///     Run,
    ///     Jump,
    ///     Shoot,
    ///     Dash,
    /// }
    ///
    /// let input_map = CooldownState::new([
    ///     (Action::Shoot, Cooldown::from_secs(0.1)),
    ///     (Action::Dash, Cooldown::from_secs(1.0)),
    /// ]);
    /// ```
    #[must_use]
    pub fn new(action_cooldown_pairs: impl IntoIterator<Item = (A, Cooldown)>) -> Self {
        let mut cooldowns = CooldownState::default();
        for (action, cooldown) in action_cooldown_pairs.into_iter() {
            cooldowns.set(action, cooldown);
        }
        cooldowns
    }

    /// Triggers the cooldown of the `action` if it is available to be used.
    ///
    /// This can be paired with [`Cooldowns::ready`],
    /// to check if the action can be used before triggering its cooldown,
    /// or this can be used on its own,
    /// reading the returned [`Result`] to determine if the ability was used.
    #[inline]
    pub fn trigger(&mut self, action: A) -> Result<(), CannotUseAbility> {
        if let Some(cooldown) = self.get_mut(action) {
            cooldown.trigger()?;
        }

        if let Some(global_cooldown) = self.global_cooldown.as_mut() {
            global_cooldown.trigger()?;
        }

        Ok(())
    }

    /// Can the corresponding `action` be used?
    ///
    /// This will be `Ok` if the underlying [`Cooldown::ready`] call is true,
    /// or if no cooldown is stored for this action.
    #[inline]
    pub fn ready(&self, action: A) -> Result<(), CannotUseAbility> {
        self.gcd_ready()?;

        if let Some(cooldown) = self.get(action) {
            cooldown.ready()
        } else {
            Ok(())
        }
    }

    /// Has the global cooldown for actions of type `A` expired?
    ///
    /// Returns `Ok(())` if no GCD is set.
    #[inline]
    pub fn gcd_ready(&self) -> Result<(), CannotUseAbility> {
        if let Some(global_cooldown) = self.global_cooldown.as_ref() {
            global_cooldown.ready()
        } else {
            Ok(())
        }
    }

    /// Advances each underlying [`Cooldown`] according to the elapsed `delta_time`.
    ///
    /// When you have a [`Option<Mut<ActionCharges<A>>>`](bevy::ecs::change_detection::Mut),
    /// use `charges.map(|res| res.into_inner())` to convert it to the correct form.
    pub fn tick(&mut self, delta_time: Duration, maybe_charges: Option<&mut ChargeState<A>>) {
        if let Some(charge_state) = maybe_charges {
            for (action, cooldown) in self.cooldown_map.iter_mut() {
                let charges = charge_state.get_mut(action.clone());
                cooldown.tick(delta_time, charges);
            }
        } else {
            for (_, cooldown) in self.cooldown_map.iter_mut() {
                cooldown.tick(delta_time, None);
            }
        }

        if let Some(global_cooldown) = self.global_cooldown.as_mut() {
            global_cooldown.tick(delta_time, None);
        }
    }

    /// The cooldown associated with the specified `action`, if any.
    #[inline]
    #[must_use]
    pub fn get(&self, action: A) -> Option<&Cooldown> {
        self.cooldown_map.get(&action)
    }

    /// A mutable reference to the cooldown associated with the specified `action`, if any.
    #[inline]
    #[must_use]
    pub fn get_mut(&mut self, action: A) -> Option<&mut Cooldown> {
        self.cooldown_map.get_mut(&action)
    }

    /// Set a cooldown for the specified `action`.
    ///
    /// If a cooldown already existed, it will be replaced by a new cooldown with the specified duration.
    #[inline]
    pub fn set(&mut self, action: A, cooldown: Cooldown) -> &mut Self {
        self.cooldown_map.insert(action, cooldown);
        self
    }

    /// Collects a `&mut Self` into a `Self`.
    ///
    /// Used to conclude the builder pattern. Actually just calls `self.clone()`.
    #[inline]
    #[must_use]
    pub fn build(&mut self) -> Self {
        self.clone()
    }

    /// Returns an iterator of references to the underlying non-[`None`] [`Cooldown`]s
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = &Cooldown> {
        self.cooldown_map.values()
    }

    /// Returns an iterator of mutable references to the underlying non-[`None`] [`Cooldown`]s
    #[inline]
    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Cooldown> {
        self.cooldown_map.values_mut()
    }
}

/// A timer-like struct that records the amount of time until an action is available to be used again.
///
/// Cooldowns are typically stored in an [`ActionState`](crate::action_state::ActionState), associated with an action that is to be
/// cooldown-regulated.
///
/// When initialized, cooldowns are always fully available.
///
/// ```rust
/// use bevy::utils::Duration;
/// use leafwing_abilities::cooldown::Cooldown;
/// use leafwing_abilities::CannotUseAbility;
///
/// let mut cooldown = Cooldown::new(Duration::from_secs(3));
/// assert_eq!(cooldown.remaining(), Duration::ZERO);
///
/// cooldown.trigger();
/// assert_eq!(cooldown.remaining(), Duration::from_secs(3));
///
/// cooldown.tick(Duration::from_secs(1), None);
/// assert_eq!(cooldown.ready(), Err(CannotUseAbility::OnCooldown));
///
/// cooldown.tick(Duration::from_secs(5), None);
/// let triggered = cooldown.trigger();
/// assert!(triggered.is_ok());
///
/// cooldown.refresh();
/// assert!(cooldown.ready().is_ok());
/// ```
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Cooldown {
    max_time: Duration,
    /// The amount of time that has elapsed since all [`Charges`](crate::charges::Charges) were fully replenished.
    elapsed_time: Duration,
}

impl Cooldown {
    /// Creates a new [`Cooldown`], which will take `max_time` after it is used until it is ready again.
    ///
    /// # Panics
    ///
    /// The provided max time cannot be [`Duration::ZERO`].
    /// Instead, use [`None`] in the [`Cooldowns`] struct for an action without a cooldown.
    pub fn new(max_time: Duration) -> Cooldown {
        assert!(max_time != Duration::ZERO);

        Cooldown {
            max_time,
            elapsed_time: max_time,
        }
    }

    /// Creates a new [`Cooldown`] with a [`f32`] number of seconds, which will take `max_time` after it is used until it is ready again.
    ///
    /// # Panics
    ///
    /// The provided max time must be greater than 0.
    /// Instead, use [`None`] in the [`CooldownState`] struct for an action without a cooldown.
    pub fn from_secs(max_time: f32) -> Cooldown {
        assert!(max_time > 0.);
        let max_time = Duration::from_secs_f32(max_time);

        Cooldown::new(max_time)
    }

    /// Advance the cooldown by `delta_time`.
    ///
    /// If the elapsed time is enough to reset the cooldown, the number of available charges.
    pub fn tick(&mut self, delta_time: Duration, charges: Option<&mut Charges>) {
        // Don't tick cooldowns when they are fully elapsed
        if self.elapsed_time == self.max_time {
            return;
        }

        assert!(self.max_time != Duration::ZERO);

        if let Some(charges) = charges {
            let total_time = self.elapsed_time.saturating_add(delta_time);

            let total_nanos: u64 = total_time.as_nanos().try_into().unwrap_or(u64::MAX);
            let max_nanos: u64 = self.max_time.as_nanos().try_into().unwrap_or(u64::MAX);

            let n_completed = (total_nanos / max_nanos).try_into().unwrap_or(u8::MAX);
            let extra_time = Duration::from_nanos(total_nanos % max_nanos);

            let excess_completions = charges.add_charges(n_completed);
            if excess_completions == 0 {
                self.elapsed_time =
                    (self.elapsed_time.saturating_add(extra_time)).min(self.max_time);
            } else {
                self.elapsed_time = self.max_time;
            }
        } else {
            self.elapsed_time = self
                .elapsed_time
                .saturating_add(delta_time)
                .min(self.max_time);
        }
    }

    /// Is this action ready to be used?
    ///
    /// This will be true if and only if at least one charge is available.
    /// For cooldowns without charges, this will be true if `time_remaining` is [`Duration::Zero`].
    pub fn ready(&self) -> Result<(), CannotUseAbility> {
        match self.elapsed_time >= self.max_time {
            true => Ok(()),
            false => Err(CannotUseAbility::OnCooldown),
        }
    }

    /// Refreshes the cooldown, causing the underlying action to be ready to use immediately.
    ///
    /// If this cooldown has charges, the number of available charges is increased by one (but the point within the cycle is unchanged).
    #[inline]
    pub fn refresh(&mut self) {
        self.elapsed_time = self.max_time
    }

    /// Use the underlying cooldown if and only if it is ready, resetting the cooldown to its maximum value.
    ///
    /// If this cooldown has multiple charges, only one will be consumed.
    ///
    /// Returns a result indicating whether the cooldown was ready.
    /// If the cooldown was not ready, [`CannotUseAbility::OnCooldown`] is returned and this call has no effect.
    #[inline]
    pub fn trigger(&mut self) -> Result<(), CannotUseAbility> {
        self.ready()?;
        self.elapsed_time = Duration::ZERO;

        Ok(())
    }

    /// Returns the time that it will take for this action to be ready to use again after being triggered.
    #[inline]
    pub fn max_time(&self) -> Duration {
        self.max_time
    }

    /// Sets the time that it will take for this action to be ready to use again after being triggered.
    ///
    /// If the current time remaining is greater than the new max time, it will be clamped to the `max_time`.
    ///
    /// # Panics
    ///
    /// The provided max time cannot be [`Duration::ZERO`].
    /// Instead, use [`None`] in the [`Cooldowns`] struct for an action without a cooldown.
    #[inline]
    pub fn set_max_time(&mut self, max_time: Duration) {
        assert!(max_time != Duration::ZERO);

        self.max_time = max_time;
        self.elapsed_time = self.elapsed_time.min(max_time);
    }

    /// Returns the time that has passed since the cooldown was triggered.
    #[inline]
    pub fn elapsed(&self) -> Duration {
        self.elapsed_time
    }

    /// Sets the time that has passed since the cooldown was triggered.
    ///
    /// This will always be clamped between [`Duration::ZERO`] and the `max_time` of this cooldown.
    #[inline]
    pub fn set_elapsed(&mut self, elapsed_time: Duration) {
        self.elapsed_time = elapsed_time.clamp(Duration::ZERO, self.max_time);
    }

    /// Returns the time remaining until the next charge is ready.
    ///
    /// When a cooldown is fully charged, this will return [`Duration::ZERO`].
    #[inline]
    pub fn remaining(&self) -> Duration {
        self.max_time.saturating_sub(self.elapsed_time)
    }

    /// Sets the time remaining until the next charge is ready.
    ///
    /// This will always be clamped between [`Duration::ZERO`] and the `max_time` of this cooldown.
    #[inline]
    pub fn set_remaining(&mut self, time_remaining: Duration) {
        self.elapsed_time = self
            .max_time
            .saturating_sub(time_remaining.clamp(Duration::ZERO, self.max_time));
    }
}

#[cfg(test)]
mod tick_tests {
    use super::*;

    #[test]
    #[should_panic]
    fn zero_duration_cooldown_cannot_be_constructed() {
        Cooldown::new(Duration::ZERO);
    }

    #[test]
    fn tick_has_no_effect_on_fresh_cooldown() {
        let cooldown = Cooldown::from_secs(1.);
        let mut cloned_cooldown = cooldown.clone();
        cloned_cooldown.tick(Duration::from_secs_f32(1.234), None);
        assert_eq!(cooldown, cloned_cooldown);
    }

    #[test]
    fn cooldowns_start_ready() {
        let cooldown = Cooldown::from_secs(1.);
        assert!(cooldown.ready().is_ok());
    }

    #[test]
    fn cooldowns_are_ready_when_refreshed() {
        let mut cooldown = Cooldown::from_secs(1.);
        assert!(cooldown.ready().is_ok());
        let _ = cooldown.trigger();
        assert_eq!(cooldown.ready(), Err(CannotUseAbility::OnCooldown));
        cooldown.refresh();
        assert!(cooldown.ready().is_ok());
    }

    #[test]
    fn ticking_changes_cooldown() {
        let cooldown = Cooldown::new(Duration::from_millis(1000));
        let mut cloned_cooldown = cooldown.clone();
        let _ = cloned_cooldown.trigger();
        assert!(cooldown != cloned_cooldown);

        cloned_cooldown.tick(Duration::from_millis(123), None);
        assert!(cooldown != cloned_cooldown);
    }

    #[test]
    fn cooldowns_reset_after_being_ticked() {
        let mut cooldown = Cooldown::from_secs(1.);
        let _ = cooldown.trigger();
        assert_eq!(cooldown.ready(), Err(CannotUseAbility::OnCooldown));

        cooldown.tick(Duration::from_secs(3), None);
        assert!(cooldown.ready().is_ok());
    }

    #[test]
    fn time_remaining_on_fresh_cooldown_is_zero() {
        let cooldown = Cooldown::from_secs(1.);
        assert_eq!(cooldown.remaining(), Duration::ZERO);
    }
}