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 spell_category_id: s.SpellCategoryID,
525 equip_slot: s.EquipSlot,
526 field_12_1_0_68209_009: s.Field_12_1_0_68209_009,
527 })
528 .collect()
529 })
530 .unwrap_or_default();
531
532 CooldownSetFlat {
533 id: set.ID,
534 chr_specialization: set.ChrSpecialization,
535 player_condition_id: set.PlayerConditionID,
536 spells,
537 }
538 })
539 .collect()
540}
541
542#[must_use]
544pub fn transform_all_expected_stats(dbc: &DbcData) -> Vec<ExpectedStatFlat> {
545 dbc.expected_stat
546 .values()
547 .map(|row| ExpectedStatFlat {
548 id: row.ID,
549 expansion_id: row.ExpansionID,
550 lvl: row.Lvl,
551 creature_health: row.CreatureHealth,
552 player_health: row.PlayerHealth,
553 creature_auto_attack_dps: row.CreatureAutoAttackDps,
554 creature_armor: row.CreatureArmor,
555 player_mana: row.PlayerMana,
556 player_primary_stat: row.PlayerPrimaryStat,
557 player_secondary_stat: row.PlayerSecondaryStat,
558 armor_constant: row.ArmorConstant,
559 creature_spell_damage: row.CreatureSpellDamage,
560 content_set_id: row.ContentSetID,
561 })
562 .collect()
563}
564
565#[must_use]
567pub fn transform_all_expected_stat_mods(dbc: &DbcData) -> Vec<ExpectedStatModFlat> {
568 dbc.expected_stat_mod
569 .values()
570 .map(|row| ExpectedStatModFlat {
571 id: row.ID,
572 creature_health_mod: row.CreatureHealthMod,
573 player_health_mod: row.PlayerHealthMod,
574 creature_auto_attack_dps_mod: row.CreatureAutoAttackDPSMod,
575 creature_armor_mod: row.CreatureArmorMod,
576 player_mana_mod: row.PlayerManaMod,
577 player_primary_stat_mod: row.PlayerPrimaryStatMod,
578 player_secondary_stat_mod: row.PlayerSecondaryStatMod,
579 armor_constant_mod: row.ArmorConstantMod,
580 creature_spell_damage_mod: row.CreatureSpellDamageMod,
581 })
582 .collect()
583}
584
585#[must_use]
587pub fn transform_all_power_types(dbc: &DbcData) -> Vec<PowerTypeFlat> {
588 dbc.power_type
589 .values()
590 .map(|row| PowerTypeFlat {
591 id: row.ID,
592 name_global_string_tag: row.NameGlobalStringTag.clone().unwrap_or_default(),
593 cost_global_string_tag: row.CostGlobalStringTag.clone().unwrap_or_default(),
594 power_type_enum: row.PowerTypeEnum,
595 min_power: row.MinPower,
596 max_base_power: row.MaxBasePower,
597 center_power: row.CenterPower,
598 default_power: row.DefaultPower,
599 display_modifier: row.DisplayModifier,
600 regen_interrupt_time_ms: row.RegenInterruptTimeMS,
601 regen_peace: row.RegenPeace,
602 regen_combat: row.RegenCombat,
603 flags: row.Flags,
604 })
605 .collect()
606}
607
608#[must_use]
610pub fn transform_all_item_armor_quality(dbc: &DbcData) -> Vec<ItemArmorQualityFlat> {
611 dbc.item_armor_quality
612 .values()
613 .map(|row| ItemArmorQualityFlat {
614 id: row.ID,
615 qualitymod_0: row.Qualitymod_0,
616 qualitymod_1: row.Qualitymod_1,
617 qualitymod_2: row.Qualitymod_2,
618 qualitymod_3: row.Qualitymod_3,
619 qualitymod_4: row.Qualitymod_4,
620 qualitymod_5: row.Qualitymod_5,
621 qualitymod_6: row.Qualitymod_6,
622 })
623 .collect()
624}
625
626#[must_use]
628pub fn transform_all_item_armor_shield(dbc: &DbcData) -> Vec<ItemArmorShieldFlat> {
629 dbc.item_armor_shield
630 .values()
631 .map(|row| ItemArmorShieldFlat {
632 id: row.ID,
633 item_level: row.ItemLevel,
634 quality_0: row.Quality_0,
635 quality_1: row.Quality_1,
636 quality_2: row.Quality_2,
637 quality_3: row.Quality_3,
638 quality_4: row.Quality_4,
639 quality_5: row.Quality_5,
640 quality_6: row.Quality_6,
641 })
642 .collect()
643}
644
645#[must_use]
647pub fn transform_all_item_armor_total(dbc: &DbcData) -> Vec<ItemArmorTotalFlat> {
648 dbc.item_armor_total
649 .values()
650 .map(|row| ItemArmorTotalFlat {
651 id: row.ID,
652 item_level: row.ItemLevel,
653 cloth: row.Cloth,
654 leather: row.Leather,
655 mail: row.Mail,
656 plate: row.Plate,
657 })
658 .collect()
659}
660
661#[must_use]
663pub fn transform_all_specialization_spells(dbc: &DbcData) -> Vec<SpecializationSpellFlat> {
664 dbc.specialization_spells
665 .values()
666 .flatten()
667 .map(|row| SpecializationSpellFlat {
668 id: row.ID,
669 spec_id: row.SpecID,
670 spell_id: row.SpellID,
671 overrides_spell_id: row.OverridesSpellID,
672 })
673 .collect()
674}
675
676#[must_use]
678pub fn transform_all_armor_location(dbc: &DbcData) -> Vec<ArmorLocationFlat> {
679 dbc.armor_location
680 .values()
681 .map(|row| ArmorLocationFlat {
682 id: row.ID,
683 cloth_modifier: row.Clothmodifier,
684 leather_modifier: row.Leathermodifier,
685 chain_modifier: row.Chainmodifier,
686 plate_modifier: row.Platemodifier,
687 modifier: row.Modifier,
688 })
689 .collect()
690}
691
692#[cfg(test)]
693mod tests {
694 use googletest::prelude::*;
695
696 use super::*;
697 use crate::parsers::{
698 dbc::rows::{
699 CooldownSetRow, CooldownSetSpellRow, CraftingDataEnchantQualityRow,
700 CraftingDataItemQualityRow, CraftingDataRow, CraftingQualityRow, ItemDamageAmmoRow,
701 ItemDamageOneHandCasterRow, ItemDamageOneHandRow, ItemDamageTwoHandCasterRow,
702 ItemDamageTwoHandRow, ItemEffectRow, ItemXItemEffectRow, SpellEquippedItemsRow,
703 SpellItemEnchantmentRow,
704 },
705 transform::fixtures::{
706 dbc, item_row, skill_line_ability_row, spell_effect_row, spell_name_row,
707 },
708 };
709
710 fn enchantment_row(id: i32) -> SpellItemEnchantmentRow {
711 SpellItemEnchantmentRow {
712 ID: id,
713 Name_lang: None,
714 HordeName_lang: None,
715 Duration: 0,
716 EffectArg_0: 0,
717 EffectArg_1: 0,
718 EffectArg_2: 0,
719 Flags: 0,
720 EffectScalingPoints_0: 0.0,
721 EffectScalingPoints_1: 0.0,
722 EffectScalingPoints_2: 0.0,
723 IconFileDataID: 0,
724 ItemLevelMin: 0,
725 ItemLevelMax: 0,
726 TransmogUseConditionID: 0,
727 TransmogCost: 0,
728 EffectPointsMin_0: 0.0,
729 EffectPointsMin_1: 0.0,
730 EffectPointsMin_2: 0.0,
731 ItemVisual: 0,
732 RequiredSkillID: 0,
733 RequiredSkillRank: 0,
734 ItemLevel: 0,
735 Charges: 0,
736 Effect_0: 0,
737 Effect_1: 0,
738 Effect_2: 0,
739 ScalingClass: 0,
740 ScalingClassRestricted: 0,
741 Condition_ID: 0,
742 MinLevel: 0,
743 MaxLevel: 0,
744 }
745 }
746
747 fn equipped_items(
748 spell_id: i32,
749 inventory_type_mask: i32,
750 subclass_mask: i32,
751 ) -> SpellEquippedItemsRow {
752 SpellEquippedItemsRow {
753 ID: spell_id,
754 SpellID: spell_id,
755 EquippedItemClass: 4,
756 EquippedItemInvTypes: inventory_type_mask,
757 EquippedItemSubclass: subclass_mask,
758 }
759 }
760
761 fn add_effect(dbc: &mut DbcData, spell_id: i32, kind: i32, misc: i32, item_id: i32) {
762 let mut effect = spell_effect_row(spell_id, kind, 0);
763
764 effect.EffectMiscValue_0 = misc;
765 effect.EffectItemType = item_id;
766 dbc.spell_effect.insert(spell_id, vec![effect]);
767 }
768
769 fn add_item_enchant(
770 dbc: &mut DbcData,
771 item_id: i32,
772 item_effect_id: i32,
773 spell_id: i32,
774 enchant_id: i32,
775 inventory_type_mask: i32,
776 subclass_mask: i32,
777 ) {
778 dbc.item_x_item_effect.insert(
779 item_id,
780 vec![ItemXItemEffectRow {
781 ID: item_effect_id,
782 ItemEffectID: item_effect_id,
783 ItemID: item_id,
784 }],
785 );
786 dbc.item_effect.insert(
787 item_effect_id,
788 ItemEffectRow {
789 ID: item_effect_id,
790 LegacySlotIndex: 0,
791 TriggerType: 0,
792 Charges: 0,
793 CoolDownMSec: 0,
794 CategoryCoolDownMSec: 0,
795 SpellCategoryID: 0,
796 SpellID: spell_id,
797 ChrSpecializationID: 0,
798 },
799 );
800 add_effect(
801 dbc,
802 spell_id,
803 EFFECT_ENCHANT_ITEM_PERMANENTLY,
804 enchant_id,
805 0,
806 );
807 dbc.spell_equipped_items.insert(
808 spell_id,
809 equipped_items(spell_id, inventory_type_mask, subclass_mask),
810 );
811 }
812
813 #[gtest]
814 fn permanent_enchants_follow_all_effect_paths_and_quality_joins() -> Result<()> {
815 let mut dbc = dbc();
816
817 for (spell_id, name, effect_type) in [
818 (100, "Plain Enchant", EFFECT_ENCHANT_ITEM_PERMANENTLY),
819 (200, "Created Item Enchant", EFFECT_CREATE_ITEM),
820 (
821 300,
822 "Enchant Ring - Eyes of the Eagle",
823 EFFECT_CRAFTING_DATA_ENCHANT,
824 ),
825 (400, "Forest Hunter's Armor Kit", EFFECT_CRAFTING_DATA_ITEM),
826 ] {
827 dbc.spell_name
828 .insert(spell_id, spell_name_row(spell_id, Some(name)));
829 dbc.skill_line_ability.insert(
830 spell_id,
831 vec![skill_line_ability_row(spell_id, 333, spell_id)],
832 );
833 add_effect(
834 &mut dbc,
835 spell_id,
836 effect_type,
837 spell_id + 1_000,
838 spell_id + 2_000,
839 );
840 }
841
842 dbc.spell_item_enchantment
843 .insert(1_100, enchantment_row(1_100));
844 dbc.spell_equipped_items
845 .insert(100, equipped_items(100, 1 << 16, 1 << 2));
846
847 dbc.spell_item_enchantment
848 .insert(1_200, enchantment_row(1_200));
849 add_item_enchant(&mut dbc, 2_200, 3_200, 4_200, 1_200, 1 << 13, 1 << 3);
850
851 dbc.crafting_data.insert(
852 1_300,
853 CraftingDataRow {
854 ID: 1_300,
855 Type: 0,
856 CraftingDifficultyID: 0,
857 CraftedItemID: 0,
858 ItemBonusTreeID: 0,
859 },
860 );
861 dbc.crafting_quality.insert(
862 13,
863 CraftingQualityRow {
864 ID: 13,
865 QualityTier: 2,
866 CraftingQualityAtlasSetID: 0,
867 },
868 );
869 dbc.crafting_data_enchant_quality.insert(
870 1_300,
871 vec![CraftingDataEnchantQualityRow {
872 ID: 1,
873 Rank: 13,
874 SpellItemEnchantmentID: 7_967,
875 ItemID: 0,
876 CraftingDataID: 1_300,
877 }],
878 );
879 dbc.spell_item_enchantment
880 .insert(7_967, enchantment_row(7_967));
881 dbc.spell_equipped_items
882 .insert(300, equipped_items(300, 1 << 11, 0x1f));
883
884 dbc.crafting_data.insert(
885 1_400,
886 CraftingDataRow {
887 ID: 1_400,
888 Type: 0,
889 CraftingDifficultyID: 0,
890 CraftedItemID: 0,
891 ItemBonusTreeID: 0,
892 },
893 );
894 dbc.crafting_quality.insert(
895 14,
896 CraftingQualityRow {
897 ID: 14,
898 QualityTier: 2,
899 CraftingQualityAtlasSetID: 0,
900 },
901 );
902 let mut quality_item = item_row(2_400, 8, 8, 0);
903
904 quality_item.CraftingQualityID = 14;
905 dbc.item.insert(2_400, quality_item);
906 dbc.crafting_data_item_quality.insert(
907 1_400,
908 vec![CraftingDataItemQualityRow {
909 ID: 2,
910 ItemID: 2_400,
911 CraftingDataID: 1_400,
912 }],
913 );
914 dbc.spell_item_enchantment
915 .insert(8_159, enchantment_row(8_159));
916 add_item_enchant(&mut dbc, 2_400, 3_400, 4_400, 8_159, 1 << 7, 0x1e);
917
918 let result = transform_all_permanent_enchants(&dbc);
919
920 verify_that!(
921 result,
922 unordered_elements_are![
923 eq(&PermanentEnchantFlat {
924 enchant_id: 1_100,
925 rank: 0,
926 item_class: 4,
927 inventory_type_mask: 1 << 16,
928 subclass_mask: 1 << 2,
929 tokenized_name: "plain_enchant".to_string(),
930 }),
931 eq(&PermanentEnchantFlat {
932 enchant_id: 1_200,
933 rank: 0,
934 item_class: 4,
935 inventory_type_mask: 1 << 13,
936 subclass_mask: 1 << 3,
937 tokenized_name: "created_item_enchant".to_string(),
938 }),
939 eq(&PermanentEnchantFlat {
940 enchant_id: 7_967,
941 rank: 2,
942 item_class: 4,
943 inventory_type_mask: 1 << 11,
944 subclass_mask: 0x1f,
945 tokenized_name: "enchant_ring__eyes_of_the_eagle".to_string(),
946 }),
947 eq(&PermanentEnchantFlat {
948 enchant_id: 8_159,
949 rank: 2,
950 item_class: 4,
951 inventory_type_mask: 1 << 7,
952 subclass_mask: 0x1e,
953 tokenized_name: "forest_hunters_armor_kit".to_string(),
954 }),
955 ]
956 )
957 }
958
959 #[gtest]
960 fn item_damage_scaling_merges_with_offsets() -> Result<()> {
961 let mut dbc = dbc();
962
963 dbc.item_damage_one_hand.insert(
964 10,
965 ItemDamageOneHandRow {
966 ID: 10,
967 ItemLevel: 100,
968 Quality_0: 0.0,
969 Quality_1: 0.0,
970 Quality_2: 0.0,
971 Quality_3: 0.0,
972 Quality_4: 0.0,
973 Quality_5: 0.0,
974 Quality_6: 0.0,
975 },
976 );
977 dbc.item_damage_two_hand.insert(
978 10,
979 ItemDamageTwoHandRow {
980 ID: 10,
981 ItemLevel: 100,
982 Quality_0: 0.0,
983 Quality_1: 0.0,
984 Quality_2: 0.0,
985 Quality_3: 0.0,
986 Quality_4: 0.0,
987 Quality_5: 0.0,
988 Quality_6: 0.0,
989 },
990 );
991 dbc.item_damage_ammo.insert(
992 10,
993 ItemDamageAmmoRow {
994 ID: 10,
995 ItemLevel: 100,
996 Quality_0: 0.0,
997 Quality_1: 0.0,
998 Quality_2: 0.0,
999 Quality_3: 0.0,
1000 Quality_4: 0.0,
1001 Quality_5: 0.0,
1002 Quality_6: 0.0,
1003 },
1004 );
1005 dbc.item_damage_one_hand_caster.insert(
1006 10,
1007 ItemDamageOneHandCasterRow {
1008 ID: 10,
1009 ItemLevel: 100,
1010 Quality_0: 0.0,
1011 Quality_1: 0.0,
1012 Quality_2: 0.0,
1013 Quality_3: 0.0,
1014 Quality_4: 0.0,
1015 Quality_5: 0.0,
1016 Quality_6: 0.0,
1017 },
1018 );
1019 dbc.item_damage_two_hand_caster.insert(
1020 10,
1021 ItemDamageTwoHandCasterRow {
1022 ID: 10,
1023 ItemLevel: 100,
1024 Quality_0: 0.0,
1025 Quality_1: 0.0,
1026 Quality_2: 0.0,
1027 Quality_3: 0.0,
1028 Quality_4: 0.0,
1029 Quality_5: 0.0,
1030 Quality_6: 0.0,
1031 },
1032 );
1033
1034 let result = transform_all_item_damage_scaling(&dbc);
1035
1036 verify_that!(result, len(eq(5)))?;
1037 verify_that!(
1038 result,
1039 contains(matches_pattern!(ItemDamageScalingFlat {
1040 id: eq(&10),
1041 weapon_type: eq("one_hand"),
1042 ..
1043 }))
1044 )?;
1045 verify_that!(
1046 result,
1047 contains(matches_pattern!(ItemDamageScalingFlat {
1048 id: eq(&100_010),
1049 weapon_type: eq("two_hand"),
1050 ..
1051 }))
1052 )?;
1053 verify_that!(
1054 result,
1055 contains(matches_pattern!(ItemDamageScalingFlat {
1056 id: eq(&200_010),
1057 weapon_type: eq("ammo"),
1058 ..
1059 }))
1060 )?;
1061 verify_that!(
1062 result,
1063 contains(matches_pattern!(ItemDamageScalingFlat {
1064 id: eq(&300_010),
1065 weapon_type: eq("one_hand_caster"),
1066 ..
1067 }))
1068 )?;
1069
1070 verify_that!(
1071 result,
1072 contains(matches_pattern!(ItemDamageScalingFlat {
1073 id: eq(&400_010),
1074 weapon_type: eq("two_hand_caster"),
1075 ..
1076 }))
1077 )
1078 }
1079
1080 fn cooldown_set_spell(cooldown_set_id: i32, spell_id: i32) -> CooldownSetSpellRow {
1081 CooldownSetSpellRow {
1082 ID: 0,
1083 CooldownSetID: cooldown_set_id,
1084 SpellID: spell_id,
1085 Category: 7,
1086 PlayerConditionID: 0,
1087 OrderIndex: 0,
1088 Flags: 0,
1089 SpellCategoryID: 30,
1090 EquipSlot: 13,
1091 Field_12_1_0_68209_009: 1,
1092 }
1093 }
1094
1095 #[gtest]
1096 fn cooldown_sets_join_spells() -> Result<()> {
1097 let mut dbc = dbc();
1098
1099 dbc.cooldown_set.insert(
1100 1,
1101 CooldownSetRow {
1102 ID: 1,
1103 ChrSpecialization: 63,
1104 Field: 0,
1105 PlayerConditionID: 160_935,
1106 },
1107 );
1108 dbc.cooldown_set.insert(
1109 2,
1110 CooldownSetRow {
1111 ID: 2,
1112 ChrSpecialization: 64,
1113 Field: 0,
1114 PlayerConditionID: 0,
1115 },
1116 );
1117 dbc.cooldown_set_spell.insert(
1118 1,
1119 vec![cooldown_set_spell(1, 100), cooldown_set_spell(1, 200)],
1120 );
1121
1122 let result = transform_all_cooldown_sets(&dbc);
1123
1124 verify_that!(
1125 result,
1126 contains(matches_pattern!(CooldownSetFlat {
1127 id: eq(&1),
1128 player_condition_id: eq(&160_935),
1129 spells: all!(
1130 len(eq(2)),
1131 contains(matches_pattern!(CooldownSetSpellEntry {
1132 spell_id: eq(&100),
1133 category: eq(&7),
1134 spell_category_id: eq(&30),
1135 equip_slot: eq(&13),
1136 field_12_1_0_68209_009: eq(&1),
1137 ..
1138 }))
1139 ),
1140 ..
1141 }))
1142 )?;
1143
1144 verify_that!(
1145 result,
1146 contains(matches_pattern!(CooldownSetFlat {
1147 id: eq(&2),
1148 spells: len(eq(0)),
1149 ..
1150 }))
1151 )
1152 }
1153}