1const TWO_HAND_ID_OFFSET: i32 = 100_000;
4const AMMO_ID_OFFSET: i32 = 200_000;
5const ONE_HAND_CASTER_ID_OFFSET: i32 = 300_000;
6const TWO_HAND_CASTER_ID_OFFSET: i32 = 400_000;
7const EFFECT_CREATE_ITEM: i32 = 24;
8const EFFECT_ENCHANT_ITEM_PERMANENTLY: i32 = 53;
9const EFFECT_CRAFTING_DATA_ENCHANT: i32 = 301;
10const EFFECT_CRAFTING_DATA_ITEM: i32 = 288;
11const PERMANENT_ENCHANT_SKILL_LINES: [i32; 9] = [164, 165, 171, 197, 202, 333, 755, 773, 960];
12
13use wowlab_types::data::{
14 ArmorLocationFlat, CooldownSetFlat, CooldownSetSpellEntry, CurveFlat, CurvePointFlat,
15 EnchantmentFlat, ExpectedStatFlat, ExpectedStatModFlat, GemPropertiesFlat,
16 ItemArmorQualityFlat, ItemArmorShieldFlat, ItemArmorTotalFlat, ItemBonusFlat,
17 ItemDamageScalingFlat, ItemOffsetCurveFlat, ItemScalingConfigFlat, ItemSquishEraFlat,
18 PermanentEnchantFlat, PowerTypeFlat, RandPropPointsFlat, SpecializationSpellFlat,
19};
20
21#[cfg(feature = "dbc")]
22use super::super::dbc::DbcData;
23
24#[must_use]
26pub fn transform_all_item_bonuses(dbc: &DbcData) -> Vec<ItemBonusFlat> {
27 dbc.item_bonus
28 .values()
29 .flatten()
30 .map(|row| ItemBonusFlat {
31 id: row.ID,
32 value_0: row.Value_0,
33 value_1: row.Value_1,
34 value_2: row.Value_2,
35 value_3: row.Value_3,
36 parent_item_bonus_list_id: row.ParentItemBonusListID,
37 bonus_type: row.Type,
38 order_index: row.OrderIndex,
39 })
40 .collect()
41}
42
43#[must_use]
45pub fn transform_all_curves(dbc: &DbcData) -> Vec<CurveFlat> {
46 dbc.curve
47 .values()
48 .map(|row| CurveFlat {
49 id: row.ID,
50 curve_type: row.Type,
51 flags: row.Flags,
52 })
53 .collect()
54}
55
56#[must_use]
58pub fn transform_all_curve_points(dbc: &DbcData) -> Vec<CurvePointFlat> {
59 dbc.curve_point
60 .values()
61 .flatten()
62 .map(|row| CurvePointFlat {
63 id: row.ID,
64 curve_id: row.CurveID,
65 order_index: row.OrderIndex,
66 pos_0: row.Pos_0,
67 pos_1: row.Pos_1,
68 pos_pre_squish_0: row.PosPreSquish_0,
69 pos_pre_squish_1: row.PosPreSquish_1,
70 })
71 .collect()
72}
73
74#[must_use]
76pub fn transform_all_rand_prop_points(dbc: &DbcData) -> Vec<RandPropPointsFlat> {
77 dbc.rand_prop_points
78 .values()
79 .map(|row| RandPropPointsFlat {
80 id: row.ID,
81 damage_replace_stat_f: row.DamageReplaceStatF,
82 damage_secondary_f: row.DamageSecondaryF,
83 damage_replace_stat: row.DamageReplaceStat,
84 damage_secondary: row.DamageSecondary,
85 epic_f_0: row.EpicF_0,
86 epic_f_1: row.EpicF_1,
87 epic_f_2: row.EpicF_2,
88 epic_f_3: row.EpicF_3,
89 epic_f_4: row.EpicF_4,
90 superior_f_0: row.SuperiorF_0,
91 superior_f_1: row.SuperiorF_1,
92 superior_f_2: row.SuperiorF_2,
93 superior_f_3: row.SuperiorF_3,
94 superior_f_4: row.SuperiorF_4,
95 good_f_0: row.GoodF_0,
96 good_f_1: row.GoodF_1,
97 good_f_2: row.GoodF_2,
98 good_f_3: row.GoodF_3,
99 good_f_4: row.GoodF_4,
100 epic_0: row.Epic_0,
101 epic_1: row.Epic_1,
102 epic_2: row.Epic_2,
103 epic_3: row.Epic_3,
104 epic_4: row.Epic_4,
105 superior_0: row.Superior_0,
106 superior_1: row.Superior_1,
107 superior_2: row.Superior_2,
108 superior_3: row.Superior_3,
109 superior_4: row.Superior_4,
110 good_0: row.Good_0,
111 good_1: row.Good_1,
112 good_2: row.Good_2,
113 good_3: row.Good_3,
114 good_4: row.Good_4,
115 })
116 .collect()
117}
118
119#[must_use]
121pub fn transform_all_item_scaling_configs(dbc: &DbcData) -> Vec<ItemScalingConfigFlat> {
122 dbc.item_scaling_config
123 .values()
124 .map(|row| ItemScalingConfigFlat {
125 id: row.ID,
126 item_offset_curve_id: row.ItemOffsetCurveID,
127 item_level: row.ItemLevel,
128 required_level: row.RequiredLevel,
129 item_squish_era_id: row.ItemSquishEraID,
130 flags: row.Flags,
131 })
132 .collect()
133}
134
135#[must_use]
137pub fn transform_all_item_offset_curves(dbc: &DbcData) -> Vec<ItemOffsetCurveFlat> {
138 dbc.item_offset_curve
139 .values()
140 .map(|row| ItemOffsetCurveFlat {
141 id: row.ID,
142 curve_id: row.CurveID,
143 offset: row.Offset,
144 })
145 .collect()
146}
147
148#[must_use]
150pub fn transform_all_item_squish_eras(dbc: &DbcData) -> Vec<ItemSquishEraFlat> {
151 dbc.item_squish_era
152 .values()
153 .map(|row| ItemSquishEraFlat {
154 id: row.ID,
155 patch: row.Patch,
156 curve_id: row.CurveID,
157 flags: row.Flags,
158 })
159 .collect()
160}
161
162#[must_use]
164pub fn transform_all_enchantments(dbc: &DbcData) -> Vec<EnchantmentFlat> {
165 dbc.spell_item_enchantment
166 .values()
167 .map(|row| EnchantmentFlat {
168 id: row.ID,
169 name: row.Name_lang.clone().unwrap_or_default(),
170 duration: row.Duration,
171 flags: row.Flags,
172 icon_file_data_id: row.IconFileDataID,
173 item_level_min: row.ItemLevelMin,
174 item_level_max: row.ItemLevelMax,
175 item_level: row.ItemLevel,
176 charges: row.Charges,
177 scaling_class: row.ScalingClass,
178 scaling_class_restricted: row.ScalingClassRestricted,
179 condition_id: row.Condition_ID,
180 min_level: row.MinLevel,
181 max_level: row.MaxLevel,
182 required_skill_id: row.RequiredSkillID,
183 required_skill_rank: row.RequiredSkillRank,
184 transmog_use_condition_id: row.TransmogUseConditionID,
185 transmog_cost: row.TransmogCost,
186 item_visual: row.ItemVisual,
187 effect_0: row.Effect_0,
188 effect_1: row.Effect_1,
189 effect_2: row.Effect_2,
190 effect_arg_0: row.EffectArg_0,
191 effect_arg_1: row.EffectArg_1,
192 effect_arg_2: row.EffectArg_2,
193 effect_points_min_0: row.EffectPointsMin_0,
194 effect_points_min_1: row.EffectPointsMin_1,
195 effect_points_min_2: row.EffectPointsMin_2,
196 effect_scaling_points_0: row.EffectScalingPoints_0,
197 effect_scaling_points_1: row.EffectScalingPoints_1,
198 effect_scaling_points_2: row.EffectScalingPoints_2,
199 })
200 .collect()
201}
202
203fn permanent_enchant_token(name: &str) -> String {
204 name.trim_start_matches(['_', '+'])
205 .chars()
206 .filter(|&character| {
207 character.is_ascii_alphanumeric() || matches!(character, '_' | '+' | '.' | '%' | ' ')
208 })
209 .map(|character| {
210 if character == ' ' {
211 '_'
212 } else {
213 character.to_ascii_lowercase()
214 }
215 })
216 .collect()
217}
218
219fn permanent_enchant_effect(
220 dbc: &DbcData,
221 spell_id: i32,
222 effect_type: i32,
223) -> Option<&crate::parsers::dbc::rows::SpellEffectRow> {
224 dbc.spell_effect
225 .get(&spell_id)?
226 .iter()
227 .find(|effect| effect.Effect == effect_type)
228}
229
230fn insert_permanent_enchant(
231 dbc: &DbcData,
232 entries: &mut std::collections::BTreeSet<(String, i32, i32, i32, i32, i32)>,
233 tokenized_name: &str,
234 rank: i32,
235 enchant_id: i32,
236 restriction_spell_id: i32,
237) {
238 if !dbc.spell_item_enchantment.contains_key(&enchant_id) {
239 return;
240 }
241
242 let Some(restriction) = dbc.spell_equipped_items.get(&restriction_spell_id) else {
243 return;
244 };
245
246 entries.insert((
247 tokenized_name.to_string(),
248 rank,
249 enchant_id,
250 restriction.EquippedItemClass,
251 restriction.EquippedItemInvTypes,
252 restriction.EquippedItemSubclass,
253 ));
254}
255
256fn insert_item_permanent_enchants(
257 dbc: &DbcData,
258 entries: &mut std::collections::BTreeSet<(String, i32, i32, i32, i32, i32)>,
259 tokenized_name: &str,
260 rank: i32,
261 item_id: i32,
262) {
263 let Some(item_effects) = dbc.item_x_item_effect.get(&item_id) else {
264 return;
265 };
266
267 for mapping in item_effects {
268 let Some(item_effect) = dbc.item_effect.get(&mapping.ItemEffectID) else {
269 continue;
270 };
271 let Some(effect) =
272 permanent_enchant_effect(dbc, item_effect.SpellID, EFFECT_ENCHANT_ITEM_PERMANENTLY)
273 else {
274 continue;
275 };
276
277 insert_permanent_enchant(
278 dbc,
279 entries,
280 tokenized_name,
281 rank,
282 effect.EffectMiscValue_0,
283 item_effect.SpellID,
284 );
285 }
286}
287
288#[must_use]
290pub fn transform_all_permanent_enchants(dbc: &DbcData) -> Vec<PermanentEnchantFlat> {
291 let mut entries = std::collections::BTreeSet::new();
292
293 for ability in dbc.skill_line_ability.values().flatten() {
294 if !PERMANENT_ENCHANT_SKILL_LINES.contains(&ability.SkillLine) {
295 continue;
296 }
297
298 let Some(name) = dbc
299 .spell_name
300 .get(&ability.Spell)
301 .and_then(|spell| spell.Name_lang.as_deref())
302 else {
303 continue;
304 };
305 let tokenized_name = permanent_enchant_token(name);
306
307 if let Some(effect) =
308 permanent_enchant_effect(dbc, ability.Spell, EFFECT_ENCHANT_ITEM_PERMANENTLY)
309 {
310 insert_permanent_enchant(
311 dbc,
312 &mut entries,
313 &tokenized_name,
314 0,
315 effect.EffectMiscValue_0,
316 ability.Spell,
317 );
318 } else if let Some(effect) =
319 permanent_enchant_effect(dbc, ability.Spell, EFFECT_CREATE_ITEM)
320 {
321 insert_item_permanent_enchants(
322 dbc,
323 &mut entries,
324 &tokenized_name,
325 0,
326 effect.EffectItemType,
327 );
328 } else if let Some(effect) =
329 permanent_enchant_effect(dbc, ability.Spell, EFFECT_CRAFTING_DATA_ENCHANT)
330 {
331 if !dbc.crafting_data.contains_key(&effect.EffectMiscValue_0) {
332 continue;
333 }
334
335 for quality in dbc
336 .crafting_data_enchant_quality
337 .get(&effect.EffectMiscValue_0)
338 .into_iter()
339 .flatten()
340 {
341 let Some(rank) = dbc.crafting_quality.get(&quality.Rank) else {
342 continue;
343 };
344
345 insert_permanent_enchant(
346 dbc,
347 &mut entries,
348 &tokenized_name,
349 rank.QualityTier,
350 quality.SpellItemEnchantmentID,
351 ability.Spell,
352 );
353 }
354 } else if let Some(effect) =
355 permanent_enchant_effect(dbc, ability.Spell, EFFECT_CRAFTING_DATA_ITEM)
356 {
357 if !dbc.crafting_data.contains_key(&effect.EffectMiscValue_0) {
358 continue;
359 }
360
361 for quality in dbc
362 .crafting_data_item_quality
363 .get(&effect.EffectMiscValue_0)
364 .into_iter()
365 .flatten()
366 {
367 let Some(item) = dbc.item.get(&quality.ItemID) else {
368 continue;
369 };
370 let Some(rank) = dbc.crafting_quality.get(&item.CraftingQualityID) else {
371 continue;
372 };
373
374 insert_item_permanent_enchants(
375 dbc,
376 &mut entries,
377 &tokenized_name,
378 rank.QualityTier,
379 quality.ItemID,
380 );
381 }
382 }
383 }
384
385 entries
386 .into_iter()
387 .map(
388 |(tokenized_name, rank, enchant_id, item_class, inventory_type_mask, subclass_mask)| {
389 PermanentEnchantFlat {
390 enchant_id,
391 rank,
392 item_class,
393 inventory_type_mask,
394 subclass_mask,
395 tokenized_name,
396 }
397 },
398 )
399 .collect()
400}
401
402#[must_use]
404pub fn transform_all_gem_properties(dbc: &DbcData) -> Vec<GemPropertiesFlat> {
405 dbc.gem_properties
406 .values()
407 .map(|row| GemPropertiesFlat {
408 id: row.ID,
409 enchant_id: row.Enchant_ID,
410 gem_type: row.Type,
411 })
412 .collect()
413}
414
415#[must_use]
417pub fn transform_all_item_damage_scaling(dbc: &DbcData) -> Vec<ItemDamageScalingFlat> {
418 let one_hand = dbc
419 .item_damage_one_hand
420 .values()
421 .map(|row| ItemDamageScalingFlat {
422 id: row.ID,
423 weapon_type: "one_hand".to_string(),
424 item_level: row.ItemLevel,
425 quality_0: row.Quality_0,
426 quality_1: row.Quality_1,
427 quality_2: row.Quality_2,
428 quality_3: row.Quality_3,
429 quality_4: row.Quality_4,
430 quality_5: row.Quality_5,
431 quality_6: row.Quality_6,
432 });
433
434 let two_hand = dbc
435 .item_damage_two_hand
436 .values()
437 .map(|row| ItemDamageScalingFlat {
438 id: row.ID + TWO_HAND_ID_OFFSET,
439 weapon_type: "two_hand".to_string(),
440 item_level: row.ItemLevel,
441 quality_0: row.Quality_0,
442 quality_1: row.Quality_1,
443 quality_2: row.Quality_2,
444 quality_3: row.Quality_3,
445 quality_4: row.Quality_4,
446 quality_5: row.Quality_5,
447 quality_6: row.Quality_6,
448 });
449
450 let ammo = dbc
451 .item_damage_ammo
452 .values()
453 .map(|row| ItemDamageScalingFlat {
454 id: row.ID + AMMO_ID_OFFSET,
455 weapon_type: "ammo".to_string(),
456 item_level: row.ItemLevel,
457 quality_0: row.Quality_0,
458 quality_1: row.Quality_1,
459 quality_2: row.Quality_2,
460 quality_3: row.Quality_3,
461 quality_4: row.Quality_4,
462 quality_5: row.Quality_5,
463 quality_6: row.Quality_6,
464 });
465
466 let one_hand_caster =
467 dbc.item_damage_one_hand_caster
468 .values()
469 .map(|row| ItemDamageScalingFlat {
470 id: row.ID + ONE_HAND_CASTER_ID_OFFSET,
471 weapon_type: "one_hand_caster".to_string(),
472 item_level: row.ItemLevel,
473 quality_0: row.Quality_0,
474 quality_1: row.Quality_1,
475 quality_2: row.Quality_2,
476 quality_3: row.Quality_3,
477 quality_4: row.Quality_4,
478 quality_5: row.Quality_5,
479 quality_6: row.Quality_6,
480 });
481
482 let two_hand_caster =
483 dbc.item_damage_two_hand_caster
484 .values()
485 .map(|row| ItemDamageScalingFlat {
486 id: row.ID + TWO_HAND_CASTER_ID_OFFSET,
487 weapon_type: "two_hand_caster".to_string(),
488 item_level: row.ItemLevel,
489 quality_0: row.Quality_0,
490 quality_1: row.Quality_1,
491 quality_2: row.Quality_2,
492 quality_3: row.Quality_3,
493 quality_4: row.Quality_4,
494 quality_5: row.Quality_5,
495 quality_6: row.Quality_6,
496 });
497
498 one_hand
499 .chain(two_hand)
500 .chain(ammo)
501 .chain(one_hand_caster)
502 .chain(two_hand_caster)
503 .collect()
504}
505
506#[must_use]
508pub fn transform_all_cooldown_sets(dbc: &DbcData) -> Vec<CooldownSetFlat> {
509 dbc.cooldown_set
510 .values()
511 .map(|set| {
512 let spells: Vec<CooldownSetSpellEntry> = dbc
513 .cooldown_set_spell
514 .get(&set.ID)
515 .map(|spell_rows| {
516 spell_rows
517 .iter()
518 .map(|s| CooldownSetSpellEntry {
519 spell_id: s.SpellID,
520 category: s.Category,
521 player_condition_id: s.PlayerConditionID,
522 order_index: s.OrderIndex,
523 flags: s.Flags,
524 })
525 .collect()
526 })
527 .unwrap_or_default();
528
529 CooldownSetFlat {
530 id: set.ID,
531 chr_specialization: set.ChrSpecialization,
532 spells,
533 }
534 })
535 .collect()
536}
537
538#[must_use]
540pub fn transform_all_expected_stats(dbc: &DbcData) -> Vec<ExpectedStatFlat> {
541 dbc.expected_stat
542 .values()
543 .map(|row| ExpectedStatFlat {
544 id: row.ID,
545 expansion_id: row.ExpansionID,
546 lvl: row.Lvl,
547 creature_health: row.CreatureHealth,
548 player_health: row.PlayerHealth,
549 creature_auto_attack_dps: row.CreatureAutoAttackDps,
550 creature_armor: row.CreatureArmor,
551 player_mana: row.PlayerMana,
552 player_primary_stat: row.PlayerPrimaryStat,
553 player_secondary_stat: row.PlayerSecondaryStat,
554 armor_constant: row.ArmorConstant,
555 creature_spell_damage: row.CreatureSpellDamage,
556 content_set_id: row.ContentSetID,
557 })
558 .collect()
559}
560
561#[must_use]
563pub fn transform_all_expected_stat_mods(dbc: &DbcData) -> Vec<ExpectedStatModFlat> {
564 dbc.expected_stat_mod
565 .values()
566 .map(|row| ExpectedStatModFlat {
567 id: row.ID,
568 creature_health_mod: row.CreatureHealthMod,
569 player_health_mod: row.PlayerHealthMod,
570 creature_auto_attack_dps_mod: row.CreatureAutoAttackDPSMod,
571 creature_armor_mod: row.CreatureArmorMod,
572 player_mana_mod: row.PlayerManaMod,
573 player_primary_stat_mod: row.PlayerPrimaryStatMod,
574 player_secondary_stat_mod: row.PlayerSecondaryStatMod,
575 armor_constant_mod: row.ArmorConstantMod,
576 creature_spell_damage_mod: row.CreatureSpellDamageMod,
577 })
578 .collect()
579}
580
581#[must_use]
583pub fn transform_all_power_types(dbc: &DbcData) -> Vec<PowerTypeFlat> {
584 dbc.power_type
585 .values()
586 .map(|row| PowerTypeFlat {
587 id: row.ID,
588 name_global_string_tag: row.NameGlobalStringTag.clone().unwrap_or_default(),
589 cost_global_string_tag: row.CostGlobalStringTag.clone().unwrap_or_default(),
590 power_type_enum: row.PowerTypeEnum,
591 min_power: row.MinPower,
592 max_base_power: row.MaxBasePower,
593 center_power: row.CenterPower,
594 default_power: row.DefaultPower,
595 display_modifier: row.DisplayModifier,
596 regen_interrupt_time_ms: row.RegenInterruptTimeMS,
597 regen_peace: row.RegenPeace,
598 regen_combat: row.RegenCombat,
599 flags: row.Flags,
600 })
601 .collect()
602}
603
604#[must_use]
606pub fn transform_all_item_armor_quality(dbc: &DbcData) -> Vec<ItemArmorQualityFlat> {
607 dbc.item_armor_quality
608 .values()
609 .map(|row| ItemArmorQualityFlat {
610 id: row.ID,
611 qualitymod_0: row.Qualitymod_0,
612 qualitymod_1: row.Qualitymod_1,
613 qualitymod_2: row.Qualitymod_2,
614 qualitymod_3: row.Qualitymod_3,
615 qualitymod_4: row.Qualitymod_4,
616 qualitymod_5: row.Qualitymod_5,
617 qualitymod_6: row.Qualitymod_6,
618 })
619 .collect()
620}
621
622#[must_use]
624pub fn transform_all_item_armor_shield(dbc: &DbcData) -> Vec<ItemArmorShieldFlat> {
625 dbc.item_armor_shield
626 .values()
627 .map(|row| ItemArmorShieldFlat {
628 id: row.ID,
629 item_level: row.ItemLevel,
630 quality_0: row.Quality_0,
631 quality_1: row.Quality_1,
632 quality_2: row.Quality_2,
633 quality_3: row.Quality_3,
634 quality_4: row.Quality_4,
635 quality_5: row.Quality_5,
636 quality_6: row.Quality_6,
637 })
638 .collect()
639}
640
641#[must_use]
643pub fn transform_all_item_armor_total(dbc: &DbcData) -> Vec<ItemArmorTotalFlat> {
644 dbc.item_armor_total
645 .values()
646 .map(|row| ItemArmorTotalFlat {
647 id: row.ID,
648 item_level: row.ItemLevel,
649 cloth: row.Cloth,
650 leather: row.Leather,
651 mail: row.Mail,
652 plate: row.Plate,
653 })
654 .collect()
655}
656
657#[must_use]
659pub fn transform_all_specialization_spells(dbc: &DbcData) -> Vec<SpecializationSpellFlat> {
660 dbc.specialization_spells
661 .values()
662 .flatten()
663 .map(|row| SpecializationSpellFlat {
664 id: row.ID,
665 spec_id: row.SpecID,
666 spell_id: row.SpellID,
667 overrides_spell_id: row.OverridesSpellID,
668 })
669 .collect()
670}
671
672#[must_use]
674pub fn transform_all_armor_location(dbc: &DbcData) -> Vec<ArmorLocationFlat> {
675 dbc.armor_location
676 .values()
677 .map(|row| ArmorLocationFlat {
678 id: row.ID,
679 cloth_modifier: row.Clothmodifier,
680 leather_modifier: row.Leathermodifier,
681 chain_modifier: row.Chainmodifier,
682 plate_modifier: row.Platemodifier,
683 modifier: row.Modifier,
684 })
685 .collect()
686}
687
688#[cfg(test)]
689mod tests {
690 use googletest::prelude::*;
691
692 use super::*;
693 use crate::parsers::{
694 dbc::rows::{
695 CooldownSetRow, CooldownSetSpellRow, CraftingDataEnchantQualityRow,
696 CraftingDataItemQualityRow, CraftingDataRow, CraftingQualityRow, ItemDamageAmmoRow,
697 ItemDamageOneHandCasterRow, ItemDamageOneHandRow, ItemDamageTwoHandCasterRow,
698 ItemDamageTwoHandRow, ItemEffectRow, ItemXItemEffectRow, SpellEquippedItemsRow,
699 SpellItemEnchantmentRow,
700 },
701 transform::fixtures::{
702 dbc, item_row, skill_line_ability_row, spell_effect_row, spell_name_row,
703 },
704 };
705
706 fn enchantment_row(id: i32) -> SpellItemEnchantmentRow {
707 SpellItemEnchantmentRow {
708 ID: id,
709 Name_lang: None,
710 HordeName_lang: None,
711 Duration: 0,
712 EffectArg_0: 0,
713 EffectArg_1: 0,
714 EffectArg_2: 0,
715 Flags: 0,
716 EffectScalingPoints_0: 0.0,
717 EffectScalingPoints_1: 0.0,
718 EffectScalingPoints_2: 0.0,
719 IconFileDataID: 0,
720 ItemLevelMin: 0,
721 ItemLevelMax: 0,
722 TransmogUseConditionID: 0,
723 TransmogCost: 0,
724 EffectPointsMin_0: 0.0,
725 EffectPointsMin_1: 0.0,
726 EffectPointsMin_2: 0.0,
727 ItemVisual: 0,
728 RequiredSkillID: 0,
729 RequiredSkillRank: 0,
730 ItemLevel: 0,
731 Charges: 0,
732 Effect_0: 0,
733 Effect_1: 0,
734 Effect_2: 0,
735 ScalingClass: 0,
736 ScalingClassRestricted: 0,
737 Condition_ID: 0,
738 MinLevel: 0,
739 MaxLevel: 0,
740 }
741 }
742
743 fn equipped_items(
744 spell_id: i32,
745 inventory_type_mask: i32,
746 subclass_mask: i32,
747 ) -> SpellEquippedItemsRow {
748 SpellEquippedItemsRow {
749 ID: spell_id,
750 SpellID: spell_id,
751 EquippedItemClass: 4,
752 EquippedItemInvTypes: inventory_type_mask,
753 EquippedItemSubclass: subclass_mask,
754 }
755 }
756
757 fn add_effect(dbc: &mut DbcData, spell_id: i32, kind: i32, misc: i32, item_id: i32) {
758 let mut effect = spell_effect_row(spell_id, kind, 0);
759
760 effect.EffectMiscValue_0 = misc;
761 effect.EffectItemType = item_id;
762 dbc.spell_effect.insert(spell_id, vec![effect]);
763 }
764
765 fn add_item_enchant(
766 dbc: &mut DbcData,
767 item_id: i32,
768 item_effect_id: i32,
769 spell_id: i32,
770 enchant_id: i32,
771 inventory_type_mask: i32,
772 subclass_mask: i32,
773 ) {
774 dbc.item_x_item_effect.insert(
775 item_id,
776 vec![ItemXItemEffectRow {
777 ID: item_effect_id,
778 ItemEffectID: item_effect_id,
779 ItemID: item_id,
780 }],
781 );
782 dbc.item_effect.insert(
783 item_effect_id,
784 ItemEffectRow {
785 ID: item_effect_id,
786 LegacySlotIndex: 0,
787 TriggerType: 0,
788 Charges: 0,
789 CoolDownMSec: 0,
790 CategoryCoolDownMSec: 0,
791 SpellCategoryID: 0,
792 SpellID: spell_id,
793 ChrSpecializationID: 0,
794 },
795 );
796 add_effect(
797 dbc,
798 spell_id,
799 EFFECT_ENCHANT_ITEM_PERMANENTLY,
800 enchant_id,
801 0,
802 );
803 dbc.spell_equipped_items.insert(
804 spell_id,
805 equipped_items(spell_id, inventory_type_mask, subclass_mask),
806 );
807 }
808
809 #[gtest]
810 fn permanent_enchants_follow_all_simc_effect_paths_and_quality_joins() -> Result<()> {
811 let mut dbc = dbc();
812
813 for (spell_id, name, effect_type) in [
814 (100, "Plain Enchant", EFFECT_ENCHANT_ITEM_PERMANENTLY),
815 (200, "Created Item Enchant", EFFECT_CREATE_ITEM),
816 (
817 300,
818 "Enchant Ring - Eyes of the Eagle",
819 EFFECT_CRAFTING_DATA_ENCHANT,
820 ),
821 (400, "Forest Hunter's Armor Kit", EFFECT_CRAFTING_DATA_ITEM),
822 ] {
823 dbc.spell_name
824 .insert(spell_id, spell_name_row(spell_id, Some(name)));
825 dbc.skill_line_ability.insert(
826 spell_id,
827 vec![skill_line_ability_row(spell_id, 333, spell_id)],
828 );
829 add_effect(
830 &mut dbc,
831 spell_id,
832 effect_type,
833 spell_id + 1_000,
834 spell_id + 2_000,
835 );
836 }
837
838 dbc.spell_item_enchantment
839 .insert(1_100, enchantment_row(1_100));
840 dbc.spell_equipped_items
841 .insert(100, equipped_items(100, 1 << 16, 1 << 2));
842
843 dbc.spell_item_enchantment
844 .insert(1_200, enchantment_row(1_200));
845 add_item_enchant(&mut dbc, 2_200, 3_200, 4_200, 1_200, 1 << 13, 1 << 3);
846
847 dbc.crafting_data.insert(
848 1_300,
849 CraftingDataRow {
850 ID: 1_300,
851 Type: 0,
852 CraftingDifficultyID: 0,
853 CraftedItemID: 0,
854 ItemBonusTreeID: 0,
855 },
856 );
857 dbc.crafting_quality.insert(
858 13,
859 CraftingQualityRow {
860 ID: 13,
861 QualityTier: 2,
862 CraftingQualityAtlasSetID: 0,
863 },
864 );
865 dbc.crafting_data_enchant_quality.insert(
866 1_300,
867 vec![CraftingDataEnchantQualityRow {
868 ID: 1,
869 Rank: 13,
870 SpellItemEnchantmentID: 7_967,
871 ItemID: 0,
872 CraftingDataID: 1_300,
873 }],
874 );
875 dbc.spell_item_enchantment
876 .insert(7_967, enchantment_row(7_967));
877 dbc.spell_equipped_items
878 .insert(300, equipped_items(300, 1 << 11, 0x1f));
879
880 dbc.crafting_data.insert(
881 1_400,
882 CraftingDataRow {
883 ID: 1_400,
884 Type: 0,
885 CraftingDifficultyID: 0,
886 CraftedItemID: 0,
887 ItemBonusTreeID: 0,
888 },
889 );
890 dbc.crafting_quality.insert(
891 14,
892 CraftingQualityRow {
893 ID: 14,
894 QualityTier: 2,
895 CraftingQualityAtlasSetID: 0,
896 },
897 );
898 let mut quality_item = item_row(2_400, 8, 8, 0);
899
900 quality_item.CraftingQualityID = 14;
901 dbc.item.insert(2_400, quality_item);
902 dbc.crafting_data_item_quality.insert(
903 1_400,
904 vec![CraftingDataItemQualityRow {
905 ID: 2,
906 ItemID: 2_400,
907 CraftingDataID: 1_400,
908 }],
909 );
910 dbc.spell_item_enchantment
911 .insert(8_159, enchantment_row(8_159));
912 add_item_enchant(&mut dbc, 2_400, 3_400, 4_400, 8_159, 1 << 7, 0x1e);
913
914 let result = transform_all_permanent_enchants(&dbc);
915
916 verify_that!(
917 result,
918 unordered_elements_are![
919 eq(&PermanentEnchantFlat {
920 enchant_id: 1_100,
921 rank: 0,
922 item_class: 4,
923 inventory_type_mask: 1 << 16,
924 subclass_mask: 1 << 2,
925 tokenized_name: "plain_enchant".to_string(),
926 }),
927 eq(&PermanentEnchantFlat {
928 enchant_id: 1_200,
929 rank: 0,
930 item_class: 4,
931 inventory_type_mask: 1 << 13,
932 subclass_mask: 1 << 3,
933 tokenized_name: "created_item_enchant".to_string(),
934 }),
935 eq(&PermanentEnchantFlat {
936 enchant_id: 7_967,
937 rank: 2,
938 item_class: 4,
939 inventory_type_mask: 1 << 11,
940 subclass_mask: 0x1f,
941 tokenized_name: "enchant_ring__eyes_of_the_eagle".to_string(),
942 }),
943 eq(&PermanentEnchantFlat {
944 enchant_id: 8_159,
945 rank: 2,
946 item_class: 4,
947 inventory_type_mask: 1 << 7,
948 subclass_mask: 0x1e,
949 tokenized_name: "forest_hunters_armor_kit".to_string(),
950 }),
951 ]
952 )
953 }
954
955 #[gtest]
956 fn item_damage_scaling_merges_with_offsets() -> Result<()> {
957 let mut dbc = dbc();
958
959 dbc.item_damage_one_hand.insert(
960 10,
961 ItemDamageOneHandRow {
962 ID: 10,
963 ItemLevel: 100,
964 Quality_0: 0.0,
965 Quality_1: 0.0,
966 Quality_2: 0.0,
967 Quality_3: 0.0,
968 Quality_4: 0.0,
969 Quality_5: 0.0,
970 Quality_6: 0.0,
971 },
972 );
973 dbc.item_damage_two_hand.insert(
974 10,
975 ItemDamageTwoHandRow {
976 ID: 10,
977 ItemLevel: 100,
978 Quality_0: 0.0,
979 Quality_1: 0.0,
980 Quality_2: 0.0,
981 Quality_3: 0.0,
982 Quality_4: 0.0,
983 Quality_5: 0.0,
984 Quality_6: 0.0,
985 },
986 );
987 dbc.item_damage_ammo.insert(
988 10,
989 ItemDamageAmmoRow {
990 ID: 10,
991 ItemLevel: 100,
992 Quality_0: 0.0,
993 Quality_1: 0.0,
994 Quality_2: 0.0,
995 Quality_3: 0.0,
996 Quality_4: 0.0,
997 Quality_5: 0.0,
998 Quality_6: 0.0,
999 },
1000 );
1001 dbc.item_damage_one_hand_caster.insert(
1002 10,
1003 ItemDamageOneHandCasterRow {
1004 ID: 10,
1005 ItemLevel: 100,
1006 Quality_0: 0.0,
1007 Quality_1: 0.0,
1008 Quality_2: 0.0,
1009 Quality_3: 0.0,
1010 Quality_4: 0.0,
1011 Quality_5: 0.0,
1012 Quality_6: 0.0,
1013 },
1014 );
1015 dbc.item_damage_two_hand_caster.insert(
1016 10,
1017 ItemDamageTwoHandCasterRow {
1018 ID: 10,
1019 ItemLevel: 100,
1020 Quality_0: 0.0,
1021 Quality_1: 0.0,
1022 Quality_2: 0.0,
1023 Quality_3: 0.0,
1024 Quality_4: 0.0,
1025 Quality_5: 0.0,
1026 Quality_6: 0.0,
1027 },
1028 );
1029
1030 let result = transform_all_item_damage_scaling(&dbc);
1031
1032 verify_that!(result, len(eq(5)))?;
1033 verify_that!(
1034 result,
1035 contains(matches_pattern!(ItemDamageScalingFlat {
1036 id: eq(&10),
1037 weapon_type: eq("one_hand"),
1038 ..
1039 }))
1040 )?;
1041 verify_that!(
1042 result,
1043 contains(matches_pattern!(ItemDamageScalingFlat {
1044 id: eq(&100_010),
1045 weapon_type: eq("two_hand"),
1046 ..
1047 }))
1048 )?;
1049 verify_that!(
1050 result,
1051 contains(matches_pattern!(ItemDamageScalingFlat {
1052 id: eq(&200_010),
1053 weapon_type: eq("ammo"),
1054 ..
1055 }))
1056 )?;
1057 verify_that!(
1058 result,
1059 contains(matches_pattern!(ItemDamageScalingFlat {
1060 id: eq(&300_010),
1061 weapon_type: eq("one_hand_caster"),
1062 ..
1063 }))
1064 )?;
1065
1066 verify_that!(
1067 result,
1068 contains(matches_pattern!(ItemDamageScalingFlat {
1069 id: eq(&400_010),
1070 weapon_type: eq("two_hand_caster"),
1071 ..
1072 }))
1073 )
1074 }
1075
1076 fn cooldown_set_spell(cooldown_set_id: i32, spell_id: i32) -> CooldownSetSpellRow {
1077 CooldownSetSpellRow {
1078 ID: 0,
1079 CooldownSetID: cooldown_set_id,
1080 SpellID: spell_id,
1081 Category: 7,
1082 PlayerConditionID: 0,
1083 OrderIndex: 0,
1084 Flags: 0,
1085 }
1086 }
1087
1088 #[gtest]
1089 fn cooldown_sets_join_spells() -> Result<()> {
1090 let mut dbc = dbc();
1091
1092 dbc.cooldown_set.insert(
1093 1,
1094 CooldownSetRow {
1095 ID: 1,
1096 ChrSpecialization: 63,
1097 Field: 0,
1098 },
1099 );
1100 dbc.cooldown_set.insert(
1101 2,
1102 CooldownSetRow {
1103 ID: 2,
1104 ChrSpecialization: 64,
1105 Field: 0,
1106 },
1107 );
1108 dbc.cooldown_set_spell.insert(
1109 1,
1110 vec![cooldown_set_spell(1, 100), cooldown_set_spell(1, 200)],
1111 );
1112
1113 let result = transform_all_cooldown_sets(&dbc);
1114
1115 verify_that!(
1116 result,
1117 contains(matches_pattern!(CooldownSetFlat {
1118 id: eq(&1),
1119 spells: all!(
1120 len(eq(2)),
1121 contains(matches_pattern!(CooldownSetSpellEntry {
1122 spell_id: eq(&100),
1123 category: eq(&7),
1124 ..
1125 }))
1126 ),
1127 ..
1128 }))
1129 )?;
1130
1131 verify_that!(
1132 result,
1133 contains(matches_pattern!(CooldownSetFlat {
1134 id: eq(&2),
1135 spells: len(eq(0)),
1136 ..
1137 }))
1138 )
1139 }
1140}