wowlab_engine_combat/state/
orchestration.rs1use 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 debug_assert_eq!(
57 self.waves_active.len(),
58 definition.waves.len(),
59 "wave runtime length is finalized"
60 );
61 debug_assert_eq!(
62 self.groups_complete.len(),
63 definition.groups.len(),
64 "group completion runtime length is finalized"
65 );
66 debug_assert_eq!(
67 self.groups_queued.len(),
68 definition.groups.len(),
69 "group queue runtime length is finalized"
70 );
71 debug_assert_eq!(
72 self.pulls_complete.len(),
73 definition.pulls.len(),
74 "pull runtime length is finalized"
75 );
76 debug_assert_eq!(
77 self.enemy_completion.len(),
78 definition.enemies.len(),
79 "enemy completion runtime length is finalized"
80 );
81 self.active_pull = PullId(0);
82 self.pull_epoch = 1;
83 self.lifecycle_epoch = self.lifecycle_epoch.saturating_add(1);
84 self.pull_started_at = SimTime::ZERO;
85 self.waves_active.fill(false);
86 self.groups_complete.fill(false);
87 self.groups_queued.fill(false);
88 self.pulls_complete.fill(false);
89 self.pull_completion_queued = false;
90 self.enemy_completion.fill(EnemyCompletion::Pending);
91 self.terminated_at = None;
92 self.started = false;
93 self.pending_target_precombat = None;
94 self.mark_initial_waves(definition);
95 }
96
97 pub(crate) const fn active_pull(&self) -> PullId {
98 self.active_pull
99 }
100
101 pub(crate) const fn pull_epoch(&self) -> u32 {
102 self.pull_epoch
103 }
104
105 pub(crate) const fn generation(&self) -> PullLifecycleGeneration {
106 PullLifecycleGeneration::new(self.active_pull, self.pull_epoch, self.lifecycle_epoch)
107 }
108
109 pub(crate) const fn pull_started_at(&self) -> SimTime {
110 self.pull_started_at
111 }
112
113 pub(crate) fn is_current(&self, pull: PullId, epoch: u32) -> bool {
114 self.active_pull.0 == pull.0
115 && self.pull_epoch == epoch
116 && self.terminated_at.is_none()
117 && !self
118 .pulls_complete
119 .get(pull.as_usize())
120 .copied()
121 .unwrap_or(true)
122 }
123
124 pub(crate) fn is_current_generation(&self, generation: PullLifecycleGeneration) -> bool {
125 generation.lifecycle_epoch() == self.lifecycle_epoch
126 && self.is_current(generation.pull(), generation.pull_epoch())
127 }
128
129 pub(crate) fn start_once(&mut self) -> bool {
130 if self.started {
131 return false;
132 }
133
134 self.started = true;
135
136 true
137 }
138
139 pub(crate) fn wave_is_active(&self, wave: WaveId) -> bool {
140 self.waves_active
141 .get(wave.as_usize())
142 .copied()
143 .unwrap_or(false)
144 }
145
146 pub(crate) fn activate_wave(&mut self, wave: WaveId) -> bool {
147 let Some(active) = self.waves_active.get_mut(wave.as_usize()) else {
148 return false;
149 };
150
151 if *active {
152 return false;
153 }
154
155 *active = true;
156
157 true
158 }
159
160 pub(crate) fn group_is_complete(&self, group: GroupId) -> bool {
161 self.groups_complete
162 .get(group.as_usize())
163 .copied()
164 .unwrap_or(false)
165 }
166
167 pub(crate) fn queue_group_completion(&mut self, group: GroupId) -> bool {
168 if self.group_is_complete(group) {
169 return false;
170 }
171
172 let Some(queued) = self.groups_queued.get_mut(group.as_usize()) else {
173 return false;
174 };
175
176 if *queued {
177 return false;
178 }
179
180 *queued = true;
181
182 true
183 }
184
185 pub(crate) fn complete_group(&mut self, group: GroupId) -> bool {
186 let Some(completed) = self.groups_complete.get_mut(group.as_usize()) else {
187 return false;
188 };
189
190 if *completed {
191 return false;
192 }
193
194 *completed = true;
195
196 true
197 }
198
199 pub(crate) fn queue_pull_completion(&mut self) -> bool {
200 if self.pull_completion_queued {
201 return false;
202 }
203
204 self.pull_completion_queued = true;
205
206 true
207 }
208
209 pub(crate) fn complete_pull(&mut self, pull: PullId) -> bool {
210 let Some(completed) = self.pulls_complete.get_mut(pull.as_usize()) else {
211 return false;
212 };
213
214 if *completed {
215 return false;
216 }
217
218 *completed = true;
219
220 true
221 }
222
223 pub(crate) fn begin_next_pull(&mut self, pull: PullId, at: SimTime) {
224 self.active_pull = pull;
225 self.pull_epoch = self.pull_epoch.saturating_add(1);
226 self.pull_started_at = at;
227 self.pull_completion_queued = false;
228 }
229
230 pub(crate) fn queue_target_precombat(&mut self) {
231 self.pending_target_precombat = Some(self.generation());
232 }
233
234 pub(crate) fn take_current_target_precombat(&mut self) -> bool {
235 if !self
236 .pending_target_precombat
237 .is_some_and(|generation| self.is_current_generation(generation))
238 {
239 return false;
240 }
241
242 self.pending_target_precombat.take();
243
244 true
245 }
246
247 pub(crate) fn record_enemy_completion(
248 &mut self,
249 enemy: EnemyIdx,
250 completion: EnemyCompletion,
251 ) -> bool {
252 let Some(current) = self.enemy_completion.get_mut(enemy.as_usize()) else {
253 return false;
254 };
255
256 if *current != EnemyCompletion::Pending {
257 return false;
258 }
259
260 *current = completion;
261
262 true
263 }
264
265 pub(crate) fn enemy_completion(&self, enemy: EnemyIdx) -> Option<EnemyCompletion> {
266 self.enemy_completion.get(enemy.as_usize()).copied()
267 }
268
269 pub(crate) fn terminate(&mut self, at: SimTime) {
270 if self.terminated_at.is_none() {
271 self.terminated_at = Some(at);
272 }
273 }
274
275 pub(crate) const fn terminated_at(&self) -> Option<SimTime> {
276 self.terminated_at
277 }
278
279 fn mark_initial_waves(&mut self, definition: &EncounterDefinition) {
280 let Some(first_pull) = definition.pulls.first() else {
281 return;
282 };
283
284 for wave_id in &first_pull.wave_ids {
285 let Some(wave) = definition.waves.get(wave_id.as_usize()) else {
286 continue;
287 };
288
289 if wave.minimum_activation_s.abs() <= f64::EPSILON
290 && wave.depends_on_groups.is_empty()
291 && let Some(active) = self.waves_active.get_mut(wave.id.as_usize())
292 {
293 *active = true;
294 }
295 }
296 }
297}