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..=264 => Some(Self::Classic), 272 | 4 => Some(Self::Cataclysm),
108
109 8 => Some(Self::MoP),
111 10 => Some(Self::WoD),
112 11 => Some(Self::Legion),
113 16 => Some(Self::BfA),
114 17 => Some(Self::Shadowlands),
115 18 => Some(Self::Dragonflight),
116 19..=u32::MAX => Some(Self::TheWarWithin),
117
118 _ => None,
119 }
120 }
121
122 pub fn to_header_version(&self) -> u32 {
124 match self {
125 Self::Classic | Self::TBC | Self::WotLK => 264, Self::Cataclysm => 272, Self::MoP => 8,
129 Self::WoD => 10,
130 Self::Legion => 11,
131 Self::BfA => 16,
132 Self::Shadowlands => 17,
133 Self::Dragonflight => 18,
134 Self::TheWarWithin => 19,
135 }
136 }
137
138 pub fn expansion_name(&self) -> &'static str {
140 match self {
141 Self::Classic => "Classic",
142 Self::TBC => "The Burning Crusade",
143 Self::WotLK => "Wrath of the Lich King",
144 Self::Cataclysm => "Cataclysm",
145 Self::MoP => "Mists of Pandaria",
146 Self::WoD => "Warlords of Draenor",
147 Self::Legion => "Legion",
148 Self::BfA => "Battle for Azeroth",
149 Self::Shadowlands => "Shadowlands",
150 Self::Dragonflight => "Dragonflight",
151 Self::TheWarWithin => "The War Within",
152 }
153 }
154
155 pub fn to_version_string(&self) -> &'static str {
157 match self {
158 Self::Classic => "1.12.1",
159 Self::TBC => "2.4.3",
160 Self::WotLK => "3.3.5a",
161 Self::Cataclysm => "4.3.4",
162 Self::MoP => "5.4.8",
163 Self::WoD => "6.2.4",
164 Self::Legion => "7.3.5",
165 Self::BfA => "8.3.7",
166 Self::Shadowlands => "9.2.7",
167 Self::Dragonflight => "10.2.0",
168 Self::TheWarWithin => "11.0.0",
169 }
170 }
171
172 pub fn has_direct_conversion_path(&self, target: &Self) -> bool {
174 let self_ord = *self as usize;
176 let target_ord = *target as usize;
177
178 (self_ord as isize - target_ord as isize).abs() == 1
179 }
180}
181
182impl std::fmt::Display for M2Version {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(
185 f,
186 "{} ({})",
187 self.expansion_name(),
188 self.to_version_string()
189 )
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_version_from_string() {
199 assert_eq!(
200 M2Version::from_string("1.12.1").unwrap(),
201 M2Version::Classic
202 );
203 assert_eq!(M2Version::from_string("2.4.3").unwrap(), M2Version::TBC);
204 assert_eq!(M2Version::from_string("3.3.5a").unwrap(), M2Version::WotLK);
205 assert_eq!(
206 M2Version::from_string("4.3.4").unwrap(),
207 M2Version::Cataclysm
208 );
209 assert_eq!(M2Version::from_string("5.4.8").unwrap(), M2Version::MoP);
210 }
211
212 #[test]
213 fn test_version_from_expansion_name() {
214 assert_eq!(
215 M2Version::from_expansion_name("classic").unwrap(),
216 M2Version::Classic
217 );
218 assert_eq!(
219 M2Version::from_expansion_name("TBC").unwrap(),
220 M2Version::TBC
221 );
222 assert_eq!(
223 M2Version::from_expansion_name("wotlk").unwrap(),
224 M2Version::WotLK
225 );
226 assert_eq!(
227 M2Version::from_expansion_name("cata").unwrap(),
228 M2Version::Cataclysm
229 );
230 assert_eq!(
231 M2Version::from_expansion_name("MoP").unwrap(),
232 M2Version::MoP
233 );
234
235 assert_eq!(
237 M2Version::from_expansion_name("3.3.5a").unwrap(),
238 M2Version::WotLK
239 );
240 }
241
242 #[test]
243 fn test_header_version_conversion() {
244 assert_eq!(
246 M2Version::from_header_version(256),
247 Some(M2Version::Classic)
248 );
249 assert_eq!(
250 M2Version::from_header_version(264),
251 Some(M2Version::Classic)
252 );
253
254 assert_eq!(
256 M2Version::from_header_version(272),
257 Some(M2Version::Cataclysm)
258 );
259 assert_eq!(
260 M2Version::from_header_version(4),
261 Some(M2Version::Cataclysm)
262 );
263
264 assert_eq!(M2Version::from_header_version(8), Some(M2Version::MoP));
266 assert_eq!(M2Version::from_header_version(10), Some(M2Version::WoD));
267 assert_eq!(M2Version::from_header_version(11), Some(M2Version::Legion));
268 assert_eq!(M2Version::from_header_version(16), Some(M2Version::BfA));
269 assert_eq!(
270 M2Version::from_header_version(17),
271 Some(M2Version::Shadowlands)
272 );
273 assert_eq!(
274 M2Version::from_header_version(18),
275 Some(M2Version::Dragonflight)
276 );
277 assert_eq!(
278 M2Version::from_header_version(19),
279 Some(M2Version::TheWarWithin)
280 );
281
282 assert_eq!(M2Version::from_header_version(1), None);
284 assert_eq!(M2Version::from_header_version(5), None);
285 }
286
287 #[test]
288 fn test_conversion_paths() {
289 assert!(M2Version::Classic.has_direct_conversion_path(&M2Version::TBC));
290 assert!(M2Version::TBC.has_direct_conversion_path(&M2Version::WotLK));
291 assert!(M2Version::WotLK.has_direct_conversion_path(&M2Version::Cataclysm));
292 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::MoP));
293 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::TheWarWithin));
294 }
295}