warpack_formats/
lib.rs

1use rlua::prelude::*;
2use serde::{Deserialize, Serialize};
3
4use anyhow::anyhow;
5use bitflags::bitflags;
6
7pub mod parser {
8    pub mod slk;
9    pub mod crlf;
10    pub mod profile;
11    pub mod w3obj;
12}
13
14pub mod error;
15pub mod metadata;
16pub mod object;
17pub mod objectstore;
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
20/// A WC3 object id, which is conceptually a simple 32-bit integer,
21/// but often represented as a 4-char ASCII string.
22///
23/// Provides conversion to/from byte arrays for this reason.
24pub struct ObjectId {
25    id: u32,
26}
27
28impl ObjectId {
29    pub fn new(id: u32) -> ObjectId {
30        ObjectId { id }
31    }
32
33    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
34        if bytes.len() > 4 {
35            None
36        } else {
37            let mut value = 0;
38            for i in bytes {
39                value <<= 8;
40                value += u32::from(*i);
41            }
42
43            Some(ObjectId { id: value })
44        }
45    }
46
47    pub fn to_u32(self) -> u32 {
48        self.id
49    }
50
51    pub fn to_string(self) -> Option<String> {
52        let bytes: Vec<u8> = (&self.id.to_be_bytes()).iter().copied().collect();
53        String::from_utf8(bytes).ok()
54    }
55}
56
57impl std::fmt::Debug for ObjectId {
58    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59        if self.id == 0 {
60            write!(f, "ObjectID(NULL)")
61        } else {
62            let bytes = self.id.to_be_bytes();
63            let pretty = std::str::from_utf8(&bytes).ok();
64
65            if let Some(pretty) = pretty {
66                write!(f, "ObjectID({})", pretty)
67            } else {
68                write!(f, "ObjectID({})", self.id)
69            }
70        }
71    }
72}
73
74impl std::fmt::Display for ObjectId {
75    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
76        if self.id == 0 {
77            write!(f, "NULL")
78        } else {
79            let bytes = self.id.to_be_bytes();
80            let pretty = String::from_utf8_lossy(&bytes);
81
82            write!(f, "{}", pretty)
83        }
84    }
85}
86
87impl From<u32> for ObjectId {
88    fn from(other: u32) -> Self {
89        Self { id: other }
90    }
91}
92
93impl<'lua> FromLua<'lua> for ObjectId {
94    fn from_lua(value: LuaValue<'lua>, _ctx: LuaContext<'lua>) -> Result<Self, LuaError> {
95        match value {
96            LuaValue::String(value) => ObjectId::from_bytes(value.as_bytes()).ok_or_else(|| {
97                LuaError::FromLuaConversionError {
98                    from:    "string",
99                    to:      "objectid",
100                    message: Some("invalid byte sequence for object id".into()),
101                }
102            }),
103            LuaValue::Integer(value) => Ok(ObjectId::new(value as u32)),
104            _ => Err(LuaError::external(anyhow!(
105                "only strings and integers can be converted to object ids"
106            ))),
107        }
108    }
109}
110
111impl<'lua> ToLua<'lua> for ObjectId {
112    fn to_lua(self, ctx: LuaContext<'lua>) -> Result<LuaValue<'lua>, LuaError> {
113        if let Some(value) = self.to_string() {
114            Ok(LuaValue::String(ctx.create_string(&value)?))
115        } else {
116            Ok(LuaValue::Integer(self.id as i64))
117        }
118    }
119}
120
121#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
122/// Represents a WC3 primitive data type.
123///
124/// WC3 field metadata specifies many more types than these,
125/// but most of them collapse to strings.
126pub enum ValueType {
127    Int,
128    Real,
129    Unreal,
130    String,
131}
132
133impl ValueType {
134    /// Collapse a WC3 data type into a primitive value type.
135    ///
136    /// Mostly supposed to be used with data types specified in SLKs.
137    pub fn new(input: &str) -> ValueType {
138        match input {
139            "real" => ValueType::Real,
140            "unreal" => ValueType::Unreal,
141            "int" | "bool" | "attackBits" | "deathType" | "defenseTypeInt" | "detectionType"
142            | "teamColor" | "morphFlags" | "silenceFlags" | "stackFlags" | "interactionFlags"
143            | "pickFlags" | "versionFlags" | "fullFlags" | "channelType" | "channelFlags"
144            | "spellDetail" | "techAvail" => ValueType::Int,
145            _ => ValueType::String,
146        }
147    }
148}
149
150bitflags! {
151    #[derive(Serialize, Deserialize)]
152    /// Represents a WC3 object type.
153    pub struct ObjectKind: u32 {
154        const ABILITY = 0b1;
155        const BUFF = 0b10;
156        const DESTRUCTABLE = 0b100;
157        const MISC = 0b1000;
158        const UNIT = 0b10000;
159        const UPGRADE = 0b100000;
160        const ITEM = 0b1000000;
161        const DOODAD = 0b10000000;
162        const LIGHTNING = 0b100000000; // fake
163    }
164}
165
166impl ObjectKind {
167    /// Converts an extension of a WC3 object data file
168    /// to its corresponding object type.
169    pub fn from_ext(ext: &str) -> ObjectKind {
170        match ext {
171            "w3u" => ObjectKind::UNIT,
172            "w3a" => ObjectKind::ABILITY,
173            "w3t" => ObjectKind::ITEM,
174            "w3b" => ObjectKind::DESTRUCTABLE,
175            "w3d" => ObjectKind::DOODAD,
176            "w3h" => ObjectKind::BUFF,
177            "w3q" => ObjectKind::UPGRADE,
178            "lightning" => ObjectKind::LIGHTNING,
179            _ => ObjectKind::empty(),
180        }
181    }
182
183    pub fn to_ext(self) -> &'static str {
184        match self {
185            ObjectKind::UNIT => "w3u",
186            ObjectKind::ABILITY => "w3a",
187            ObjectKind::ITEM => "w3t",
188            ObjectKind::DESTRUCTABLE => "w3b",
189            ObjectKind::DOODAD => "w3d",
190            ObjectKind::BUFF => "w3h",
191            ObjectKind::UPGRADE => "w3q",
192            ObjectKind::LIGHTNING => "lightning",
193            _ => "none",
194        }
195    }
196
197    /// Returns true if the object type is capable
198    /// of using data/leveled fields instead of just regular fields.
199    ///
200    /// This affects the layout of WC3 object data files.
201    pub fn is_data_type(self) -> bool {
202        match self {
203            ObjectKind::DOODAD | ObjectKind::ABILITY | ObjectKind::UPGRADE => true,
204            _ => false,
205        }
206    }
207
208    pub fn to_typestr(self) -> &'static str {
209        match self {
210            ObjectKind::UNIT => "unit",
211            ObjectKind::ABILITY => "ability",
212            ObjectKind::ITEM => "item",
213            ObjectKind::DESTRUCTABLE => "destructable",
214            ObjectKind::DOODAD => "doodad",
215            ObjectKind::BUFF => "buff",
216            ObjectKind::UPGRADE => "upgrade",
217            ObjectKind::LIGHTNING => "lightning",
218            _ => "none",
219        }
220    }
221}