1use std::sync::Arc;
2
3use wowlab_engine_domain::dbc::item_matches_consumable_query;
4use wowlab_engine_ports::{
5 DataResolver, EnchantmentRow, PermanentEnchantQuery, ResolverError, SpellId,
6 select_permanent_enchant,
7};
8use wowlab_types::{
9 data::{
10 ChallengeModeHealthFlat, ContentTuningFlat, ContentTuningXDifficultyFlat,
11 ContentTuningXExpectedFlat, CreatureDifficultyFlat, CreatureFlat, ExpansionTraitTreeFlat,
12 ExpectedStatFlat, ExpectedStatModFlat, ItemDamageScalingFlat, ItemDataFlat,
13 ItemScalingData, PermanentEnchantFlat, PowerTypeFlat, SpecDataFlat, SpellDataFlat,
14 SpellEffect, TraitTreeFlat,
15 },
16 sim::{FastMap, IntMap},
17};
18
19use crate::lookup_key::{ExpansionTraitTreeKey, ExpectedStatsKey};
20
21#[derive(Debug, Default)]
23pub struct InMemoryResolver {
24 spells: IntMap<i32, SpellDataFlat>,
25 effects: IntMap<i32, Vec<SpellEffect>>,
26 items: IntMap<i32, ItemDataFlat>,
27 specs: IntMap<i32, SpecDataFlat>,
28 trait_trees: IntMap<i32, TraitTreeFlat>,
29 expansion_trait_trees: FastMap<ExpansionTraitTreeKey, ExpansionTraitTreeFlat>,
30 rotation_scripts: FastMap<String, String>,
31 expected_stats: FastMap<ExpectedStatsKey, ExpectedStatFlat>,
32 scaling_data: Option<Arc<ItemScalingData>>,
33 power_types: Vec<PowerTypeFlat>,
34 enchantments: IntMap<i32, EnchantmentRow>,
35 permanent_enchants: Vec<PermanentEnchantFlat>,
36 creatures: IntMap<i32, CreatureFlat>,
37 creature_difficulties: IntMap<i32, Vec<CreatureDifficultyFlat>>,
38 content_tunings: IntMap<i32, ContentTuningFlat>,
39 content_tuning_x_difficulty: IntMap<i32, Vec<ContentTuningXDifficultyFlat>>,
40 content_tuning_x_expected: IntMap<i32, Vec<ContentTuningXExpectedFlat>>,
41 expected_stat_mods: IntMap<i32, ExpectedStatModFlat>,
42 challenge_mode_health: IntMap<i32, ChallengeModeHealthFlat>,
43}
44
45impl InMemoryResolver {
46 #[must_use]
48 pub fn new() -> Self {
49 Self::default()
50 }
51
52 #[must_use]
54 pub fn with_spell(mut self, spell: SpellDataFlat) -> Self {
55 self.spells.insert(spell.id, spell);
56
57 self
58 }
59
60 #[must_use]
62 pub fn with_effects(mut self, spell_id: i32, mut effects: Vec<SpellEffect>) -> Self {
63 effects.sort_by_key(|e| e.index);
64 self.effects.insert(spell_id, effects);
65
66 self
67 }
68
69 #[must_use]
71 pub fn with_item(mut self, item: ItemDataFlat) -> Self {
72 self.items.insert(item.id, item);
73
74 self
75 }
76
77 #[must_use]
79 pub fn with_enchantment(mut self, enchantment: EnchantmentRow) -> Self {
80 self.enchantments.insert(enchantment.id, enchantment);
81
82 self
83 }
84
85 #[must_use]
87 pub fn with_permanent_enchant(mut self, enchantment: PermanentEnchantFlat) -> Self {
88 self.permanent_enchants.push(enchantment);
89
90 self
91 }
92
93 #[must_use]
95 pub fn with_spec(mut self, spec: SpecDataFlat) -> Self {
96 self.specs.insert(spec.id, spec);
97
98 self
99 }
100
101 #[must_use]
103 pub fn with_trait_tree(mut self, tree: TraitTreeFlat) -> Self {
104 self.trait_trees.insert(tree.spec_id, tree);
105
106 self
107 }
108
109 #[must_use]
111 pub fn with_expansion_trait_tree(mut self, tree: ExpansionTraitTreeFlat) -> Self {
112 self.expansion_trait_trees.insert(
113 ExpansionTraitTreeKey::new(tree.expansion_id, &tree.system),
114 tree,
115 );
116
117 self
118 }
119
120 #[must_use]
122 pub fn with_rotation_script(
123 mut self,
124 id: impl Into<String>,
125 script: impl Into<String>,
126 ) -> Self {
127 self.rotation_scripts.insert(id.into(), script.into());
128
129 self
130 }
131
132 #[must_use]
134 pub fn with_expected_stats(mut self, stats: ExpectedStatFlat) -> Self {
135 self.expected_stats
136 .insert(ExpectedStatsKey::new(stats.expansion_id, stats.lvl), stats);
137
138 self
139 }
140
141 #[must_use]
143 pub fn with_scaling_data(mut self, data: ItemScalingData) -> Self {
144 self.scaling_data = Some(Arc::new(data));
145
146 self
147 }
148
149 #[must_use]
151 pub fn with_power_types(mut self, power_types: Vec<PowerTypeFlat>) -> Self {
152 self.power_types = power_types;
153
154 self
155 }
156
157 #[must_use]
159 pub fn with_creature(mut self, creature: CreatureFlat) -> Self {
160 self.creatures.insert(creature.id, creature);
161
162 self
163 }
164
165 #[must_use]
167 pub fn with_creature_difficulty(mut self, row: CreatureDifficultyFlat) -> Self {
168 self.creature_difficulties
169 .entry(row.creature_id)
170 .or_default()
171 .push(row);
172
173 self
174 }
175
176 #[must_use]
178 pub fn with_content_tuning(mut self, tuning: ContentTuningFlat) -> Self {
179 self.content_tunings.insert(tuning.id, tuning);
180
181 self
182 }
183
184 #[must_use]
186 pub fn with_content_tuning_x_difficulty(mut self, row: ContentTuningXDifficultyFlat) -> Self {
187 self.content_tuning_x_difficulty
188 .entry(row.content_tuning_id)
189 .or_default()
190 .push(row);
191
192 self
193 }
194
195 #[must_use]
197 pub fn with_content_tuning_x_expected(mut self, row: ContentTuningXExpectedFlat) -> Self {
198 self.content_tuning_x_expected
199 .entry(row.content_tuning_id)
200 .or_default()
201 .push(row);
202
203 self
204 }
205
206 #[must_use]
208 pub fn with_expected_stat_mod(mut self, stat_mod: ExpectedStatModFlat) -> Self {
209 self.expected_stat_mods.insert(stat_mod.id, stat_mod);
210
211 self
212 }
213
214 #[must_use]
216 pub fn with_challenge_mode_health(mut self, health: ChallengeModeHealthFlat) -> Self {
217 self.challenge_mode_health
218 .insert(health.challenge_level, health);
219
220 self
221 }
222
223 fn resolve_spells(&self, spell_ids: &[SpellId]) -> Result<Vec<SpellDataFlat>, ResolverError> {
224 spell_ids
225 .iter()
226 .map(|&id| {
227 self.spells
228 .get(&id.as_i32())
229 .cloned()
230 .ok_or_else(|| ResolverError::spell_not_found(id))
231 })
232 .collect()
233 }
234}
235
236impl DataResolver for InMemoryResolver {
237 async fn get_spell(&self, spell_id: SpellId) -> Result<SpellDataFlat, ResolverError> {
238 self.spells
239 .get(&spell_id.as_i32())
240 .cloned()
241 .ok_or_else(|| ResolverError::spell_not_found(spell_id))
242 }
243
244 async fn get_spells(&self, spell_ids: &[SpellId]) -> Result<Vec<SpellDataFlat>, ResolverError> {
245 self.resolve_spells(spell_ids)
246 }
247
248 async fn get_spell_effect(
249 &self,
250 spell_id: SpellId,
251 effect_index: u8,
252 ) -> Result<SpellEffect, ResolverError> {
253 let effects = self
254 .effects
255 .get(&spell_id.as_i32())
256 .ok_or_else(|| ResolverError::spell_effect_not_found(spell_id, effect_index))?;
257
258 crate::rows::validate_effect_index(spell_id, effect_index, effects)
259 }
260
261 async fn get_spell_effects(
262 &self,
263 spell_id: SpellId,
264 ) -> Result<Vec<SpellEffect>, ResolverError> {
265 if let Some(effects) = self.effects.get(&spell_id.as_i32()) {
266 return Ok(effects.clone());
267 }
268
269 self.spells
270 .get(&spell_id.as_i32())
271 .map(|spell| spell.effects.clone())
272 .ok_or_else(|| ResolverError::spell_not_found(spell_id))
273 }
274
275 async fn get_item(&self, item_id: i32) -> Result<ItemDataFlat, ResolverError> {
276 self.items
277 .get(&item_id)
278 .cloned()
279 .ok_or_else(|| ResolverError::item_not_found(item_id))
280 }
281
282 async fn find_consumable_items(
283 &self,
284 name_token: &str,
285 subclass: i32,
286 ) -> Result<Vec<ItemDataFlat>, ResolverError> {
287 Ok(self
288 .items
289 .values()
290 .filter(|item| item_matches_consumable_query(item, name_token, subclass))
291 .cloned()
292 .collect())
293 }
294
295 async fn get_scaling_data(&self) -> Result<Arc<ItemScalingData>, ResolverError> {
296 self.scaling_data
297 .clone()
298 .ok_or_else(ResolverError::no_scaling_data)
299 }
300
301 async fn get_power_types(&self) -> Result<Vec<PowerTypeFlat>, ResolverError> {
302 Ok(self.power_types.clone())
303 }
304
305 async fn get_spec(&self, spec_id: i32) -> Result<SpecDataFlat, ResolverError> {
306 self.specs
307 .get(&spec_id)
308 .cloned()
309 .ok_or_else(|| ResolverError::spec_not_found(spec_id))
310 }
311
312 async fn get_trait_tree(&self, spec_id: i32) -> Result<TraitTreeFlat, ResolverError> {
313 self.trait_trees
314 .get(&spec_id)
315 .cloned()
316 .ok_or_else(|| ResolverError::trait_tree_not_found(spec_id))
317 }
318
319 async fn get_expansion_trait_tree(
320 &self,
321 expansion_id: i32,
322 system: &str,
323 ) -> Result<ExpansionTraitTreeFlat, ResolverError> {
324 self.expansion_trait_trees
325 .get(&ExpansionTraitTreeKey::new(expansion_id, system))
326 .cloned()
327 .ok_or_else(|| ResolverError::expansion_trait_tree_not_found(expansion_id, system))
328 }
329
330 async fn get_rotation_script(&self, rotation_id: &str) -> Result<String, ResolverError> {
331 self.rotation_scripts
332 .get(rotation_id)
333 .cloned()
334 .ok_or_else(|| ResolverError::rotation_script_not_found(rotation_id))
335 }
336
337 async fn get_spell_overrides(
338 &self,
339 _spec_id: i32,
340 ) -> Result<Vec<(SpellId, SpellId)>, ResolverError> {
341 Ok(vec![])
342 }
343
344 async fn get_specialization_spells(
345 &self,
346 _spec_id: i32,
347 ) -> Result<Vec<SpellId>, ResolverError> {
348 Ok(vec![])
349 }
350
351 async fn get_racial_spells(
352 &self,
353 _race_id: i32,
354 _class_id: i32,
355 ) -> Result<Vec<SpellId>, ResolverError> {
356 Ok(vec![])
357 }
358
359 async fn get_expected_stats(
360 &self,
361 expansion_id: i32,
362 lvl: i32,
363 ) -> Result<ExpectedStatFlat, ResolverError> {
364 self.expected_stats
365 .get(&ExpectedStatsKey::new(expansion_id, lvl))
366 .cloned()
367 .ok_or_else(|| ResolverError::expected_stats_not_found(expansion_id, lvl))
368 }
369
370 async fn get_item_damage_scaling(
371 &self,
372 item_level: i32,
373 weapon_type: &str,
374 ) -> Result<ItemDamageScalingFlat, ResolverError> {
375 Err(ResolverError::item_damage_scaling_not_found(
376 weapon_type,
377 item_level,
378 ))
379 }
380
381 async fn get_enchantment(&self, enchantment_id: i32) -> Result<EnchantmentRow, ResolverError> {
382 self.enchantments
383 .get(&enchantment_id)
384 .cloned()
385 .ok_or_else(|| ResolverError::enchantment_not_found(enchantment_id))
386 }
387
388 async fn find_permanent_enchant(
389 &self,
390 query: &PermanentEnchantQuery,
391 ) -> Result<Option<PermanentEnchantFlat>, ResolverError> {
392 Ok(select_permanent_enchant(&self.permanent_enchants, query))
393 }
394
395 async fn get_creature(&self, creature_id: i32) -> Result<CreatureFlat, ResolverError> {
396 self.creatures
397 .get(&creature_id)
398 .cloned()
399 .ok_or_else(|| ResolverError::creature_not_found(creature_id))
400 }
401
402 async fn get_creature_difficulties(
403 &self,
404 creature_id: i32,
405 ) -> Result<Vec<CreatureDifficultyFlat>, ResolverError> {
406 Ok(self
407 .creature_difficulties
408 .get(&creature_id)
409 .cloned()
410 .unwrap_or_default())
411 }
412
413 async fn get_content_tuning(
414 &self,
415 content_tuning_id: i32,
416 ) -> Result<ContentTuningFlat, ResolverError> {
417 self.content_tunings
418 .get(&content_tuning_id)
419 .cloned()
420 .ok_or_else(|| ResolverError::content_tuning_not_found(content_tuning_id))
421 }
422
423 async fn get_content_tuning_x_difficulty(
424 &self,
425 content_tuning_id: i32,
426 ) -> Result<Vec<ContentTuningXDifficultyFlat>, ResolverError> {
427 Ok(self
428 .content_tuning_x_difficulty
429 .get(&content_tuning_id)
430 .cloned()
431 .unwrap_or_default())
432 }
433
434 async fn get_content_tuning_x_expected(
435 &self,
436 content_tuning_id: i32,
437 ) -> Result<Vec<ContentTuningXExpectedFlat>, ResolverError> {
438 Ok(self
439 .content_tuning_x_expected
440 .get(&content_tuning_id)
441 .cloned()
442 .unwrap_or_default())
443 }
444
445 async fn get_expected_stat_mod(
446 &self,
447 expected_stat_mod_id: i32,
448 ) -> Result<ExpectedStatModFlat, ResolverError> {
449 self.expected_stat_mods
450 .get(&expected_stat_mod_id)
451 .cloned()
452 .ok_or_else(|| ResolverError::expected_stat_mod_not_found(expected_stat_mod_id))
453 }
454
455 async fn get_challenge_mode_health(
456 &self,
457 keystone_level: i32,
458 ) -> Result<ChallengeModeHealthFlat, ResolverError> {
459 self.challenge_mode_health
460 .get(&keystone_level)
461 .cloned()
462 .ok_or_else(|| ResolverError::challenge_mode_health_not_found(keystone_level))
463 }
464}
465
466#[cfg(test)]
467mod tests;