wow_wdt/
version.rs

1//! WoW version handling and version-specific behaviors
2
3use crate::error::{Error, Result};
4use std::fmt;
5
6/// WoW expansion versions that affect WDT format
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
8pub enum WowVersion {
9    /// Classic/Vanilla (1.x)
10    Classic,
11    /// The Burning Crusade (2.x)
12    TBC,
13    /// Wrath of the Lich King (3.x)
14    WotLK,
15    /// Cataclysm (4.x) - First major format change
16    Cataclysm,
17    /// Mists of Pandaria (5.x)
18    MoP,
19    /// Warlords of Draenor (6.x)
20    WoD,
21    /// Legion (7.x) - Added auxiliary WDT files
22    Legion,
23    /// Battle for Azeroth (8.x) - Added MAID chunk
24    BfA,
25    /// Shadowlands (9.x)
26    Shadowlands,
27    /// Dragonflight (10.x)
28    Dragonflight,
29}
30
31impl WowVersion {
32    /// Parse version from a string (e.g., "1.12.1", "3.3.5a", "4.3.4")
33    pub fn from_string(s: &str) -> Result<Self> {
34        let parts: Vec<&str> = s.split('.').collect();
35        if parts.is_empty() {
36            return Err(Error::ValidationError(format!(
37                "Invalid version string: {s}"
38            )));
39        }
40
41        let major = parts[0]
42            .parse::<u32>()
43            .map_err(|_| Error::ValidationError(format!("Invalid major version: {}", parts[0])))?;
44
45        Ok(match major {
46            1 => WowVersion::Classic,
47            2 => WowVersion::TBC,
48            3 => WowVersion::WotLK,
49            4 => WowVersion::Cataclysm,
50            5 => WowVersion::MoP,
51            6 => WowVersion::WoD,
52            7 => WowVersion::Legion,
53            8 => WowVersion::BfA,
54            9 => WowVersion::Shadowlands,
55            10 => WowVersion::Dragonflight,
56            _ => {
57                return Err(Error::ValidationError(format!(
58                    "Unknown WoW version: {major}"
59                )));
60            }
61        })
62    }
63
64    /// Parse version from expansion short names or numeric versions
65    /// Supports both numeric versions (e.g., "3.3.5a") and short names (e.g., "WotLK", "TBC")
66    pub fn from_expansion_name(s: &str) -> Result<Self> {
67        // First try parsing as a short name
68        match s.to_lowercase().as_str() {
69            "vanilla" | "classic" => Ok(WowVersion::Classic),
70            "tbc" | "bc" | "burningcrusade" | "burning_crusade" => Ok(WowVersion::TBC),
71            "wotlk" | "wrath" | "lichking" | "lich_king" | "wlk" => Ok(WowVersion::WotLK),
72            "cata" | "cataclysm" => Ok(WowVersion::Cataclysm),
73            "mop" | "pandaria" | "mists" | "mists_of_pandaria" => Ok(WowVersion::MoP),
74            "wod" | "draenor" | "warlords" | "warlords_of_draenor" => Ok(WowVersion::WoD),
75            "legion" => Ok(WowVersion::Legion),
76            "bfa" | "bfazeroth" | "battle_for_azeroth" | "battleforazeroth" => Ok(WowVersion::BfA),
77            "sl" | "shadowlands" => Ok(WowVersion::Shadowlands),
78            "df" | "dragonflight" => Ok(WowVersion::Dragonflight),
79            _ => {
80                // If it's not a short name, try parsing as a numeric version
81                Self::from_string(s)
82            }
83        }
84    }
85
86    /// Check if this version has terrain maps with MWMO chunks
87    pub fn has_terrain_mwmo(&self) -> bool {
88        // Pre-Cataclysm versions have empty MWMO chunks in terrain maps
89        *self < WowVersion::Cataclysm
90    }
91
92    /// Check if this version supports MAID chunk
93    pub fn has_maid_chunk(&self) -> bool {
94        // MAID was introduced in BfA 8.1.0
95        *self >= WowVersion::BfA
96    }
97
98    /// Check if this version has auxiliary WDT files
99    pub fn has_auxiliary_files(&self) -> bool {
100        // Auxiliary files (_lgt.wdt, etc.) were added in Legion
101        *self >= WowVersion::Legion
102    }
103
104    /// Get the expected MODF scale value for this version
105    pub fn expected_modf_scale(&self) -> u16 {
106        match self {
107            // Vanilla through WotLK use 0
108            WowVersion::Classic | WowVersion::TBC | WowVersion::WotLK => 0,
109            // Later versions typically use 1024 (1.0)
110            _ => 1024,
111        }
112    }
113
114    /// Get the expected MODF unique ID for this version
115    pub fn expected_modf_unique_id(&self) -> u32 {
116        match self {
117            // Early versions use 0xFFFFFFFF (-1)
118            WowVersion::Classic | WowVersion::TBC | WowVersion::WotLK => 0xFFFFFFFF,
119            // Later versions may use 0 or other values
120            _ => 0,
121        }
122    }
123
124    /// Check if a specific MPHD flag is commonly used in this version
125    pub fn is_flag_common(&self, flag: u32) -> bool {
126        match flag {
127            0x0001 => true,                       // WMO-only flag used in all versions
128            0x0002 => *self >= WowVersion::WotLK, // MCCV widely used from WotLK
129            0x0004 => *self >= WowVersion::WotLK, // Big alpha widely used from WotLK
130            0x0008 => *self >= WowVersion::WotLK, // Sorted doodads from WotLK
131            0x0010 => *self >= WowVersion::WotLK && *self < WowVersion::BfA, // Deprecated in BfA
132            0x0040 => *self >= WowVersion::Cataclysm, // Universal from Cataclysm
133            0x0080 => *self >= WowVersion::MoP,   // Height texturing active from MoP
134            0x0200 => *self >= WowVersion::BfA,   // MAID flag from BfA
135            _ => false,
136        }
137    }
138
139    /// Get a descriptive name for this version
140    pub fn name(&self) -> &'static str {
141        match self {
142            WowVersion::Classic => "Classic",
143            WowVersion::TBC => "The Burning Crusade",
144            WowVersion::WotLK => "Wrath of the Lich King",
145            WowVersion::Cataclysm => "Cataclysm",
146            WowVersion::MoP => "Mists of Pandaria",
147            WowVersion::WoD => "Warlords of Draenor",
148            WowVersion::Legion => "Legion",
149            WowVersion::BfA => "Battle for Azeroth",
150            WowVersion::Shadowlands => "Shadowlands",
151            WowVersion::Dragonflight => "Dragonflight",
152        }
153    }
154}
155
156impl fmt::Display for WowVersion {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        write!(f, "{}", self.name())
159    }
160}
161
162/// Version-specific configuration for WDT handling
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct VersionConfig {
165    pub version: WowVersion,
166}
167
168impl VersionConfig {
169    /// Create a new version configuration
170    pub fn new(version: WowVersion) -> Self {
171        Self { version }
172    }
173
174    /// Validate MPHD flags for this version
175    pub fn validate_mphd_flags(&self, flags: u32) -> Vec<String> {
176        let mut warnings = Vec::new();
177
178        // Check for flags that shouldn't be present in this version
179        if (flags & 0x0200) != 0 && !self.version.has_maid_chunk() {
180            warnings.push(format!(
181                "Flag 0x0200 (MAID) present but not supported in {}",
182                self.version
183            ));
184        }
185
186        if (flags & 0x0040) != 0 && self.version < WowVersion::Cataclysm {
187            warnings.push("Flag 0x0040 present but not expected before Cataclysm".to_string());
188        }
189
190        if (flags & 0x0080) != 0 && self.version < WowVersion::MoP {
191            warnings.push(
192                "Flag 0x0080 (height texturing) present but not active before MoP".to_string(),
193            );
194        }
195
196        warnings
197    }
198
199    /// Check if a chunk should be present based on version and flags
200    pub fn should_have_chunk(&self, chunk: &str, is_wmo_only: bool) -> bool {
201        match chunk {
202            "MVER" | "MPHD" | "MAIN" => true, // Always required
203            "MWMO" => {
204                // WMO-only maps always have MWMO
205                // Terrain maps have MWMO only pre-Cataclysm
206                is_wmo_only || self.version.has_terrain_mwmo()
207            }
208            "MODF" => is_wmo_only, // Only WMO-only maps have MODF
209            "MAID" => self.version.has_maid_chunk(), // BfA+ only
210            _ => false,
211        }
212    }
213}