Skip to main content

uorustlibs/
tiledata.rs

1//! Methods for reading supplementary data about tiles and statics from tiledata.mul
2//!
3//! `tiledata` is split in two, much like `art`; first with those for map tiles, then statics.
4//!
5//! The file starts with 512 blocks of MapTileData, which are defined as
6//!
7//! `|unknown:u32|tiles:[MapTileData..32]|`
8//!
9//! MapTileData is defined as
10//!
11//! `|flags:u32|texture_id:u16|name:[u8..20(CString)]|`
12//!
13//! The rest of the file is Static tile data, also organised into blocks
14//!
15//! `|unknown:u32|tiles:[StaticTileData..32]|`
16//!
17//! StaticTileData is defined as
18//!
19//! `|flags:u32|weight:u8|quality:u8|unknown:u16|unknown:u8|quantity:u8|anim_id:u16|unknown:u8|hue:u8|unknown:u16|height:u8|name:[u8..20(CString)]|`
20//!
21//! Several fields are used to represent multiple attributes:
22//!
23//! * Quality also represents Layer for wearables, and Light ID for lights
24//! * Quantity represents Weapon Class for weapons, and Armor value for Armor
25//! * Height represents capacity for containers
26use bitflags::bitflags;
27use byteorder::{LittleEndian, ReadBytesExt};
28use std::fs::File;
29use std::io::{Read, Seek, SeekFrom};
30use std::path::Path;
31use std::str::from_utf8;
32
33use crate::error::MulReaderResult;
34
35bitflags! {
36    /// Bitflags associated with a tile
37    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38    pub struct Flags: u32 {
39        const BackgroundFlag = 0x00000001;
40        const WeaponFlag = 0x00000002;
41        const TransparentFlag = 0x00000004;
42        const TranslucentFlag = 0x00000008;
43        const WallFlag = 0x00000010;
44        const DamagingFlag = 0x00000020;
45        const ImpassableFlag = 0x00000040;
46        const WetFlag = 0x00000080;
47        const UnknownFlag = 0x00000100;
48        const SurfaceFlag = 0x00000200;
49        const BridgeFlag = 0x00000400;
50        const StackableFlag = 0x00000800;
51        const WindowFlag = 0x00001000;
52        const NoShootFlag = 0x00002000;
53        const PrefixAFlag = 0x00004000;
54        const PrefixAnFlag = 0x00008000;
55        const InternalFlag = 0x00010000;
56        const FoliageFlag = 0x00020000;
57        const PartialHueFlag = 0x00040000;
58        const Unknown1Flag = 0x00080000;
59        const MapFlag = 0x00100000;
60        const ContainerFlag = 0x00200000;
61        const WearableFlag = 0x00400000;
62        const LightSourceFlag = 0x00800000;
63        const AnimatedFlag = 0x01000000;
64        const NoDiagonalFlag = 0x02000000;
65        const Unknown2Flag = 0x04000000;
66        const ArmorFlag = 0x08000000;
67        const RoofFlag = 0x10000000;
68        const DoorFlag = 0x20000000;
69        const StairBackFlag = 0x40000000;
70        const StairRightFlag = 0x80000000;
71    }
72}
73
74// Tile data is odd, as we have [(unknown, (LAND_TILE_DATA) * 32) * 512]
75// All values are in bytes
76const GROUP_HEADER_SIZE: u32 = 4;
77const MAP_TILE_SIZE: u32 = 26;
78const STATIC_TILE_SIZE: u32 = 37;
79const STATIC_OFFSET: u32 = 428032;
80
81/// Information about a given Map tile
82#[derive(Debug, PartialEq, Eq, Clone)]
83pub struct MapTileData {
84    pub flags: Flags,
85    /// Which TexMap to use instead if this tile is non-flat
86    pub texture_id: u16,
87    pub name: String,
88}
89
90/// Information about a given Static tile
91#[derive(Debug, PartialEq, Eq, Clone)]
92pub struct StaticTileData {
93    pub flags: Flags,
94    pub weight: u8,
95    /// This field becomes Layer for wearables, and the Light ID for lights, otherwise quality.
96    pub quality_layer_light_id: u8,
97    /// This field becomes weapon class for weapons, armor class for armor, or defaults to quantity.
98    pub quantity_weapon_class_armor_class: u8,
99    pub anim_id: u16,
100    pub hue: u8,
101    /// This field becomes capacity for containers, otherwise height
102    pub height_capacity: u8,
103    pub name: String,
104}
105
106/// A struct to help read out MapTileData and StaticTileData data
107#[derive(Debug)]
108pub struct TileDataReader<T: Read + Seek> {
109    data_reader: T,
110}
111
112impl TileDataReader<File> {
113    /// Create a new TileDataReader from a mul path
114    pub fn new(mul_path: &Path) -> MulReaderResult<TileDataReader<File>> {
115        let data_reader = File::open(mul_path)?;
116
117        Ok(TileDataReader { data_reader })
118    }
119}
120
121impl<T: Read + Seek> TileDataReader<T> {
122    /// Create a TileDataReader from an existing file reader
123    pub fn from_readable(reader: T) -> TileDataReader<T> {
124        TileDataReader {
125            data_reader: reader,
126        }
127    }
128
129    /// Read a map tile's associated data.
130    ///
131    /// The ID matches the data in ArtReader's `read_tile`
132    pub fn read_map_tile_data(&mut self, idx: u32) -> MulReaderResult<MapTileData> {
133        let offset = self.calculate_map_tile_offset(idx);
134        self.data_reader.seek(SeekFrom::Start(offset))?;
135        let flags = Flags::from_bits(self.data_reader.read_u32::<LittleEndian>()?)
136            .unwrap_or(Flags::empty());
137        let texture_id = self.data_reader.read_u16::<LittleEndian>()?;
138
139        let mut raw_name = vec![];
140        loop {
141            match self.data_reader.read_u8()? {
142                0 => break,
143                x => raw_name.push(x),
144            }
145        }
146
147        Ok(MapTileData {
148            flags,
149            texture_id,
150            name: String::from(from_utf8(&raw_name).unwrap_or("ERROR")),
151        })
152    }
153
154    fn calculate_map_tile_offset(&self, idx: u32) -> u64 {
155        //For every 32, we have to add an unknown header
156        let group_header_jumps = ((idx / 32) + 1) * GROUP_HEADER_SIZE;
157        ((idx * MAP_TILE_SIZE) + group_header_jumps) as u64
158    }
159
160    /// Read a static tile's associated data.
161    ///
162    /// The ID is read from the static offset, and matches the data in ArtReader's `read_static`
163    pub fn read_static_tile_data(&mut self, idx: u32) -> MulReaderResult<StaticTileData> {
164        let offset = self.calculate_static_tile_offset(idx);
165        self.data_reader.seek(SeekFrom::Start(offset))?;
166
167        let flags = Flags::from_bits(self.data_reader.read_u32::<LittleEndian>()?)
168            .unwrap_or(Flags::empty());
169        let weight = self.data_reader.read_u8()?;
170        let quality = self.data_reader.read_u8()?;
171        let _unknown = self.data_reader.read_u16::<LittleEndian>()?;
172        let _unknown1 = self.data_reader.read_u8()?;
173        let quantity = self.data_reader.read_u8()?;
174        let anim_id = self.data_reader.read_u16::<LittleEndian>()?;
175        let _unknown2 = self.data_reader.read_u8()?;
176        let hue = self.data_reader.read_u8()?;
177        let _unknown3 = self.data_reader.read_u16::<LittleEndian>()?;
178        let height = self.data_reader.read_u8()?;
179
180        let mut raw_name = vec![];
181        loop {
182            match self.data_reader.read_u8()? {
183                0 => break,
184                x => raw_name.push(x),
185            }
186        }
187
188        Ok(StaticTileData {
189            flags,
190            weight,
191            quality_layer_light_id: quality,
192            quantity_weapon_class_armor_class: quantity,
193            anim_id,
194            hue,
195            height_capacity: height,
196            name: String::from(from_utf8(&raw_name).unwrap_or("ERROR")),
197        })
198    }
199
200    fn calculate_static_tile_offset(&self, idx: u32) -> u64 {
201        //For every 32, we have to add an unknown header
202        let group_header_jumps = ((idx / 32) + 1) * GROUP_HEADER_SIZE;
203        ((idx * STATIC_TILE_SIZE) + group_header_jumps + STATIC_OFFSET) as u64
204    }
205}