Skip to main content

wowlab_engine_combat/state/
orchestration.rs

1//! Deterministic group, wave, pull, and termination state for one iteration.
2
3use wowlab_engine_ports::PullLifecycleGeneration;
4use wowlab_types::sim::{EncounterDefinition, EnemyIdx, GroupId, PullId, SimTime, WaveId};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub(crate) enum EnemyCompletion {
8    Pending,
9    Dead,
10    CompletionDespawn,
11    NonCompletionDespawn,
12    ForcedOptionalDespawn,
13}
14
15#[derive(Debug)]
16pub(crate) struct EncounterRuntime {
17    active_pull: PullId,
18    pull_epoch: u32,
19    lifecycle_epoch: u64,
20    pull_started_at: SimTime,
21    waves_active: Vec<bool>,
22    groups_complete: Vec<bool>,
23    groups_queued: Vec<bool>,
24    pulls_complete: Vec<bool>,
25    pull_completion_queued: bool,
26    enemy_completion: Vec<EnemyCompletion>,
27    terminated_at: Option<SimTime>,
28    started: bool,
29    pending_target_precombat: Option<PullLifecycleGeneration>,
30}
31
32impl EncounterRuntime {
33    pub(crate) fn new(definition: &EncounterDefinition) -> Self {
34        let mut runtime = Self {
35            active_pull: PullId(0),
36            pull_epoch: 1,
37            lifecycle_epoch: 1,
38            pull_started_at: SimTime::ZERO,
39            waves_active: vec![false; definition.waves.len()],
40            groups_complete: vec![false; definition.groups.len()],
41            groups_queued: vec![false; definition.groups.len()],
42            pulls_complete: vec![false; definition.pulls.len()],
43            pull_completion_queued: false,
44            enemy_completion: vec![EnemyCompletion::Pending; definition.enemies.len()],
45            terminated_at: None,
46            started: false,
47            pending_target_precombat: None,
48        };
49
50        runtime.mark_initial_waves(definition);
51
52        runtime
53    }
54
55    pub(crate) fn reset(&mut self, definition: &EncounterDefinition) {
56        let lifecycle_epoch = self.lifecycle_epoch.saturating_add(1);
57
58        *self = Self::new(definition);
59        self.lifecycle_epoch = lifecycle_epoch;
60    }
61
62    pub(crate) const fn active_pull(&self) -> PullId {
63        self.active_pull
64    }
65
66    pub(crate) const fn pull_epoch(&self) -> u32 {
67        self.pull_epoch
68    }
69
70    pub(crate) const fn generation(&self) -> PullLifecycleGeneration {
71        PullLifecycleGeneration::new(self.active_pull, self.pull_epoch, self.lifecycle_epoch)
72    }
73
74    pub(crate) const fn pull_started_at(&self) -> SimTime {
75        self.pull_started_at
76    }
77
78    pub(crate) fn is_current(&self, pull: PullId, epoch: u32) -> bool {
79        self.active_pull.0 == pull.0
80            && self.pull_epoch == epoch
81            && self.terminated_at.is_none()
82            && !self
83                .pulls_complete
84                .get(pull.as_usize())
85                .copied()
86                .unwrap_or(true)
87    }
88
89    pub(crate) fn is_current_generation(&self, generation: PullLifecycleGeneration) -> bool {
90        generation.lifecycle_epoch() == self.lifecycle_epoch
91            && self.is_current(generation.pull(), generation.pull_epoch())
92    }
93
94    pub(crate) fn start_once(&mut self) -> bool {
95        if self.started {
96            return false;
97        }
98
99        self.started = true;
100
101        true
102    }
103
104    pub(crate) fn wave_is_active(&self, wave: WaveId) -> bool {
105        self.waves_active
106            .get(wave.as_usize())
107            .copied()
108            .unwrap_or(false)
109    }
110
111    pub(crate) fn activate_wave(&mut self, wave: WaveId) -> bool {
112        let Some(active) = self.waves_active.get_mut(wave.as_usize()) else {
113            return false;
114        };
115
116        if *active {
117            return false;
118        }
119
120        *active = true;
121
122        true
123    }
124
125    pub(crate) fn group_is_complete(&self, group: GroupId) -> bool {
126        self.groups_complete
127            .get(group.as_usize())
128            .copied()
129            .unwrap_or(false)
130    }
131
132    pub(crate) fn queue_group_completion(&mut self, group: GroupId) -> bool {
133        if self.group_is_complete(group) {
134            return false;
135        }
136
137        let Some(queued) = self.groups_queued.get_mut(group.as_usize()) else {
138            return false;
139        };
140
141        if *queued {
142            return false;
143        }
144
145        *queued = true;
146
147        true
148    }
149
150    pub(crate) fn complete_group(&mut self, group: GroupId) -> bool {
151        let Some(completed) = self.groups_complete.get_mut(group.as_usize()) else {
152            return false;
153        };
154
155        if *completed {
156            return false;
157        }
158
159        *completed = true;
160
161        true
162    }
163
164    pub(crate) fn queue_pull_completion(&mut self) -> bool {
165        if self.pull_completion_queued {
166            return false;
167        }
168
169        self.pull_completion_queued = true;
170
171        true
172    }
173
174    pub(crate) fn complete_pull(&mut self, pull: PullId) -> bool {
175        let Some(completed) = self.pulls_complete.get_mut(pull.as_usize()) else {
176            return false;
177        };
178
179        if *completed {
180            return false;
181        }
182
183        *completed = true;
184
185        true
186    }
187
188    pub(crate) fn begin_next_pull(&mut self, pull: PullId, at: SimTime) {
189        self.active_pull = pull;
190        self.pull_epoch = self.pull_epoch.saturating_add(1);
191        self.pull_started_at = at;
192        self.pull_completion_queued = false;
193    }
194
195    pub(crate) fn queue_target_precombat(&mut self) {
196        self.pending_target_precombat = Some(self.generation());
197    }
198
199    pub(crate) fn take_current_target_precombat(&mut self) -> bool {
200        if !self
201            .pending_target_precombat
202            .is_some_and(|generation| self.is_current_generation(generation))
203        {
204            return false;
205        }
206
207        self.pending_target_precombat.take();
208
209        true
210    }
211
212    pub(crate) fn record_enemy_completion(
213        &mut self,
214        enemy: EnemyIdx,
215        completion: EnemyCompletion,
216    ) -> bool {
217        let Some(current) = self.enemy_completion.get_mut(enemy.as_usize()) else {
218            return false;
219        };
220
221        if *current != EnemyCompletion::Pending {
222            return false;
223        }
224
225        *current = completion;
226
227        true
228    }
229
230    pub(crate) fn enemy_completion(&self, enemy: EnemyIdx) -> Option<EnemyCompletion> {
231        self.enemy_completion.get(enemy.as_usize()).copied()
232    }
233
234    pub(crate) fn terminate(&mut self, at: SimTime) {
235        if self.terminated_at.is_none() {
236            self.terminated_at = Some(at);
237        }
238    }
239
240    pub(crate) const fn terminated_at(&self) -> Option<SimTime> {
241        self.terminated_at
242    }
243
244    fn mark_initial_waves(&mut self, definition: &EncounterDefinition) {
245        let Some(first_pull) = definition.pulls.first() else {
246            return;
247        };
248
249        for wave_id in &first_pull.wave_ids {
250            let Some(wave) = definition.waves.get(wave_id.as_usize()) else {
251                continue;
252            };
253
254            if wave.minimum_activation_s.abs() <= f64::EPSILON && wave.depends_on_groups.is_empty()
255            {
256                if let Some(active) = self.waves_active.get_mut(wave.id.as_usize()) {
257                    *active = true;
258                }
259            }
260        }
261    }
262}