1use super::{
2 AccumulatedRng, ActorId, AttackPosition, AuraKey, CooldownPool, DamagePool, EncounterRuntime,
3 EnemyIdx, EnemyState, Event, ExpiryPool, FastMap, GuardianInstance, HealthPool,
4 ImpactEffectProc, IncomingImmunityWindow, IntMap, LocalSpellIdx, PendingHookTimer,
5 PendingSpellImpact, PetActionState, RollingTickPool, RuneState, ScratchBuffers, ShuffledRng,
6 SimTime, SpatialTransform, SpecRuntime, SpecRuntimeError, SpellGatePool,
7};
8use crate::state::{
9 ActiveCast, ControlDeadline, CrowdControlKind, DiminishingGroup, DiminishingState,
10 ImpactChanceScale, MovementState, SchoolLockout,
11};
12
13#[derive(Debug)]
15pub struct CombatRuntime {
16 pub(super) spec_runtime: Box<dyn SpecRuntime>,
17 pub(super) enemies: Vec<EnemyState>,
18 pub(crate) encounter: EncounterRuntime,
19 pub player_transform: SpatialTransform,
20 pub attack_position: AttackPosition,
21 pub current_target: Option<EnemyIdx>,
22 pub(crate) health: RuntimeHealth,
23 pub(super) runtime_error: Option<SpecRuntimeError>,
24 pub total_damage: f64,
25 pub pending_events: Vec<Event>,
26 pub(crate) resources: RuntimeResources,
27 pub(crate) casting: RuntimeCasting,
28 pub(crate) control: RuntimeControl,
29 pub scratch: ScratchBuffers,
30 pub(crate) deferred_work: crate::state::DeferredWorkQueue,
31 pub(crate) procs: RuntimeProcs,
32 pub(crate) mask_reasons:
36 IntMap<u32, std::mem::Discriminant<crate::handler::can_cast::CastReject>>,
37 pub recharge_rate_mults: Vec<f64>,
38 pub(crate) pools: RuntimePools,
39 pub(crate) pending: RuntimePending,
40 pub(crate) companions: RuntimeCompanions,
41}
42
43#[derive(Debug)]
44pub(crate) struct RuntimeHealth {
45 pub(crate) player_health: HealthPool,
46 pub(crate) pet_health: Vec<Option<HealthPool>>,
47 pub(crate) immunity_windows: Vec<IncomingImmunityWindow>,
48 pub(crate) active_absorbs: Vec<crate::state::ActiveAbsorb>,
49 pub(crate) next_absorb_application_order: u64,
50 pub(crate) enemy_swing_deadlines: Vec<Option<SimTime>>,
51 pub(crate) enemy_invulnerable_until: Vec<SimTime>,
52}
53
54#[derive(Debug)]
55pub(crate) struct RuntimeResources {
56 pub(crate) last_primary_spent: f64,
57 pub(crate) last_optional_primary_spent: f64,
58 pub(crate) last_secondary_spent: f64,
59 pub(crate) primary_base_regen_override: Option<f64>,
60 pub(crate) runes: RuneState,
61}
62
63#[derive(Debug)]
64pub(crate) struct RuntimeCasting {
65 pub(crate) instant_cast_pending_at: Option<SimTime>,
66 pub(crate) instant_off_gcd_casts_at: Option<SimTime>,
67 pub(crate) instant_off_gcd_spell_ids: Vec<u32>,
68 pub(crate) active_channel_target: Option<EnemyIdx>,
69 pub(crate) channel_generation: u64,
70 pub(crate) active_channel_damage_mult: f64,
71 pub(crate) active_channel_spell_id: Option<u32>,
72 pub(crate) active_channel_last_tick: SimTime,
73 pub(crate) active_channel_tick_interval_ms: u32,
74 pub(crate) active_channel_ticks_remaining: u8,
75 pub(crate) active_channel_on_last_tick: bool,
76 pub(crate) active_channel_pushback_count: u8,
77 pub(crate) free_action_dispatch_depth: u8,
78 pub(crate) channel_chain_spell_id: Option<u32>,
79 pub(crate) active_casts: FastMap<ActorId, ActiveCast>,
80}
81
82#[derive(Debug)]
83pub(crate) struct RuntimeControl {
84 pub(crate) school_lockouts: FastMap<ActorId, SchoolLockout>,
85 pub(crate) movement: FastMap<ActorId, MovementState>,
86 pub(crate) movement_generation: FastMap<ActorId, u64>,
87 pub(crate) crowd_control: FastMap<(ActorId, CrowdControlKind), ControlDeadline>,
88 pub(crate) diminishing_returns: FastMap<(ActorId, DiminishingGroup), DiminishingState>,
89}
90
91#[derive(Debug)]
92pub(crate) struct RuntimeProcs {
93 pub(crate) impact_effect_ready_at: Vec<f64>,
94 pub(crate) impact_effect_target_ready_at: Vec<FastMap<EnemyIdx, SimTime>>,
95 pub(crate) impact_effect_charges: Vec<u8>,
96 pub(crate) impact_effect_rng: Vec<ImpactEffectRngState>,
97 pub(crate) proc_category_ready_at: IntMap<u32, SimTime>,
98 pub(crate) proc_attempts: IntMap<u32, u32>,
99 pub(crate) proc_counters: IntMap<u32, u32>,
100 pub(crate) proc_heartbeat_scheduled_at: Option<SimTime>,
101}
102
103#[derive(Debug)]
104pub(crate) struct RuntimePools {
105 pub(crate) residual_damage_pools: DamagePool<AuraKey>,
106 pub(crate) accumulated_damage_pools: DamagePool<AuraKey>,
107 pub(crate) rolling_tick_mult: RollingTickPool<AuraKey>,
108 pub(crate) async_stack_expiries: ExpiryPool<AuraKey>,
109 pub(crate) overkill_absorb_ready_at: CooldownPool<AuraKey>,
110 pub(crate) cooldown_ready_at: CooldownPool<LocalSpellIdx>,
111 pub(crate) start_recovery_ready_at: CooldownPool<wowlab_types::data::CooldownCategoryId>,
112 pub(crate) cooldown_category_ready_at: CooldownPool<wowlab_types::data::CooldownCategoryId>,
113 pub(crate) charge_category_ready_at: CooldownPool<wowlab_types::data::CooldownCategoryId>,
114 pub(crate) spell_gates: SpellGatePool<LocalSpellIdx>,
115}
116
117#[derive(Debug)]
118pub(crate) struct RuntimePending {
119 pub(crate) pending_hook_timers: Vec<Option<PendingHookTimer>>,
120 pub(crate) pending_spell_impacts: Vec<Option<PendingSpellImpact>>,
121}
122
123#[derive(Debug)]
124pub(crate) struct RuntimeCompanions {
125 pub(crate) guardians: Vec<Option<GuardianInstance>>,
126 pub(crate) guardian_generations: Vec<u32>,
127 pub(crate) pet_actions: Vec<Option<PetActionState>>,
128}
129
130impl CombatRuntime {
131 pub(crate) fn new(enemies: Vec<EnemyState>, init: CombatRuntimeInit) -> Self {
132 let enemy_count = enemies.len();
133 let CombatRuntimeInit {
134 encounter,
135 preferred_target,
136 player_transform,
137 player_max_health,
138 has_pet,
139 } = init;
140 let current_target = enemies
141 .get(preferred_target.as_usize())
142 .filter(|enemy| enemy.id() == preferred_target && enemy.is_active() && enemy.is_alive())
143 .map(EnemyState::id)
144 .or_else(|| {
145 enemies
146 .iter()
147 .find(|enemy| enemy.is_active() && enemy.is_alive())
148 .map(EnemyState::id)
149 });
150
151 Self {
152 spec_runtime: Box::new(()),
153 enemies,
154 encounter,
155 player_transform,
156 attack_position: AttackPosition::Behind,
157 current_target,
158 health: RuntimeHealth {
159 player_health: HealthPool::full(player_max_health),
160 pet_health: if has_pet {
161 vec![Some(HealthPool::full(player_max_health))]
162 } else {
163 Vec::new()
164 },
165 immunity_windows: Vec::new(),
166 active_absorbs: Vec::new(),
167 next_absorb_application_order: 0,
168 enemy_swing_deadlines: vec![None; enemy_count],
169 enemy_invulnerable_until: vec![SimTime::ZERO; enemy_count],
170 },
171 runtime_error: None,
172 total_damage: 0.0,
173 pending_events: Vec::new(),
174 resources: RuntimeResources {
175 last_primary_spent: 0.0,
176 last_optional_primary_spent: 0.0,
177 last_secondary_spent: 0.0,
178 primary_base_regen_override: None,
179 runes: RuneState::default(),
180 },
181 casting: RuntimeCasting {
182 instant_cast_pending_at: None,
183 instant_off_gcd_casts_at: None,
184 instant_off_gcd_spell_ids: Vec::new(),
185 active_channel_target: None,
186 channel_generation: 0,
187 active_channel_damage_mult: 1.0,
188 active_channel_spell_id: None,
189 active_channel_last_tick: SimTime::ZERO,
190 active_channel_tick_interval_ms: 0,
191 active_channel_ticks_remaining: 0,
192 active_channel_on_last_tick: false,
193 active_channel_pushback_count: 0,
194 free_action_dispatch_depth: 0,
195 channel_chain_spell_id: None,
196 active_casts: FastMap::default(),
197 },
198 control: RuntimeControl {
199 school_lockouts: FastMap::default(),
200 movement: FastMap::default(),
201 movement_generation: FastMap::default(),
202 crowd_control: FastMap::default(),
203 diminishing_returns: FastMap::default(),
204 },
205 scratch: ScratchBuffers::default(),
206 deferred_work: crate::state::DeferredWorkQueue::default(),
207 procs: RuntimeProcs {
208 impact_effect_ready_at: Vec::new(),
209 impact_effect_target_ready_at: Vec::new(),
210 impact_effect_charges: Vec::new(),
211 impact_effect_rng: Vec::new(),
212 proc_category_ready_at: IntMap::default(),
213 proc_attempts: IntMap::default(),
214 proc_counters: IntMap::default(),
215 proc_heartbeat_scheduled_at: None,
216 },
217 mask_reasons: IntMap::default(),
218 recharge_rate_mults: Vec::new(),
219 pools: RuntimePools {
220 residual_damage_pools: DamagePool::default(),
221 accumulated_damage_pools: DamagePool::default(),
222 rolling_tick_mult: RollingTickPool::default(),
223 async_stack_expiries: ExpiryPool::default(),
224 overkill_absorb_ready_at: CooldownPool::default(),
225 cooldown_ready_at: CooldownPool::default(),
226 start_recovery_ready_at: CooldownPool::default(),
227 cooldown_category_ready_at: CooldownPool::default(),
228 charge_category_ready_at: CooldownPool::default(),
229 spell_gates: SpellGatePool::default(),
230 },
231 pending: RuntimePending {
232 pending_hook_timers: Vec::new(),
233 pending_spell_impacts: Vec::new(),
234 },
235 companions: RuntimeCompanions {
236 guardians: Vec::new(),
237 guardian_generations: Vec::new(),
238 pet_actions: Vec::new(),
239 },
240 }
241 }
242
243 #[inline]
244 pub(crate) fn reset_spec_runtime(&mut self) {
245 self.spec_runtime.reset();
246 }
247}
248
249#[derive(Debug)]
250pub(crate) enum ImpactEffectRngState {
251 Stateless,
252 Shuffled(ShuffledRng),
253 Accumulated(AccumulatedRng),
254}
255
256impl ImpactEffectRngState {
257 pub(crate) fn from_proc(proc: &ImpactEffectProc) -> Self {
258 match proc.chance_scale {
259 ImpactChanceScale::Shuffled {
260 success_entries,
261 total_entries,
262 } => Self::Shuffled(ShuffledRng::from_valid_counts(
263 success_entries,
264 total_entries,
265 )),
266 ImpactChanceScale::Accumulated { cap, initial_count } => {
267 Self::Accumulated(AccumulatedRng::new(proc.chance, cap, initial_count))
268 }
269 _ => Self::Stateless,
270 }
271 }
272
273 pub(crate) fn reset(&mut self) {
274 match self {
275 Self::Stateless => {}
276 Self::Shuffled(tracker) => tracker.reset(),
277 Self::Accumulated(tracker) => tracker.reset(),
278 }
279 }
280}
281
282pub(crate) struct CombatRuntimeInit {
283 pub(crate) encounter: EncounterRuntime,
284 pub(crate) preferred_target: EnemyIdx,
285 pub(crate) player_transform: SpatialTransform,
286 pub(crate) player_max_health: f64,
287 pub(crate) has_pet: bool,
288}