Skip to main content

wowlab_engine_domain/dbc/semantics/
items.rs

1//! Canonical identities for raw item-classification values stored in DBC rows.
2
3use wowlab_engine_gamedata::{ItemBudget, RatingMultiplierSlot, ResolvedGameTables};
4use wowlab_types::data::{ItemDataFlat, ItemQuality, RandPropPointsUse};
5
6/// `Item.InventoryType`.
7#[derive(Clone, Copy, Debug, Eq, PartialEq, num_enum::TryFromPrimitive)]
8#[repr(i32)]
9#[non_exhaustive]
10pub enum InventoryType {
11    NonEquippable = 0,
12    Head = 1,
13    Neck = 2,
14    Shoulder = 3,
15    Body = 4,
16    Chest = 5,
17    Waist = 6,
18    Legs = 7,
19    Feet = 8,
20    Wrists = 9,
21    Hands = 10,
22    Finger = 11,
23    Trinket = 12,
24    Weapon = 13,
25    Shield = 14,
26    Ranged = 15,
27    Cloak = 16,
28    TwoHandWeapon = 17,
29    Bag = 18,
30    Tabard = 19,
31    Robe = 20,
32    MainHandWeapon = 21,
33    OffHandWeapon = 22,
34    Holdable = 23,
35    Ammo = 24,
36    Thrown = 25,
37    RangedRight = 26,
38    Relic = 28,
39}
40
41const INVENTORY_TYPES: &[(i32, InventoryType, &str)] = &[
42    (0, InventoryType::NonEquippable, "Non-equippable"),
43    (1, InventoryType::Head, "Head"),
44    (2, InventoryType::Neck, "Neck"),
45    (3, InventoryType::Shoulder, "Shoulder"),
46    (4, InventoryType::Body, "Shirt"),
47    (5, InventoryType::Chest, "Chest"),
48    (6, InventoryType::Waist, "Waist"),
49    (7, InventoryType::Legs, "Legs"),
50    (8, InventoryType::Feet, "Feet"),
51    (9, InventoryType::Wrists, "Wrist"),
52    (10, InventoryType::Hands, "Hands"),
53    (11, InventoryType::Finger, "Finger"),
54    (12, InventoryType::Trinket, "Trinket"),
55    (13, InventoryType::Weapon, "One-Hand"),
56    (14, InventoryType::Shield, "Shield"),
57    (15, InventoryType::Ranged, "Ranged"),
58    (16, InventoryType::Cloak, "Back"),
59    (17, InventoryType::TwoHandWeapon, "Two-Hand"),
60    (18, InventoryType::Bag, "Bag"),
61    (19, InventoryType::Tabard, "Tabard"),
62    (20, InventoryType::Robe, "Robe"),
63    (21, InventoryType::MainHandWeapon, "Main Hand"),
64    (22, InventoryType::OffHandWeapon, "Off Hand"),
65    (23, InventoryType::Holdable, "Held In Off-Hand"),
66    (24, InventoryType::Ammo, "Ammo"),
67    (25, InventoryType::Thrown, "Thrown"),
68    (26, InventoryType::RangedRight, "Ranged Right"),
69    (28, InventoryType::Relic, "Relic"),
70];
71
72impl InventoryType {
73    #[must_use]
74    pub fn display_name(self) -> &'static str {
75        INVENTORY_TYPES
76            .iter()
77            .find_map(|&(_, kind, name)| (kind == self).then_some(name))
78            .unwrap_or("Unknown")
79    }
80}
81
82/// `Item.ClassID`.
83#[derive(Clone, Copy, Debug, Eq, PartialEq, num_enum::TryFromPrimitive)]
84#[repr(i32)]
85#[non_exhaustive]
86pub enum ItemClass {
87    Consumable = 0,
88    Container = 1,
89    Weapon = 2,
90    Gem = 3,
91    Armor = 4,
92    Reagent = 5,
93    Projectile = 6,
94    TradeGoods = 7,
95    ItemEnhancement = 8,
96    Recipe = 9,
97    Money = 10,
98    Quiver = 11,
99    Quest = 12,
100    Key = 13,
101    Permanent = 14,
102    Miscellaneous = 15,
103    Glyph = 16,
104    BattlePet = 17,
105    Token = 18,
106    WowToken = 19,
107    Profession = 20,
108}
109
110/// Whether an item satisfies the consumable lookup coordinates used by data resolvers.
111#[must_use]
112pub fn item_matches_consumable_query(item: &ItemDataFlat, name_token: &str, subclass: i32) -> bool {
113    ItemClass::try_from(item.class_id).ok() == Some(ItemClass::Consumable)
114        && item.subclass_id == subclass
115        && wowlab_engine_ports::tokenize_name(&item.name).contains(name_token)
116}
117
118/// `ItemEffect.TriggerType`.
119#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, num_enum::TryFromPrimitive)]
120#[repr(i32)]
121#[non_exhaustive]
122pub enum ItemEffectTrigger {
123    #[default]
124    OnUse = 0,
125    OnEquip = 1,
126    ChanceOnHit = 2,
127    Soulstone = 4,
128    OnUseNoDelay = 5,
129    LearnSpell = 6,
130}
131
132/// `Item.SubclassID` when `ClassID == ItemClass::Consumable`.
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134#[repr(i32)]
135#[non_exhaustive]
136pub enum ConsumableSubclass {
137    Potion = 1,
138    Flask = 3,
139    Food = 5,
140}
141
142/// `Item.SubclassID` when `ClassID == ItemClass::Gem`.
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144#[repr(i32)]
145#[non_exhaustive]
146pub enum GemSubclass {
147    ArtifactRelic = 11,
148}
149
150/// `Item.SubclassID` when `ClassID == ItemClass::Armor`.
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152#[repr(i32)]
153#[non_exhaustive]
154pub enum ArmorSubclass {
155    Cloth = 1,
156    Leather = 2,
157    Mail = 3,
158    Plate = 4,
159}
160
161/// Ranged identities from `Item.SubclassID` when `ClassID == ItemClass::Weapon`.
162#[derive(Clone, Copy, Debug, Eq, PartialEq, num_enum::TryFromPrimitive)]
163#[repr(i32)]
164#[non_exhaustive]
165pub enum WeaponSubclass {
166    Bow = 2,
167    Gun = 3,
168    Thrown = 16,
169    Crossbow = 18,
170    Wand = 19,
171}
172
173/// Resolve the item stat/rating budget after interpreting the raw DBC quality.
174#[must_use]
175pub fn resolve_item_budget(
176    tables: &ResolvedGameTables,
177    item_level: i32,
178    raw_quality: i32,
179    slot: RatingMultiplierSlot,
180) -> Option<ItemBudget> {
181    let quality = ItemQuality::from_dbc(raw_quality)?;
182    let row = tables.rand_prop_points_row(item_level)?;
183    let bucket = quality.rand_prop_points_bucket(RandPropPointsUse::ItemEffect)?;
184    let stat = row.budget(bucket, 0);
185    let rating = stat * crate::stats::rating_multiplier_at_ilvl(tables, slot, item_level);
186
187    Some(ItemBudget {
188        item_level,
189        stat,
190        rating,
191    })
192}
193
194#[cfg(test)]
195mod tests;