1mod aura_state;
6mod bounded_vec;
7pub(crate) mod buff_effect;
8mod casting;
9pub(crate) mod config;
10mod control;
11pub(crate) mod damage;
12mod deferred_work;
13pub(crate) mod defs;
14pub(crate) mod ground_effect;
15mod health;
16mod orchestration;
17pub(crate) mod program;
18pub(crate) mod resource;
19pub(crate) mod runtime;
20
21pub(crate) use aura_state::AuraState;
22pub(crate) use bounded_vec::BoundedVec;
23pub(crate) use buff_effect::{
24 BuffEffect, BuffTotals, EffectConsumer, EffectFamily, MAX_AURA_BUFF_EFFECTS, try_push_effect,
25};
26pub(crate) use casting::{ActiveCast, MovementState, SchoolLockout};
27pub(crate) use config::{
28 BaseStats, EffectProgramFrame, ScratchBuffers, TargetWorkspace, with_scratch,
29};
30pub(crate) use control::{
31 ControlDeadline, CrowdControlApplication, CrowdControlDuration, CrowdControlKind,
32 DiminishingGroup, DiminishingState,
33};
34pub(crate) use damage::{
35 ActiveSpellGate, DamageEffectRef, DamageReplication, DamageSource, DamageTransfer,
36 DamageTransferTiming, HealthThresholdDirection, MAX_CDR_EFFECTS, RuntimeDamageDef,
37 WeaponApType, resolved_damage_base_points,
38};
39pub(crate) use deferred_work::{DeferredWork, DeferredWorkQueue, PendingAuraExpireHook};
40pub(crate) use defs::{
41 AccumulatingImpactProc, AuraApplicationEvent, AuraData, AuraTickBehavior, AutoAttackData,
42 CastHookFn, CritChargeReduction, DriverSpellModifier, DynamicTickAction,
43 EmpowerReleaseAdjustment, EmpowerReleaseHookFn, GuardianAbility, GuardianAbilityState,
44 GuardianEvent, GuardianHandle, GuardianInstance, GuardianSpec, GuardianSummonError,
45 ImpactActorFilter, ImpactChanceScale, ImpactEffectProc, ImpactEvent, ImpactProc, ImpactProcFn,
46 ImpactSource, LandedImpactEvent, LandedImpactProc, MAX_AURA_APPLICATION_EXCLUSIONS,
47 PassiveDriverEffect, PendingEffectProgram, PendingHookTimer, PendingSpellImpact,
48 PendingTimerAction, PeriodicData, PeriodicDriver, PeriodicKind, PetAbilityCondition,
49 PetActionBar, PetActionState, PetOwnerCoefficients, PetStatInheritance, PetStatSnapshot,
50 ProcDriver, ProcEventKind, ProcHitMask, ProcPhaseMask, ProcProvenance, ProcSourceKind,
51 ProcTriggerSide, ResolvedWeaponEnchantBinding, ResolvedWeaponImbueBinding, ResourceGainEvent,
52 ResourceGainProc, ResourceGainProcAttempts, ResourcePool, ResourceThresholdTrigger,
53 SpellAvailability, SpellBehavior, SpellChannel, SpellChannelBehavior, SpellChannelCompletion,
54 SpellChannelTiming, SpellCooldown, SpellCost, SpellDamagePolicy, SpellData, SpellEffectData,
55 SpellEffectRange, SpellEquipmentRequirements, SpellFormRequirements, SpellGroup, SpellOverride,
56 SpellRequirements, SpellTargeting, SwingEvent, SwingHand, SwingHookFn, SwingMissChanceHookFn,
57 SwingReplacementHookFn, TriggerProgramPredicateFn, WeaponImbueBinding,
58};
59pub(crate) use ground_effect::{GroundEffectDefinition, GroundEffectId, PendingGroundPulse};
60pub(crate) use health::{
61 AbsorbEffect, AbsorbKind, ActiveAbsorb, HealthPool, IncomingImmunityWindow,
62 OverkillAbsorbDecision, OverkillAbsorbEvent, OverkillAbsorbScriptContext,
63 OverkillAbsorbScriptFn,
64};
65pub(crate) use orchestration::{EncounterRuntime, EnemyCompletion};
66pub use program::CombatProgram;
67pub(crate) use program::{
68 BufferBindings, CastAuraRequirement, CastProfile, ProgramConfig, RotationSpellBinding,
69 config::AttackPosition,
70};
71pub(crate) use resource::{
72 RUNE_COUNT, RUNE_RECHARGE_SECONDS, ResourceGain, ResourceGainScaling, ResourceGainSource,
73 RuneState,
74};
75pub use runtime::CombatInstance;
76#[cfg(all(
77 feature = "rotation-optimizations",
78 any(test, feature = "test-support")
79))]
80pub(crate) use runtime::DamageProjectionSheetStats;
81pub(crate) use runtime::{
82 ActiveChannelIdentity, AuraActivation, AuraMutation, AuraRefresh, AuraSchedule, AuraSnapshot,
83 AuraTransferState, CombatRuntime, CombatRuntimeInit, ImpactEffectRngState, ProjectionDirty,
84};
85#[cfg(feature = "rotation-optimizations")]
86pub(crate) use runtime::{DamageBuffProjection, DamageProjectionKey};
87pub(crate) use wowlab_engine_rng::{RppmTracker, ThresholdTracker};
88pub(crate) use wowlab_types::combat::SpellGroupRule;
89
90#[derive(Clone, Copy, Debug)]
92#[must_use]
93pub struct LocalSpellIdx {
94 index: u8,
95 brand: u32,
96}
97
98#[derive(Clone, Copy, Debug)]
100#[must_use]
101pub struct LocalAuraIdx {
102 index: u8,
103 brand: u32,
104}
105
106macro_rules! impl_local_identity_traits {
107 ($identity:ty) => {
108 impl PartialEq for $identity {
109 fn eq(&self, other: &Self) -> bool {
110 self.index == other.index
111 }
112 }
113
114 impl Eq for $identity {}
115
116 impl std::hash::Hash for $identity {
117 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
118 std::hash::Hash::hash(&self.index, state);
119 }
120 }
121
122 impl PartialOrd for $identity {
123 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
124 Some(self.cmp(other))
125 }
126 }
127
128 impl Ord for $identity {
129 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
130 self.index.cmp(&other.index)
131 }
132 }
133 };
134}
135
136impl_local_identity_traits!(LocalSpellIdx);
137impl_local_identity_traits!(LocalAuraIdx);
138
139#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
141#[must_use]
142pub struct AuraBinding(pub(crate) u16);
143
144#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
146#[must_use]
147pub struct SpellBinding(pub(crate) u16);
148
149#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
151#[must_use]
152pub struct SpecIdentityProof {
153 brand: u32,
154 spec_id: u32,
155}
156
157#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
159#[must_use]
160pub struct SpecAuraBinding {
161 brand: u32,
162 local: u8,
163}
164
165#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
168#[must_use]
169pub struct SpecSpellBinding {
170 brand: u32,
171 local: u8,
172}
173
174#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
176#[must_use]
177pub struct RppmBinding(pub(crate) u16);
178
179#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
181#[must_use]
182pub struct SystemRppmBinding(pub(crate) u8);
183
184#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
186#[must_use]
187pub struct ProcCategoryBinding(pub(crate) u8);
188
189#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
191#[must_use]
192pub struct PeriodicDriverBinding(pub(crate) u8);
193
194#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
196#[must_use]
197pub struct ProcCounterBinding(pub(crate) u8);
198
199#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
201#[must_use]
202pub struct PetResourceBinding {
203 brand: u32,
204 auto_attack: u16,
205}
206
207impl PetResourceBinding {
208 pub(crate) const fn new(brand: u32, auto_attack: u16) -> Self {
209 Self { brand, auto_attack }
210 }
211
212 pub(crate) const fn belongs_to(self, brand: u32) -> bool {
213 self.brand == brand
214 }
215
216 pub(crate) const fn auto_attack(self) -> u16 {
217 self.auto_attack
218 }
219}
220
221#[derive(Clone, Copy, Debug)]
222pub(crate) struct ResolvedSystemRppmBinding {
223 pub(crate) tracker: LocalRppmIdx,
224 pub(crate) recovery_ms: u32,
225}
226
227#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
229#[must_use]
230pub struct LocalRppmIdx(pub(crate) u8);
231
232#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
234#[must_use]
235pub struct LocalThresholdIdx(pub(crate) u8);
236
237impl LocalSpellIdx {
238 pub(crate) const fn new(index: u8) -> Self {
239 Self { index, brand: 0 }
240 }
241
242 const fn branded(index: u8, brand: u32) -> Self {
243 Self { index, brand }
244 }
245
246 #[inline]
247 #[must_use]
248 pub const fn as_usize(&self) -> usize {
249 self.index as usize
250 }
251
252 #[must_use]
253 pub(crate) const fn raw(self) -> u8 {
254 self.index
255 }
256}
257
258impl LocalAuraIdx {
259 pub(crate) const fn new(index: u8) -> Self {
260 Self { index, brand: 0 }
261 }
262
263 const fn branded(index: u8, brand: u32) -> Self {
264 Self { index, brand }
265 }
266
267 #[inline]
268 #[must_use]
269 pub const fn as_usize(&self) -> usize {
270 self.index as usize
271 }
272
273 #[must_use]
274 pub(crate) const fn raw(self) -> u8 {
275 self.index
276 }
277}
278
279impl AuraBinding {
280 pub(crate) const fn new(index: u16) -> Self {
281 Self(index)
282 }
283
284 #[inline]
285 #[expect(
286 clippy::trivially_copy_pass_by_ref,
287 reason = "C-CONV requires borrowed as_ receivers"
288 )]
289 pub(crate) const fn as_usize(&self) -> usize {
290 self.0 as usize
291 }
292}
293
294impl SpellBinding {
295 pub(crate) const fn new(index: u16) -> Self {
296 Self(index)
297 }
298
299 #[inline]
300 #[expect(
301 clippy::trivially_copy_pass_by_ref,
302 reason = "C-CONV requires borrowed as_ receivers"
303 )]
304 pub(crate) const fn as_usize(&self) -> usize {
305 self.0 as usize
306 }
307}
308
309impl SpecIdentityProof {
310 pub(crate) const fn new(brand: u32, spec_id: u32) -> Self {
311 Self { brand, spec_id }
312 }
313
314 pub(crate) const fn brand(self) -> u32 {
315 self.brand
316 }
317
318 pub(crate) const fn spec_id(self) -> u32 {
319 self.spec_id
320 }
321
322 #[cfg(test)]
323 pub(crate) const fn owns_spell(self, binding: SpecSpellBinding) -> bool {
324 self.brand == binding.brand
325 }
326
327 #[cfg(test)]
328 pub(crate) const fn owns_aura(self, binding: SpecAuraBinding) -> bool {
329 self.brand == binding.brand
330 }
331}
332
333impl SpecAuraBinding {
334 pub(crate) const fn new(brand: u32, local: u8) -> Self {
335 Self { brand, local }
336 }
337
338 pub const fn bind(self, proof: SpecIdentityProof) -> LocalAuraIdx {
344 assert!(
345 self.brand == proof.brand,
346 "foreign specialization aura binding"
347 );
348
349 LocalAuraIdx::branded(self.local, self.brand)
350 }
351}
352
353impl SpecSpellBinding {
354 pub(crate) const fn new(brand: u32, local: u8) -> Self {
355 Self { brand, local }
356 }
357
358 pub const fn bind(self, proof: SpecIdentityProof) -> LocalSpellIdx {
364 assert!(
365 self.brand == proof.brand,
366 "foreign specialization spell binding"
367 );
368
369 LocalSpellIdx::branded(self.local, self.brand)
370 }
371}
372
373impl RppmBinding {
374 pub(crate) const fn new(index: u16) -> Self {
375 Self(index)
376 }
377
378 #[inline]
379 #[expect(
380 clippy::trivially_copy_pass_by_ref,
381 reason = "C-CONV requires borrowed as_ receivers"
382 )]
383 pub(crate) const fn as_usize(&self) -> usize {
384 self.0 as usize
385 }
386}
387
388impl PeriodicDriverBinding {
389 pub(crate) const fn new(index: u8) -> Self {
390 Self(index)
391 }
392
393 #[inline]
394 #[expect(
395 clippy::trivially_copy_pass_by_ref,
396 reason = "C-CONV requires borrowed as_ receivers"
397 )]
398 pub(crate) const fn as_usize(&self) -> usize {
399 self.0 as usize
400 }
401}
402
403impl ProcCounterBinding {
404 pub(crate) const fn new(index: u8) -> Self {
405 Self(index)
406 }
407
408 #[inline]
409 #[expect(
410 clippy::trivially_copy_pass_by_ref,
411 reason = "C-CONV requires borrowed as_ receivers"
412 )]
413 pub(crate) const fn as_usize(&self) -> usize {
414 self.0 as usize
415 }
416}
417
418impl SystemRppmBinding {
419 pub(crate) const fn new(index: u8) -> Self {
420 Self(index)
421 }
422
423 #[inline]
424 #[expect(
425 clippy::trivially_copy_pass_by_ref,
426 reason = "C-CONV requires borrowed as_ receivers"
427 )]
428 pub(crate) const fn as_usize(&self) -> usize {
429 self.0 as usize
430 }
431}
432
433impl ProcCategoryBinding {
434 pub(crate) const fn new(index: u8) -> Self {
435 Self(index)
436 }
437
438 #[inline]
439 #[expect(
440 clippy::trivially_copy_pass_by_ref,
441 reason = "C-CONV requires borrowed as_ receivers"
442 )]
443 pub(crate) const fn as_usize(&self) -> usize {
444 self.0 as usize
445 }
446}
447
448impl LocalRppmIdx {
449 pub(crate) const fn new(index: u8) -> Self {
450 Self(index)
451 }
452
453 #[inline]
454 #[must_use]
455 pub const fn as_usize(&self) -> usize {
456 self.0 as usize
457 }
458}
459
460impl LocalThresholdIdx {
461 pub(crate) const fn new(index: u8) -> Self {
462 Self(index)
463 }
464
465 #[inline]
466 #[must_use]
467 pub const fn as_usize(&self) -> usize {
468 self.0 as usize
469 }
470}
471
472#[cfg(test)]
473mod identity_tests {
474 use wowlab_engine_ports::CombatStats;
475
476 use crate::{CombatSystemBuilder, generated_identities::specs};
477
478 #[googletest::gtest]
479 fn foreign_binding_and_matching_foreign_proof_are_rejected_by_program() {
480 let own_proof = specs::arms_warrior::PROOF;
481 let foreign_proof = specs::fury_warrior::PROOF;
482 let own_spell = specs::arms_warrior::SPELL_MORTAL_STRIKE.bind(own_proof);
483 let foreign_spell = specs::fury_warrior::SPELL_BLOODTHIRST.bind(foreign_proof);
484 let own_aura = specs::arms_warrior::AURA_COLOSSUS_SMASH.bind(own_proof);
485 let foreign_aura = specs::fury_warrior::AURA_ENRAGE.bind(foreign_proof);
486 let built = CombatSystemBuilder::new(CombatStats::default())
487 .spec_identity(own_proof)
488 .encounter(crate::test_support::default_encounter())
489 .spell("mortal_strike", 1, Ok)
490 .aura("sudden_death", 2, Ok)
491 .into_draft()
492 .expect("valid branded draft")
493 .finalize(&crate::test_support::minimal_rotation())
494 .expect("valid branded program");
495
496 assert!(built.program.resolve_spell_identity(own_spell).is_some());
497 assert!(built.program.resolve_aura_identity(own_aura).is_some());
498 assert!(
499 built
500 .program
501 .resolve_spell_identity(foreign_spell)
502 .is_none()
503 );
504 assert!(built.program.resolve_aura_identity(foreign_aura).is_none());
505 }
506}