wowlab_engine_combat/handler/can_cast/
wait.rs1use wowlab_engine_domain::rotation::DenseBuffer;
4use wowlab_engine_ports::SpecAction;
5use wowlab_types::{
6 constants::MS_PER_SECOND,
7 sim::{SimTime, SpellIdx},
8};
9
10use super::{CastReject, predicates::cast_rejection};
11use crate::{
12 context::CombatView,
13 state::CombatState,
14 systems::{effective_spell_ids, rune_time_to_regen},
15};
16
17pub(super) const MIN_WAIT_MS: u32 = 10;
18
19fn resource_wait(
20 state: &CombatState,
21 buf: &DenseBuffer,
22 now: SimTime,
23 target_cost: Option<f64>,
24) -> SimTime {
25 let (current, regen) = if let Some(resource_type) = state.config.base_stats.resource_type {
26 buf.resource(resource_type).map_or((0.0, 0.0), |resource| {
27 (resource.current, resource.regen_per_sec)
28 })
29 } else {
30 (0.0, 0.0)
31 };
32
33 let cost = match target_cost {
34 Some(cost) if cost > 0.0 => cost,
35 _ => {
36 if state.config.min_primary_cost <= 0.0 || state.config.min_primary_cost >= f64::MAX {
37 return now.saturating_add(SimTime::from_millis(MIN_WAIT_MS));
38 }
39
40 state.config.min_primary_cost
41 }
42 };
43
44 if regen <= 0.0 || current >= cost {
45 return now.saturating_add(SimTime::from_millis(MIN_WAIT_MS));
46 }
47
48 let deficit = cost - current;
49
50 if state.config.base_stats.resource_type == Some(wowlab_types::combat::ResourceType::Runes) {
51 let needed = wowlab_types::numeric::f64_to_u32_saturating_ceil(deficit.max(0.0)) as usize;
52
53 if let Some(seconds) = rune_time_to_regen(state, buf, needed) {
54 return now.saturating_add(SimTime::from_millis(
55 wowlab_types::numeric::f64_to_u32_saturating_ceil(seconds * MS_PER_SECOND)
56 .max(MIN_WAIT_MS),
57 ));
58 }
59 }
60
61 let wait_ms =
62 wowlab_types::numeric::f64_to_u32_saturating_ceil((deficit / regen) * MS_PER_SECOND)
63 .max(MIN_WAIT_MS);
64
65 now.saturating_add(SimTime::from_millis(wait_ms))
66}
67
68pub(in crate::handler) fn reject_to_wait(
69 view: &CombatView<'_>,
70 spell_id: u32,
71 now: SimTime,
72) -> SpecAction {
73 let CombatView { state, buf, .. } = view;
74
75 if let Err(CastReject::StartRecoveryCategory { ready_at, .. }) =
76 cast_rejection(view, spell_id, now)
77 {
78 return SpecAction::Wait { until_ms: ready_at };
79 }
80
81 let spell_id = state.current_target().map_or(spell_id, |target| {
82 effective_spell_ids(view, spell_id, target).0
83 });
84 let cost = buf
85 .spell(SpellIdx(spell_id))
86 .map(|spell| spell.cost)
87 .filter(|cost| *cost > 0.0);
88
89 SpecAction::Wait {
90 until_ms: resource_wait(state, buf, now, cost),
91 }
92}
93
94pub(in crate::handler) fn pool_wait(
95 state: &CombatState,
96 buf: &DenseBuffer,
97 now: SimTime,
98) -> SpecAction {
99 SpecAction::Wait {
100 until_ms: resource_wait(state, buf, now, None),
101 }
102}