1use std::sync::atomic::{AtomicU64, Ordering};
6
7use wowlab_types::sim::{ActorId, AuraKey, EnemyIdx, PullId, SpellIdx};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10#[must_use]
11pub struct ScheduledSpellBinding {
12 program: u64,
13 local: u16,
14 spell_id: SpellIdx,
15}
16
17pub trait EngineScheduledSpellAccess {
21 fn engine_coordinates(self) -> SpellIdx;
22}
23
24impl EngineScheduledSpellAccess for ScheduledSpellBinding {
25 fn engine_coordinates(self) -> SpellIdx {
26 self.spell_id
27 }
28}
29
30#[derive(Debug)]
31pub struct ScheduledSpellCatalog {
32 program: u64,
33 spell_ids: Box<[SpellIdx]>,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37#[must_use]
38pub struct PendingImpactBinding {
39 program: u64,
40 slot: u32,
41 generation: u32,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45#[must_use]
46pub struct HookTimerBinding {
47 program: u64,
48 slot: u32,
49 generation: u32,
50}
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53#[must_use]
54pub struct AuraEventBinding {
55 program: u64,
56 local: u8,
57 concrete: u32,
58 key: AuraKey,
59 target: Option<EnemyIdx>,
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63#[must_use]
64pub struct GuardianEventBinding {
65 program: u64,
66 slot: u32,
67 generation: u32,
68 ability: Option<u16>,
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72#[must_use]
73pub struct PetActionBinding {
74 program: u64,
75 plan: u16,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79#[must_use]
80pub struct RaidEventBinding {
81 program: u64,
82 plan: u16,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86#[must_use]
87pub struct ExternalBuffBinding {
88 program: u64,
89 plan: u16,
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93#[must_use]
94pub struct ActorMovementBinding {
95 program: u64,
96 actor: ActorId,
97 generation: u64,
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct PullLifecycleGeneration {
102 pull: PullId,
103 pull_epoch: u32,
104 lifecycle_epoch: u64,
105}
106
107impl PullLifecycleGeneration {
108 #[must_use]
109 pub const fn new(pull: PullId, pull_epoch: u32, lifecycle_epoch: u64) -> Self {
110 Self {
111 pull,
112 pull_epoch,
113 lifecycle_epoch,
114 }
115 }
116
117 #[must_use]
118 pub const fn pull(self) -> PullId {
119 self.pull
120 }
121
122 #[must_use]
123 pub const fn pull_epoch(self) -> u32 {
124 self.pull_epoch
125 }
126
127 #[must_use]
128 pub const fn lifecycle_epoch(self) -> u64 {
129 self.lifecycle_epoch
130 }
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134#[must_use]
135pub struct RaidAddBinding {
136 program: u64,
137 target: EnemyIdx,
138 activation_generation: u64,
139 encounter_generation: PullLifecycleGeneration,
140}
141
142pub trait DiagnosticEventBinding {
144 type Coordinates;
145
146 fn diagnostic_coordinates(self) -> Self::Coordinates;
147}
148
149impl DiagnosticEventBinding for AuraEventBinding {
150 type Coordinates = (AuraKey, Option<EnemyIdx>);
151
152 fn diagnostic_coordinates(self) -> Self::Coordinates {
153 (self.key, self.target)
154 }
155}
156
157impl DiagnosticEventBinding for GuardianEventBinding {
159 type Coordinates = (u32, u32);
160
161 fn diagnostic_coordinates(self) -> Self::Coordinates {
162 (self.slot, self.generation)
163 }
164}
165
166impl DiagnosticEventBinding for RaidAddBinding {
168 type Coordinates = u64;
169
170 fn diagnostic_coordinates(self) -> Self::Coordinates {
171 self.activation_generation
172 }
173}
174
175#[derive(Debug)]
176pub struct RuntimeEventCatalog {
177 program: u64,
178}
179
180impl RuntimeEventCatalog {
181 #[must_use]
182 pub fn new() -> Self {
183 static NEXT_PROGRAM: AtomicU64 = AtomicU64::new(1);
185
186 Self {
187 program: NEXT_PROGRAM.fetch_add(1, Ordering::Relaxed),
188 }
189 }
190
191 pub const fn pending_impact(&self, slot: u32, generation: u32) -> PendingImpactBinding {
192 PendingImpactBinding {
193 program: self.program,
194 slot,
195 generation,
196 }
197 }
198
199 #[must_use]
200 pub const fn validate_pending_impact(
201 &self,
202 binding: PendingImpactBinding,
203 ) -> Option<(u32, u32)> {
204 if binding.program == self.program {
205 Some((binding.slot, binding.generation))
206 } else {
207 None
208 }
209 }
210
211 pub const fn hook_timer(&self, slot: u32, generation: u32) -> HookTimerBinding {
212 HookTimerBinding {
213 program: self.program,
214 slot,
215 generation,
216 }
217 }
218
219 #[must_use]
220 pub const fn validate_hook_timer(&self, binding: HookTimerBinding) -> Option<(u32, u32)> {
221 if binding.program == self.program {
222 Some((binding.slot, binding.generation))
223 } else {
224 None
225 }
226 }
227
228 pub const fn aura(
229 &self,
230 local: u8,
231 concrete: u32,
232 key: AuraKey,
233 target: Option<EnemyIdx>,
234 ) -> AuraEventBinding {
235 AuraEventBinding {
236 program: self.program,
237 local,
238 concrete,
239 key,
240 target,
241 }
242 }
243
244 #[must_use]
245 pub const fn validate_aura(
246 &self,
247 binding: AuraEventBinding,
248 ) -> Option<(u8, u32, AuraKey, Option<EnemyIdx>)> {
249 if binding.program == self.program {
250 Some((binding.local, binding.concrete, binding.key, binding.target))
251 } else {
252 None
253 }
254 }
255
256 pub const fn guardian(
257 &self,
258 slot: u32,
259 generation: u32,
260 ability: Option<u16>,
261 ) -> GuardianEventBinding {
262 GuardianEventBinding {
263 program: self.program,
264 slot,
265 generation,
266 ability,
267 }
268 }
269
270 #[must_use]
271 pub const fn validate_guardian(
272 &self,
273 binding: GuardianEventBinding,
274 ) -> Option<(u32, u32, Option<u16>)> {
275 if binding.program == self.program {
276 Some((binding.slot, binding.generation, binding.ability))
277 } else {
278 None
279 }
280 }
281
282 pub const fn pet_action(&self, plan: u16) -> PetActionBinding {
283 PetActionBinding {
284 program: self.program,
285 plan,
286 }
287 }
288
289 #[must_use]
290 pub const fn validate_pet_action(&self, binding: PetActionBinding) -> Option<u16> {
291 if binding.program == self.program {
292 Some(binding.plan)
293 } else {
294 None
295 }
296 }
297
298 pub const fn raid_event(&self, plan: u16) -> RaidEventBinding {
299 RaidEventBinding {
300 program: self.program,
301 plan,
302 }
303 }
304
305 #[must_use]
306 pub const fn validate_raid_event(&self, binding: RaidEventBinding) -> Option<u16> {
307 if binding.program == self.program {
308 Some(binding.plan)
309 } else {
310 None
311 }
312 }
313
314 pub const fn external_buff(&self, plan: u16) -> ExternalBuffBinding {
315 ExternalBuffBinding {
316 program: self.program,
317 plan,
318 }
319 }
320
321 #[must_use]
322 pub const fn validate_external_buff(&self, binding: ExternalBuffBinding) -> Option<u16> {
323 if binding.program == self.program {
324 Some(binding.plan)
325 } else {
326 None
327 }
328 }
329
330 pub const fn actor_movement(&self, actor: ActorId, generation: u64) -> ActorMovementBinding {
331 ActorMovementBinding {
332 program: self.program,
333 actor,
334 generation,
335 }
336 }
337
338 #[must_use]
339 pub const fn validate_actor_movement(
340 &self,
341 binding: ActorMovementBinding,
342 ) -> Option<(ActorId, u64)> {
343 if binding.program == self.program {
344 Some((binding.actor, binding.generation))
345 } else {
346 None
347 }
348 }
349
350 pub const fn raid_add(
351 &self,
352 target: EnemyIdx,
353 activation_generation: u64,
354 encounter_generation: PullLifecycleGeneration,
355 ) -> RaidAddBinding {
356 RaidAddBinding {
357 program: self.program,
358 target,
359 activation_generation,
360 encounter_generation,
361 }
362 }
363
364 #[must_use]
365 pub const fn validate_raid_add(
366 &self,
367 binding: RaidAddBinding,
368 ) -> Option<(EnemyIdx, u64, PullLifecycleGeneration)> {
369 if binding.program == self.program {
370 Some((
371 binding.target,
372 binding.activation_generation,
373 binding.encounter_generation,
374 ))
375 } else {
376 None
377 }
378 }
379}
380
381impl Default for RuntimeEventCatalog {
382 fn default() -> Self {
383 Self::new()
384 }
385}
386
387impl ScheduledSpellCatalog {
388 #[must_use]
389 pub fn new(spell_ids: impl IntoIterator<Item = SpellIdx>) -> Self {
390 static NEXT_PROGRAM: AtomicU64 = AtomicU64::new(1);
392
393 Self {
394 program: NEXT_PROGRAM.fetch_add(1, Ordering::Relaxed),
395 spell_ids: spell_ids.into_iter().collect(),
396 }
397 }
398
399 #[must_use]
400 pub fn bind(&self, local: u16) -> Option<ScheduledSpellBinding> {
401 let spell_id = self.spell_ids.get(usize::from(local)).copied()?;
402
403 Some(ScheduledSpellBinding {
404 program: self.program,
405 local,
406 spell_id,
407 })
408 }
409
410 #[must_use]
411 pub fn bind_spell_id(&self, spell_id: SpellIdx) -> Option<ScheduledSpellBinding> {
412 let local = self
413 .spell_ids
414 .iter()
415 .position(|candidate| *candidate == spell_id)
416 .and_then(|index| u16::try_from(index).ok())?;
417
418 self.bind(local)
419 }
420
421 #[must_use]
422 pub fn validate(&self, binding: ScheduledSpellBinding) -> Option<u16> {
423 (binding.program == self.program
424 && self
425 .spell_ids
426 .get(usize::from(binding.local))
427 .is_some_and(|spell| *spell == binding.spell_id))
428 .then_some(binding.local)
429 }
430}