wowlab_engine_domain/rotation/
resolver.rs1use wowlab_types::{
4 combat::ResourceType,
5 sim::{AuraIdx, FastMap, FastSet, SpellIdx},
6};
7
8use super::error::{Error, Result};
9
10mod policy;
11
12pub use policy::{SpellAuraPolicy, SpellAuraPolicyKind};
13
14#[derive(Clone, Copy, Debug)]
16pub struct TalentInfo {
17 pub enabled: bool,
18 pub rank: i32,
19 pub max_rank: i32,
20}
21
22impl TalentInfo {
23 #[must_use]
24 pub fn new(enabled: bool) -> Self {
25 Self {
26 enabled,
27 rank: i32::from(enabled),
28 max_rank: 1,
29 }
30 }
31
32 #[must_use]
33 pub const fn enabled() -> Self {
34 Self {
35 enabled: true,
36 rank: 1,
37 max_rank: 1,
38 }
39 }
40
41 #[must_use]
42 pub const fn disabled() -> Self {
43 Self {
44 enabled: false,
45 rank: 0,
46 max_rank: 1,
47 }
48 }
49
50 #[must_use]
51 pub fn ranked(rank: i32, max_rank: i32) -> Self {
52 Self {
53 enabled: rank > 0,
54 rank,
55 max_rank,
56 }
57 }
58}
59
60#[derive(Clone, Debug)]
62pub struct SpecResolver {
63 pub name: String,
64 resource_type_str: Option<String>,
65 secondary_resource_type_str: Option<String>,
66 resources: FastMap<String, ResourceType>,
67 spells: FastMap<String, SpellIdx>,
68 auras: FastMap<String, AuraIdx>,
69 dots: FastMap<String, AuraIdx>,
70 talents: FastMap<String, TalentInfo>,
71 charged_cooldowns: FastSet<String>,
72 spell_aura_policies: Vec<SpellAuraPolicy>,
73}
74
75impl SpecResolver {
76 pub fn new(name: impl Into<String>) -> Self {
77 Self {
78 name: name.into(),
79 resource_type_str: None,
80 secondary_resource_type_str: None,
81 resources: FastMap::default(),
82 spells: FastMap::default(),
83 auras: FastMap::default(),
84 dots: FastMap::default(),
85 talents: FastMap::default(),
86 charged_cooldowns: FastSet::default(),
87 spell_aura_policies: Vec::new(),
88 }
89 }
90
91 #[must_use]
92 pub fn resource(mut self, resource_name: impl Into<String>) -> Self {
93 let name = resource_name.into();
94
95 self.resource_type_str = Some(name.clone());
96
97 if let Ok(res_type) = name.parse() {
98 self.resources.insert(name, res_type);
99 }
100
101 self
102 }
103
104 #[must_use]
105 pub fn secondary_resource(mut self, resource_name: impl Into<String>) -> Self {
106 let name = resource_name.into();
107
108 self.secondary_resource_type_str = Some(name.clone());
109
110 if let Ok(res_type) = name.parse() {
111 self.resources.insert(name, res_type);
112 }
113
114 self
115 }
116
117 #[must_use]
118 pub fn resource_type(mut self, name: impl Into<String>, res_type: ResourceType) -> Self {
119 self.resources.insert(name.into(), res_type);
120
121 self
122 }
123
124 #[must_use]
125 pub fn spell(mut self, name: impl Into<String>, id: u32) -> Self {
126 self.spells.insert(name.into(), SpellIdx(id));
127
128 self
129 }
130
131 #[must_use]
132 pub fn aura(mut self, name: impl Into<String>, id: u32) -> Self {
133 self.auras.insert(name.into(), AuraIdx(id));
134
135 self
136 }
137
138 #[must_use]
140 pub fn dot(mut self, name: impl Into<String>, id: u32) -> Self {
141 self.dots.insert(name.into(), AuraIdx(id));
142
143 self
144 }
145
146 #[must_use]
147 pub fn charged_cooldown(mut self, name: impl Into<String>) -> Self {
148 self.charged_cooldowns.insert(name.into());
149
150 self
151 }
152
153 #[must_use]
154 pub fn talent(mut self, name: impl Into<String>, enabled: bool) -> Self {
155 self.talents.insert(name.into(), TalentInfo::new(enabled));
156
157 self
158 }
159
160 #[must_use]
161 pub fn talent_ranked(mut self, name: impl Into<String>, rank: i32, max_rank: i32) -> Self {
162 self.talents
163 .insert(name.into(), TalentInfo::ranked(rank, max_rank));
164
165 self
166 }
167
168 pub fn resolve_spell(&self, name: &str) -> Result<SpellIdx> {
173 self.spells
174 .get(name)
175 .copied()
176 .ok_or_else(|| Error::unknown_spell(name))
177 }
178
179 pub fn resolve_aura(&self, name: &str) -> Result<AuraIdx> {
184 self.auras
185 .get(name)
186 .or_else(|| self.dots.get(name))
187 .copied()
188 .ok_or_else(|| Error::unknown_aura(name))
189 }
190
191 pub fn resolve_dot(&self, name: &str) -> Result<AuraIdx> {
196 self.dots
197 .get(name)
198 .copied()
199 .ok_or_else(|| Error::unknown_aura(name))
200 }
201
202 pub fn resolve_talent(&self, name: &str) -> Result<bool> {
207 self.talents
208 .get(name)
209 .map(|info| info.enabled)
210 .ok_or_else(|| Error::unknown_talent(name))
211 }
212
213 pub fn resolve_talent_info(&self, name: &str) -> Result<TalentInfo> {
218 self.talents
219 .get(name)
220 .copied()
221 .ok_or_else(|| Error::unknown_talent(name))
222 }
223
224 #[must_use]
225 pub fn is_charged(&self, name: &str) -> bool {
226 self.charged_cooldowns.contains(name)
227 }
228
229 #[must_use]
230 pub fn primary_resource(&self) -> Option<&str> {
231 self.resource_type_str.as_deref()
232 }
233
234 #[must_use]
235 pub fn primary_resource_type(&self) -> Option<ResourceType> {
236 self.resource_type_str.as_deref()?.parse().ok()
237 }
238
239 #[must_use]
240 pub fn secondary_resource_type(&self) -> Option<ResourceType> {
241 self.secondary_resource_type_str.as_deref()?.parse().ok()
242 }
243
244 pub fn resolve_resource(&self, name: &str) -> Result<ResourceType> {
249 if let Some(&res_type) = self.resources.get(name) {
250 return Ok(res_type);
251 }
252
253 if let Ok(res_type) = name.parse() {
254 return Ok(res_type);
255 }
256
257 Err(Error::unknown_resource(name))
258 }
259
260 #[must_use]
261 pub fn has_spell(&self, name: &str) -> bool {
262 self.spells.contains_key(name)
263 }
264
265 #[must_use]
266 pub fn has_aura(&self, name: &str) -> bool {
267 self.auras.contains_key(name) || self.dots.contains_key(name)
268 }
269
270 #[must_use]
271 pub fn has_dot(&self, name: &str) -> bool {
272 self.dots.contains_key(name)
273 }
274
275 #[must_use]
276 pub fn has_talent(&self, name: &str) -> bool {
277 self.talents.get(name).is_some_and(|info| info.enabled)
278 }
279
280 #[must_use]
281 pub fn knows_talent(&self, name: &str) -> bool {
282 self.talents.contains_key(name)
283 }
284
285 #[must_use]
286 pub fn registered_resource_types(&self) -> FastSet<ResourceType> {
287 self.resources.values().copied().collect()
288 }
289}