wowlab_engine_combat/context/
combat_ctx.rs1use wowlab_engine_telemetry::TelemetrySink;
4use wowlab_types::sim::{ActorId, EnemyIdx, SimTime};
5
6use super::{HookCtx, HookCtxRequest, HookCtxServices};
7use crate::{
8 state::{BuffTotals, CombatInstance, CombatProgram},
9 systems,
10};
11
12#[derive(Debug)]
14#[must_use]
15pub struct CombatView<'a> {
16 pub(crate) program: &'a CombatProgram,
17 pub(crate) instance: &'a CombatInstance,
18}
19
20impl<'a> CombatView<'a> {
21 pub fn new(program: &'a CombatProgram, instance: &'a CombatInstance) -> Self {
22 Self { program, instance }
23 }
24
25 pub const fn for_actor(&self, actor: ActorId) -> ActorView<'a> {
26 ActorView {
27 program: self.program,
28 instance: self.instance,
29 actor,
30 }
31 }
32
33 pub const fn for_hook(&self, now: SimTime, target: Option<EnemyIdx>) -> HookView<'a> {
34 HookView {
35 program: self.program,
36 instance: self.instance,
37 target,
38 now,
39 }
40 }
41
42 #[must_use]
44 pub fn primary_resource_fraction(&self) -> f64 {
45 self.program
46 .config
47 .base_stats
48 .resource_type
49 .and_then(|resource_type| self.program.buffer_resource(self.instance, resource_type))
50 .map_or(1.0, |resource| {
51 if resource.max > 0.0 {
52 resource.current / resource.max
53 } else {
54 1.0
55 }
56 })
57 }
58
59 #[must_use]
61 pub fn player_aura_remaining_ms_at(
62 &self,
63 local: crate::LocalAuraIdx,
64 now: SimTime,
65 ) -> Option<u32> {
66 systems::aura_remaining_ms(
67 self.program,
68 self.instance,
69 local,
70 ActorId::Player,
71 None,
72 now,
73 )
74 }
75
76 #[must_use]
78 pub fn is_player_aura_active_on_target(
79 &self,
80 local: crate::LocalAuraIdx,
81 target: EnemyIdx,
82 ) -> bool {
83 systems::is_aura_active(self, local, ActorId::Player, Some(target))
84 }
85
86 pub(crate) fn buff_totals(&self) -> BuffTotals {
87 systems::buff_totals(self.program, self.instance, None)
88 }
89}
90
91#[derive(Clone, Copy, Debug)]
93#[must_use]
94pub struct ActorView<'a> {
95 pub(crate) program: &'a CombatProgram,
96 pub(crate) instance: &'a CombatInstance,
97 pub(crate) actor: ActorId,
98}
99
100impl<'a> ActorView<'a> {
101 pub const fn new(
102 program: &'a CombatProgram,
103 instance: &'a CombatInstance,
104 actor: ActorId,
105 ) -> Self {
106 Self {
107 program,
108 instance,
109 actor,
110 }
111 }
112
113 pub fn combat(self) -> CombatView<'a> {
114 CombatView::new(self.program, self.instance)
115 }
116}
117
118#[derive(Clone, Copy, Debug)]
120#[must_use]
121pub struct HookView<'a> {
122 pub(crate) program: &'a CombatProgram,
123 pub(crate) instance: &'a CombatInstance,
124 pub target: Option<EnemyIdx>,
125 pub now: SimTime,
126}
127
128#[derive(Clone, Copy, Debug)]
130pub struct HookProgramView<'a> {
131 pub(super) program: &'a CombatProgram,
132}
133
134impl<'a> HookProgramView<'a> {
135 pub(crate) const fn new(program: &'a CombatProgram) -> Self {
136 Self { program }
137 }
138
139 #[must_use]
140 pub fn spec_config<T>(self) -> Option<&'a T>
141 where
142 T: wowlab_engine_ports::SpecConfig,
143 {
144 self.program.spec_config::<T>()
145 }
146
147 #[must_use]
148 pub fn base_stats(self) -> &'a crate::state::BaseStats {
149 self.program.base_stats()
150 }
151
152 #[must_use]
153 pub fn encounter(self) -> &'a wowlab_engine_ports::ResolvedEncounter {
154 self.program.encounter()
155 }
156
157 pub fn spell(self, local: crate::state::LocalSpellIdx) -> &'a crate::state::SpellData {
159 self.program.spell(local)
160 }
161
162 pub fn aura(self, local: crate::state::LocalAuraIdx) -> &'a crate::state::AuraData {
164 self.program.aura(local)
165 }
166
167 #[must_use]
169 pub fn bound_aura(self, binding: crate::AuraBinding) -> Option<crate::LocalAuraIdx> {
170 self.program
171 .aura_bindings
172 .get(binding.as_usize())
173 .copied()
174 .flatten()
175 }
176
177 #[must_use]
179 pub fn bound_spell(self, binding: crate::SpellBinding) -> Option<crate::LocalSpellIdx> {
180 self.program
181 .spell_bindings
182 .get(binding.as_usize())
183 .copied()
184 .flatten()
185 }
186
187 #[must_use]
189 pub fn bound_rppm(self, binding: crate::RppmBinding) -> Option<crate::LocalRppmIdx> {
190 self.program
191 .rppm_bindings
192 .get(binding.as_usize())
193 .copied()
194 .flatten()
195 }
196
197 #[cfg(feature = "test-support")]
199 pub fn auras(self) -> &'a [crate::state::AuraData] {
200 self.program.auras()
201 }
202
203 #[cfg(feature = "test-support")]
205 pub fn precombat_auras(self) -> &'a [crate::state::LocalAuraIdx] {
206 self.program.precombat_auras()
207 }
208}
209
210impl<'a> HookView<'a> {
211 #[must_use]
212 pub const fn program(self) -> HookProgramView<'a> {
213 HookProgramView::new(self.program)
214 }
215
216 #[must_use]
217 pub const fn instance(self) -> &'a CombatInstance {
218 self.instance
219 }
220
221 pub fn combat(self) -> CombatView<'a> {
222 CombatView::new(self.program, self.instance)
223 }
224
225 pub fn for_actor(self, actor: ActorId) -> ActorView<'a> {
226 self.combat().for_actor(actor)
227 }
228
229 #[must_use]
230 pub fn last_secondary_spent(self) -> f64 {
231 self.instance.runtime.resources.last_secondary_spent
232 }
233
234 #[must_use]
236 pub fn target_health_fraction(self) -> f64 {
237 self.target
238 .and_then(|target| {
239 self.instance
240 .enemy_health_fraction(self.program, target, self.now)
241 })
242 .unwrap_or(1.0)
243 }
244}
245
246#[must_use]
248pub struct CombatCtx<'a> {
250 #[cfg(feature = "test-support")]
251 #[doc(hidden)]
252 pub program: &'a CombatProgram,
253 #[cfg(not(feature = "test-support"))]
254 pub(crate) program: &'a CombatProgram,
255 #[cfg(feature = "test-support")]
256 #[doc(hidden)]
257 pub instance: &'a mut CombatInstance,
258 #[cfg(not(feature = "test-support"))]
259 pub(crate) instance: &'a mut CombatInstance,
260 #[cfg(feature = "test-support")]
261 #[doc(hidden)]
262 pub sink: &'a mut TelemetrySink,
263 #[cfg(not(feature = "test-support"))]
264 pub(crate) sink: &'a mut TelemetrySink,
265 #[cfg(feature = "test-support")]
266 #[doc(hidden)]
267 pub now: SimTime,
268 #[cfg(not(feature = "test-support"))]
269 pub(crate) now: SimTime,
270 #[cfg(feature = "test-support")]
271 #[doc(hidden)]
272 pub rng: &'a mut dyn FnMut() -> f64,
273 #[cfg(not(feature = "test-support"))]
274 pub(crate) rng: &'a mut dyn FnMut() -> f64,
275 #[cfg(feature = "test-support")]
276 #[doc(hidden)]
277 pub source: ActorId,
278 #[cfg(not(feature = "test-support"))]
279 pub(crate) source: ActorId,
280 #[cfg(feature = "test-support")]
281 #[doc(hidden)]
282 pub target: EnemyIdx,
283 #[cfg(not(feature = "test-support"))]
284 pub(crate) target: EnemyIdx,
285}
286
287#[derive(Clone, Copy, Debug, Eq, PartialEq)]
289#[must_use]
290pub struct CombatCtxRequest {
291 pub now: SimTime,
292 pub source: ActorId,
293 pub target: EnemyIdx,
294}
295
296impl CombatCtxRequest {
297 pub const fn new(now: SimTime, source: ActorId, target: EnemyIdx) -> Self {
298 Self {
299 now,
300 source,
301 target,
302 }
303 }
304}
305
306impl CombatCtx<'_> {
307 pub fn for_optional_target<'ctx, R>(
311 hook: &'ctx mut HookCtx<'_>,
312 target: Option<EnemyIdx>,
313 operation: impl FnOnce(&mut CombatCtx<'ctx>) -> R,
314 ) -> Option<R> {
315 let target = target?;
316 let mut ctx = CombatCtx {
317 program: hook.program,
318 instance: &mut *hook.instance,
319 sink: &mut *hook.sink,
320 now: hook.now,
321 rng: &mut *hook.rng,
322 source: hook.source,
323 target,
324 };
325
326 Some(operation(&mut ctx))
327 }
328
329 pub fn view(&self) -> CombatView<'_> {
330 CombatView::new(self.program, self.instance)
331 }
332
333 pub(crate) fn hook_ctx(&mut self) -> HookCtx<'_> {
334 self.hook_ctx_with_source(self.source)
335 }
336
337 pub(crate) fn hook_ctx_with_source(&mut self, source: ActorId) -> HookCtx<'_> {
338 HookCtx::new(
339 HookCtxServices {
340 program: self.program,
341 instance: self.instance,
342 sink: self.sink,
343 rng: self.rng,
344 },
345 HookCtxRequest::for_target(self.now, self.target).with_source(source),
346 )
347 }
348}
349
350impl std::fmt::Debug for CombatCtx<'_> {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 f.debug_struct("CombatCtx")
353 .field("now", &self.now)
354 .field("source", &self.source)
355 .field("target", &self.target)
356 .finish_non_exhaustive()
357 }
358}