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 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}