1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
use strum_macros::{EnumCount, EnumIter, FromRepr};
/// Slot names for equipment, such as weapons and armor.
#[repr(u16)]
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, FromRepr, EnumIter, EnumCount)]
pub enum EquipSlot {
/// The main hand slot.
MainHand = 0,
/// The secondary/off-hand hand slot.
OffHand,
/// The head slot.
Head,
/// The body slot.
Body,
/// The hands slot.
Hands,
/// What used to be the belt slot, but it still takes space.
Waist,
/// The legs slot.
Legs,
/// The feet slot.
Feet,
/// The ears/earrings slot.
Ears,
/// The neck/necklace slot.
Neck,
/// The wrists/bracelets slot.
Wrists,
/// The right ring slot.
RightRing,
/// The left ring slot.
LeftRing,
/// The soul crystal slot.
SoulCrystal,
}
impl From<&EquipSlotCategory> for EquipSlot {
fn from(value: &EquipSlotCategory) -> Self {
match value {
EquipSlotCategory::MainHand
| EquipSlotCategory::MainHandTwoHand
| EquipSlotCategory::MainHandDualWield => EquipSlot::MainHand,
EquipSlotCategory::OffHand => EquipSlot::OffHand,
EquipSlotCategory::Head => EquipSlot::Head,
EquipSlotCategory::Body
| EquipSlotCategory::BodyNoHead
| EquipSlotCategory::BodyNoHandsLegsFeet
| EquipSlotCategory::BodyNoHeadHandsLegsFeet
| EquipSlotCategory::BodyNoHandsLegs
| EquipSlotCategory::BodyNoLegsFeet
| EquipSlotCategory::BodyNoHands
| EquipSlotCategory::BodyNoLegs => EquipSlot::Body,
EquipSlotCategory::Hands => EquipSlot::Hands,
EquipSlotCategory::Legs | EquipSlotCategory::LegsNoFeet => EquipSlot::Legs,
EquipSlotCategory::Feet => EquipSlot::Feet,
EquipSlotCategory::Earring => EquipSlot::Ears,
EquipSlotCategory::Neck => EquipSlot::Neck,
EquipSlotCategory::Wrists => EquipSlot::Wrists,
EquipSlotCategory::Rings => EquipSlot::LeftRing,
EquipSlotCategory::SoulCrystal => EquipSlot::SoulCrystal,
_ => EquipSlot::Waist,
}
}
}
/// Corresponds to rows in the EquipSlotCategory Excel sheet.
#[repr(u8)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, FromRepr)]
pub enum EquipSlotCategory {
#[default]
/// No applicable slot.
// NOTE: this invariant isn't great for Rust, but is needed for the C api.
Invalid = 0,
/// The main weapon slot. By default this is a single-handed weapon, as two-handers and dual-wield weapons are defined below.
MainHand,
/// The off-hand weapon slot.
OffHand,
/// The head slot.
Head,
/// The body or chest slot.
Body,
/// The hands slot.
Hands,
/// What used to be the belt slot, but it still takes space.
Waist,
/// The legs slot.
Legs,
/// The feet slot.
Feet,
/// The earrings slot.
Earring,
/// The neck slot.
Neck,
/// The wrists slot.
Wrists,
/// The ring slots. This sheet does not differentiate between the two types.
Rings,
/// A two-handed weapon that goes in the main weapon slot.
MainHandTwoHand,
/// This seems to be unused, but both main hand and off-hand are considered usable, so we'll include it for possible future use.
MainHandDualWield,
/// The body or chest slot, but it restricts headgear.
BodyNoHead,
/// The body or chest slot, but it restricts gloves, legs, and footwear.
BodyNoHandsLegsFeet,
/// The soul crystal slot.
SoulCrystal,
/// The legs slot, but it restricts footwear.
LegsNoFeet,
/// The body or chest slot, but it restricts hats, gloves, legs, and footwear.
BodyNoHeadHandsLegsFeet,
/// The body or chest slot, but it restricts gloves and legs.
BodyNoHandsLegs,
/// The body or chest slot, but it resticts legs and footwear.
BodyNoLegsFeet,
/// The body or chest slot, but it restricts gloves.
BodyNoHands,
/// The body or chest slot, but it restricts legs.
BodyNoLegs,
/// No applicable slot, again. Probably just a placeholder.
Invalid2 = 24,
}
impl EquipSlotCategory {
/// Returns the shorthand abbreviation of `slot`. For example, Body's shorthand is "top".
pub fn abbreviation(&self) -> Option<&'static str> {
match self {
EquipSlotCategory::Head => Some("met"),
EquipSlotCategory::Hands => Some("glv"),
EquipSlotCategory::Legs => Some("dwn"),
EquipSlotCategory::Feet => Some("sho"),
EquipSlotCategory::Body => Some("top"),
EquipSlotCategory::Earring => Some("ear"),
EquipSlotCategory::Neck => Some("nek"),
EquipSlotCategory::Wrists => Some("wrs"),
EquipSlotCategory::Rings => Some("ril"),
_ => None,
}
}
}
impl From<&str> for EquipSlotCategory {
fn from(value: &str) -> Self {
match value {
"met" => EquipSlotCategory::Head,
"glv" => EquipSlotCategory::Hands,
"dwn" => EquipSlotCategory::Legs,
"sho" => EquipSlotCategory::Feet,
"top" => EquipSlotCategory::Body,
"ear" => EquipSlotCategory::Earring,
"nek" => EquipSlotCategory::Neck,
"wrs" => EquipSlotCategory::Wrists,
"ril" => EquipSlotCategory::Rings,
_ => EquipSlotCategory::Invalid,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum CharacterCategory {
Body,
Hair,
Face,
Tail,
Ear,
}
impl CharacterCategory {
pub fn path(&self) -> &'static str {
match self {
CharacterCategory::Body => "body",
CharacterCategory::Hair => "hair",
CharacterCategory::Face => "face",
CharacterCategory::Tail => "tail",
CharacterCategory::Ear => "zear",
}
}
pub fn abbreviation(&self) -> &'static str {
match self {
CharacterCategory::Body => "top",
CharacterCategory::Hair => "hir",
CharacterCategory::Face => "fac",
CharacterCategory::Tail => "til",
CharacterCategory::Ear => "zer",
}
}
pub fn prefix(&self) -> &'static str {
match self {
CharacterCategory::Body => "b",
CharacterCategory::Hair => "h",
CharacterCategory::Face => "f",
CharacterCategory::Tail => "t",
CharacterCategory::Ear => "z",
}
}
}
pub fn deconstruct_equipment_path(path: &str) -> Option<(i32, EquipSlotCategory)> {
let model_id = &path[6..10];
let slot_name = &path[11..14];
Some((model_id.parse().ok()?, slot_name.into()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deconstruct() {
assert_eq!(
deconstruct_equipment_path("c0101e0000_top.mdl"),
Some((0, EquipSlotCategory::Body))
);
}
}