Skip to main content

dotzuki_engine/items/
kind.rs

1//! # Item kind classification
2//!
3//! [`ItemKind`] classifies items into gameplay categories that determine
4//! default behaviours for selling, discarding, stacking, and consumption.
5//!
6//! | Variant | Default sellable | Default discardable | Default stackable | Default consumed on use |
7//! |---------|-----------------|-------------------|------------------|------------------------|
8//! | Consumable | true | true | true | true |
9//! | Equipment | true | true | false | false |
10//! | KeyItem | false | false | false | false |
11//! | Evolution | false | true | true | true |
12//! | StatBoost | true | true | true | true |
13//! | Currency | true | true | true | true |
14//! | TeachMove | false | true | false | true |
15//! | Custom(Id) | true | true | true | true |
16
17use std::fmt::Debug;
18use std::hash::Hash;
19
20/// Broad classification of an item's purpose, influencing default shop, bag,
21/// and usage behaviour.
22///
23/// The generic parameter `Id` allows a game-specific crate to supply
24/// additional custom kinds (e.g. `ItemKind::Custom(GameItemKind::TM)`).
25///
26/// # Default methods
27///
28/// Each variant has baked-in defaults for four orthogonal behaviours.
29/// A game's [`ItemProvider`](crate::items::ItemProvider) implementation may
30/// override any of these per-item.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum ItemKind<Id: Copy + Eq + Hash + Debug> {
33    /// Single-use recovery / utility items (potions, antidotes, repels).
34    Consumable,
35    /// Equippable gear (weapons, armour, accessories).
36    Equipment,
37    /// Plot-critical items that cannot be sold, discarded, or stacked.
38    KeyItem,
39    /// Items that trigger a monster evolution.
40    Evolution,
41    /// Permanent stat-enhancing items (vitamins, feathers).
42    StatBoost,
43    /// In-game currency (dollars, coins, shards).
44    Currency,
45    /// Items that teach a new move (TMs, HMs, skill discs).
46    TeachMove,
47    /// Game-specific kind not covered by the standard variants.
48    Custom(Id),
49}
50
51impl<Id: Copy + Eq + Hash + Debug> ItemKind<Id> {
52    /// Whether shops will buy this kind of item by default.
53    pub fn default_sellable(&self) -> bool {
54        match self {
55            ItemKind::KeyItem | ItemKind::Evolution | ItemKind::TeachMove => false,
56            _ => true,
57        }
58    }
59
60    /// Whether this kind of item can be discarded from the bag by default.
61    pub fn default_discardable(&self) -> bool {
62        match self {
63            ItemKind::KeyItem => false,
64            _ => true,
65        }
66    }
67
68    /// Whether multiple copies of this kind of item stack in a single
69    /// inventory slot by default.
70    pub fn default_stackable(&self) -> bool {
71        match self {
72            ItemKind::Equipment | ItemKind::KeyItem | ItemKind::TeachMove => false,
73            _ => true,
74        }
75    }
76
77    /// Whether this kind of item is consumed (removed from inventory) on
78    /// successful use by default.
79    pub fn default_consumed_on_use(&self) -> bool {
80        match self {
81            ItemKind::Equipment | ItemKind::KeyItem => false,
82            _ => true,
83        }
84    }
85}
86
87// ── Tests ──────────────────────────────────────────────────────────────────
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    /// Dummy id type for testing the Custom variant.
94    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95    enum TestKind {
96        Tm,
97    }
98
99    /// Shorthand: a fully-qualified [`ItemKind`] with the test id type.
100    type Kind = ItemKind<TestKind>;
101
102    // ── default_sellable ──────────────────────────────────────────────────
103
104    #[test]
105    fn sellable_consumable() {
106        assert!(Kind::Consumable.default_sellable());
107    }
108
109    #[test]
110    fn sellable_equipment() {
111        assert!(Kind::Equipment.default_sellable());
112    }
113
114    #[test]
115    fn sellable_key_item() {
116        assert!(!Kind::KeyItem.default_sellable());
117    }
118
119    #[test]
120    fn sellable_evolution() {
121        assert!(!Kind::Evolution.default_sellable());
122    }
123
124    #[test]
125    fn sellable_stat_boost() {
126        assert!(Kind::StatBoost.default_sellable());
127    }
128
129    #[test]
130    fn sellable_currency() {
131        assert!(Kind::Currency.default_sellable());
132    }
133
134    #[test]
135    fn sellable_teach_move() {
136        assert!(!Kind::TeachMove.default_sellable());
137    }
138
139    #[test]
140    fn sellable_custom() {
141        assert!(Kind::Custom(TestKind::Tm).default_sellable());
142    }
143
144    // ── default_discardable ───────────────────────────────────────────────
145
146    #[test]
147    fn discardable_consumable() {
148        assert!(Kind::Consumable.default_discardable());
149    }
150
151    #[test]
152    fn discardable_equipment() {
153        assert!(Kind::Equipment.default_discardable());
154    }
155
156    #[test]
157    fn discardable_key_item() {
158        assert!(!Kind::KeyItem.default_discardable());
159    }
160
161    #[test]
162    fn discardable_evolution() {
163        assert!(Kind::Evolution.default_discardable());
164    }
165
166    #[test]
167    fn discardable_stat_boost() {
168        assert!(Kind::StatBoost.default_discardable());
169    }
170
171    #[test]
172    fn discardable_currency() {
173        assert!(Kind::Currency.default_discardable());
174    }
175
176    #[test]
177    fn discardable_teach_move() {
178        assert!(Kind::TeachMove.default_discardable());
179    }
180
181    #[test]
182    fn discardable_custom() {
183        assert!(Kind::Custom(TestKind::Tm).default_discardable());
184    }
185
186    // ── default_stackable ─────────────────────────────────────────────────
187
188    #[test]
189    fn stackable_consumable() {
190        assert!(Kind::Consumable.default_stackable());
191    }
192
193    #[test]
194    fn stackable_equipment() {
195        assert!(!Kind::Equipment.default_stackable());
196    }
197
198    #[test]
199    fn stackable_key_item() {
200        assert!(!Kind::KeyItem.default_stackable());
201    }
202
203    #[test]
204    fn stackable_evolution() {
205        assert!(Kind::Evolution.default_stackable());
206    }
207
208    #[test]
209    fn stackable_stat_boost() {
210        assert!(Kind::StatBoost.default_stackable());
211    }
212
213    #[test]
214    fn stackable_currency() {
215        assert!(Kind::Currency.default_stackable());
216    }
217
218    #[test]
219    fn stackable_teach_move() {
220        assert!(!Kind::TeachMove.default_stackable());
221    }
222
223    #[test]
224    fn stackable_custom() {
225        assert!(Kind::Custom(TestKind::Tm).default_stackable());
226    }
227
228    // ── default_consumed_on_use ────────────────────────────────────────────
229
230    #[test]
231    fn consumed_consumable() {
232        assert!(Kind::Consumable.default_consumed_on_use());
233    }
234
235    #[test]
236    fn consumed_equipment() {
237        assert!(!Kind::Equipment.default_consumed_on_use());
238    }
239
240    #[test]
241    fn consumed_key_item() {
242        assert!(!Kind::KeyItem.default_consumed_on_use());
243    }
244
245    #[test]
246    fn consumed_evolution() {
247        assert!(Kind::Evolution.default_consumed_on_use());
248    }
249
250    #[test]
251    fn consumed_stat_boost() {
252        assert!(Kind::StatBoost.default_consumed_on_use());
253    }
254
255    #[test]
256    fn consumed_currency() {
257        assert!(Kind::Currency.default_consumed_on_use());
258    }
259
260    #[test]
261    fn consumed_teach_move() {
262        assert!(Kind::TeachMove.default_consumed_on_use());
263    }
264
265    #[test]
266    fn consumed_custom() {
267        assert!(Kind::Custom(TestKind::Tm).default_consumed_on_use());
268    }
269}