1use crate::error::Result;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub enum M2Version {
6 Classic,
8
9 TBC,
11
12 WotLK,
14
15 Cataclysm,
17
18 MoP,
20
21 WoD,
23
24 Legion,
26
27 BfA,
29
30 Shadowlands,
32
33 Dragonflight,
35
36 TheWarWithin,
38}
39
40impl M2Version {
41 pub fn from_string(s: &str) -> Result<Self> {
43 let parts: Vec<&str> = s.split('.').collect();
44 if parts.is_empty() {
45 return Err(crate::error::M2Error::UnsupportedVersion(format!(
46 "Invalid version string: {s}"
47 )));
48 }
49
50 let major = parts[0].parse::<u32>().map_err(|_| {
51 crate::error::M2Error::UnsupportedVersion(format!(
52 "Invalid major version: {}",
53 parts[0]
54 ))
55 })?;
56
57 Ok(match major {
58 1 => M2Version::Classic,
59 2 => M2Version::TBC,
60 3 => M2Version::WotLK,
61 4 => M2Version::Cataclysm,
62 5 => M2Version::MoP,
63 6 => M2Version::WoD,
64 7 => M2Version::Legion,
65 8 => M2Version::BfA,
66 9 => M2Version::Shadowlands,
67 10 => M2Version::Dragonflight,
68 11 => M2Version::TheWarWithin,
69 _ => {
70 return Err(crate::error::M2Error::UnsupportedVersion(format!(
71 "Unknown WoW version: {major}"
72 )));
73 }
74 })
75 }
76
77 pub fn from_expansion_name(s: &str) -> Result<Self> {
80 match s.to_lowercase().as_str() {
82 "vanilla" | "classic" => Ok(M2Version::Classic),
83 "tbc" | "bc" | "burningcrusade" | "burning_crusade" => Ok(M2Version::TBC),
84 "wotlk" | "wrath" | "lichking" | "lich_king" | "wlk" => Ok(M2Version::WotLK),
85 "cata" | "cataclysm" => Ok(M2Version::Cataclysm),
86 "mop" | "pandaria" | "mists" | "mists_of_pandaria" => Ok(M2Version::MoP),
87 "wod" | "draenor" | "warlords" | "warlords_of_draenor" => Ok(M2Version::WoD),
88 "legion" => Ok(M2Version::Legion),
89 "bfa" | "bfazeroth" | "battle_for_azeroth" | "battleforazeroth" => Ok(M2Version::BfA),
90 "sl" | "shadowlands" => Ok(M2Version::Shadowlands),
91 "df" | "dragonflight" => Ok(M2Version::Dragonflight),
92 "tww" | "warwithin" | "the_war_within" | "thewarwithin" => Ok(M2Version::TheWarWithin),
93 _ => {
94 Self::from_string(s)
96 }
97 }
98 }
99
100 pub fn from_header_version(version: u32) -> Option<Self> {
102 match version {
103 256..=263 => Some(Self::Classic), 264 => Some(Self::WotLK),
106
107 272 | 4 => Some(Self::Cataclysm),
109
110 8 => Some(Self::MoP),
112 10 => Some(Self::WoD),
113 11 => Some(Self::Legion),
114 16 => Some(Self::BfA),
115 17 => Some(Self::Shadowlands),
116 18 => Some(Self::Dragonflight),
117 19..=u32::MAX => Some(Self::TheWarWithin),
118
119 _ => None,
120 }
121 }
122
123 pub fn to_header_version(&self) -> u32 {
125 match self {
126 Self::Classic | Self::TBC => 263, Self::WotLK => 264,
129 Self::Cataclysm => 272, Self::MoP => 8,
131 Self::WoD => 10,
132 Self::Legion => 11,
133 Self::BfA => 16,
134 Self::Shadowlands => 17,
135 Self::Dragonflight => 18,
136 Self::TheWarWithin => 19,
137 }
138 }
139
140 pub fn expansion_name(&self) -> &'static str {
142 match self {
143 Self::Classic => "Classic",
144 Self::TBC => "The Burning Crusade",
145 Self::WotLK => "Wrath of the Lich King",
146 Self::Cataclysm => "Cataclysm",
147 Self::MoP => "Mists of Pandaria",
148 Self::WoD => "Warlords of Draenor",
149 Self::Legion => "Legion",
150 Self::BfA => "Battle for Azeroth",
151 Self::Shadowlands => "Shadowlands",
152 Self::Dragonflight => "Dragonflight",
153 Self::TheWarWithin => "The War Within",
154 }
155 }
156
157 pub fn to_version_string(&self) -> &'static str {
159 match self {
160 Self::Classic => "1.12.1",
161 Self::TBC => "2.4.3",
162 Self::WotLK => "3.3.5a",
163 Self::Cataclysm => "4.3.4",
164 Self::MoP => "5.4.8",
165 Self::WoD => "6.2.4",
166 Self::Legion => "7.3.5",
167 Self::BfA => "8.3.7",
168 Self::Shadowlands => "9.2.7",
169 Self::Dragonflight => "10.2.0",
170 Self::TheWarWithin => "11.0.0",
171 }
172 }
173
174 pub fn has_direct_conversion_path(&self, target: &Self) -> bool {
176 let self_ord = *self as usize;
178 let target_ord = *target as usize;
179
180 (self_ord as isize - target_ord as isize).abs() == 1
181 }
182}
183
184impl std::fmt::Display for M2Version {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 write!(
187 f,
188 "{} ({})",
189 self.expansion_name(),
190 self.to_version_string()
191 )
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_version_from_string() {
201 assert_eq!(
202 M2Version::from_string("1.12.1").unwrap(),
203 M2Version::Classic
204 );
205 assert_eq!(M2Version::from_string("2.4.3").unwrap(), M2Version::TBC);
206 assert_eq!(M2Version::from_string("3.3.5a").unwrap(), M2Version::WotLK);
207 assert_eq!(
208 M2Version::from_string("4.3.4").unwrap(),
209 M2Version::Cataclysm
210 );
211 assert_eq!(M2Version::from_string("5.4.8").unwrap(), M2Version::MoP);
212 }
213
214 #[test]
215 fn test_version_from_expansion_name() {
216 assert_eq!(
217 M2Version::from_expansion_name("classic").unwrap(),
218 M2Version::Classic
219 );
220 assert_eq!(
221 M2Version::from_expansion_name("TBC").unwrap(),
222 M2Version::TBC
223 );
224 assert_eq!(
225 M2Version::from_expansion_name("wotlk").unwrap(),
226 M2Version::WotLK
227 );
228 assert_eq!(
229 M2Version::from_expansion_name("cata").unwrap(),
230 M2Version::Cataclysm
231 );
232 assert_eq!(
233 M2Version::from_expansion_name("MoP").unwrap(),
234 M2Version::MoP
235 );
236
237 assert_eq!(
239 M2Version::from_expansion_name("3.3.5a").unwrap(),
240 M2Version::WotLK
241 );
242 }
243
244 #[test]
245 fn test_header_version_conversion() {
246 assert_eq!(
248 M2Version::from_header_version(256),
249 Some(M2Version::Classic)
250 );
251 assert_eq!(
252 M2Version::from_header_version(263),
253 Some(M2Version::Classic)
254 );
255 assert_eq!(M2Version::from_header_version(264), Some(M2Version::WotLK));
256
257 assert_eq!(
259 M2Version::from_header_version(272),
260 Some(M2Version::Cataclysm)
261 );
262 assert_eq!(
263 M2Version::from_header_version(4),
264 Some(M2Version::Cataclysm)
265 );
266
267 assert_eq!(M2Version::from_header_version(8), Some(M2Version::MoP));
269 assert_eq!(M2Version::from_header_version(10), Some(M2Version::WoD));
270 assert_eq!(M2Version::from_header_version(11), Some(M2Version::Legion));
271 assert_eq!(M2Version::from_header_version(16), Some(M2Version::BfA));
272 assert_eq!(
273 M2Version::from_header_version(17),
274 Some(M2Version::Shadowlands)
275 );
276 assert_eq!(
277 M2Version::from_header_version(18),
278 Some(M2Version::Dragonflight)
279 );
280 assert_eq!(
281 M2Version::from_header_version(19),
282 Some(M2Version::TheWarWithin)
283 );
284
285 assert_eq!(M2Version::from_header_version(1), None);
287 assert_eq!(M2Version::from_header_version(5), None);
288 }
289
290 #[test]
291 fn test_conversion_paths() {
292 assert!(M2Version::Classic.has_direct_conversion_path(&M2Version::TBC));
293 assert!(M2Version::TBC.has_direct_conversion_path(&M2Version::WotLK));
294 assert!(M2Version::WotLK.has_direct_conversion_path(&M2Version::Cataclysm));
295 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::MoP));
296 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::TheWarWithin));
297 }
298}