genie_dat/
sprite.rs

1use crate::sound::SoundID;
2use arrayvec::ArrayVec;
3use byteorder::{ReadBytesExt, WriteBytesExt, LE};
4pub use genie_support::SpriteID;
5use genie_support::{fallible_try_into, infallible_try_into, read_opt_u16, MapInto};
6use std::convert::{TryFrom, TryInto};
7use std::io::{Read, Result, Write};
8use std::num::TryFromIntError;
9
10/// An ID identifying a string resource.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
12pub struct GraphicID(u32);
13
14impl From<u16> for GraphicID {
15    fn from(n: u16) -> Self {
16        GraphicID(n.into())
17    }
18}
19
20impl From<u32> for GraphicID {
21    fn from(n: u32) -> Self {
22        GraphicID(n)
23    }
24}
25
26impl TryFrom<i32> for GraphicID {
27    type Error = TryFromIntError;
28    fn try_from(n: i32) -> std::result::Result<Self, Self::Error> {
29        Ok(GraphicID(n.try_into()?))
30    }
31}
32
33fallible_try_into!(GraphicID, i16);
34infallible_try_into!(GraphicID, u32);
35fallible_try_into!(GraphicID, i32);
36
37#[derive(Debug, Default, Clone)]
38pub struct SpriteDelta {
39    pub sprite_id: Option<SpriteID>,
40    pub offset_x: i16,
41    pub offset_y: i16,
42    pub display_angle: i16,
43}
44
45#[derive(Debug, Default, Clone, Copy)]
46pub struct SoundProp {
47    pub sound_delay: i16,
48    pub sound_id: SoundID,
49    wwise_sound_id: Option<u32>,
50}
51
52#[derive(Debug, Default, Clone)]
53pub struct SpriteAttackSound {
54    pub sound_props: ArrayVec<[SoundProp; 3]>,
55}
56
57#[derive(Debug, Default, Clone)]
58pub struct Sprite {
59    pub id: SpriteID,
60    pub name: String,
61    pub filename: String,
62    /// The SLP resource ID for this sprite.
63    pub slp_id: Option<GraphicID>,
64    pub is_loaded: bool,
65    /// If `Some(id)`, the sprite will always be rendered with this player colour.
66    force_player_color: Option<u8>,
67    /// The layer describes order of graphics being rendered.
68    /// Possible values: 0 (lowest layer) to 40 (highest layer)
69    /// Graphics on a higher layer will be rendered above graphics of a lower
70    /// layer. If graphics share the same layer, graphics will be displayed
71    /// dependend on their map positions.
72    ///
73    /// Draw Level
74    /// ```txt
75    /// 0   Terrain
76    /// 5   Shadows, farms
77    /// 6   Rubble
78    /// 10   Constructions, corpses, shadows, flowers, ruins
79    /// 11   Fish
80    /// 19   Rugs, craters
81    /// 20   Buildings, units, damage flames, mill animation
82    /// 21   Blacksmith smoke
83    /// 22   Hawk
84    /// 30   Projectiles, explosions
85    /// ```
86    pub layer: u8,
87    pub color_table: u16,
88    pub transparent_selection: bool,
89    pub bounding_box: (i16, i16, i16, i16),
90    pub sound_id: Option<SoundID>,
91    /// Number of frames per angle animation
92    pub num_frames: u16,
93    /// Number of angles tored in slp and also the number of extra structures.
94    /// If there are more than 1 angle, AngleCount/2 - 1 frames will be
95    /// mirrored. That means angles starting from south going clockwise to
96    /// north are stored and the others will be mirrored.
97    pub num_angles: u16,
98    /// If this is over 0, the speed of the unit will be replaced with this.
99    pub base_speed: f32,
100    /// Frame rate in seconds. (Delay between frames)
101    pub frame_rate: f32,
102    /// Time to wait until the animation sequence is started again.
103    pub replay_delay: f32,
104    pub sequence_type: u8,
105    pub mirror_flag: i8,
106    /// editor flag?
107    other_flag: i8,
108    pub deltas: Vec<SpriteDelta>,
109    pub attack_sounds: Vec<SpriteAttackSound>,
110}
111
112impl SpriteDelta {
113    pub fn read_from(mut input: impl Read) -> Result<Self> {
114        let mut delta = SpriteDelta::default();
115        delta.sprite_id = read_opt_u16(&mut input)?;
116        let _padding = input.read_i16::<LE>()?;
117        let _parent_sprite_pointer = input.read_i32::<LE>()?;
118        delta.offset_x = input.read_i16::<LE>()?;
119        delta.offset_y = input.read_i16::<LE>()?;
120        delta.display_angle = input.read_i16::<LE>()?;
121        let _padding = input.read_i16::<LE>()?;
122
123        Ok(delta)
124    }
125
126    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
127        output.write_u16::<LE>(self.sprite_id.map_into().unwrap_or(0xFFFF))?;
128        // padding
129        output.write_i16::<LE>(0)?;
130        // pointer address to the parent sprite (overridden at load time by the game)
131        output.write_i32::<LE>(0)?;
132        output.write_i16::<LE>(self.offset_x)?;
133        output.write_i16::<LE>(self.offset_y)?;
134        output.write_i16::<LE>(self.display_angle)?;
135        // padding
136        output.write_i16::<LE>(0)?;
137
138        Ok(())
139    }
140}
141
142impl SoundProp {
143    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
144        let sound_delay = input.read_i16::<LE>()?;
145        let sound_id = input.read_u16::<LE>()?.into();
146        Ok(Self {
147            sound_delay,
148            sound_id,
149            wwise_sound_id: None,
150        })
151    }
152
153    pub fn write_to<W: Write>(self, output: &mut W) -> Result<()> {
154        output.write_i16::<LE>(self.sound_delay)?;
155        output.write_u16::<LE>(self.sound_id.into())?;
156        Ok(())
157    }
158
159    pub fn write_empty<W: Write>(output: &mut W) -> Result<()> {
160        output.write_u16::<LE>(0)?;
161        output.write_u16::<LE>(0xFFFF)?;
162        Ok(())
163    }
164}
165
166impl SpriteAttackSound {
167    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
168        let mut val = SpriteAttackSound::default();
169        for _ in 0..val.sound_props.capacity() {
170            let prop = SoundProp::read_from(input)?;
171            if u16::from(prop.sound_id) != 0xFFFF {
172                val.sound_props.push(prop);
173            }
174        }
175        Ok(val)
176    }
177
178    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
179        for index in 0..self.sound_props.capacity() {
180            match self.sound_props.get(index) {
181                Some(prop) => prop.write_to(output)?,
182                None => SoundProp::write_empty(output)?,
183            }
184        }
185        Ok(())
186    }
187}
188
189impl Sprite {
190    pub fn read_from(mut input: impl Read) -> Result<Self> {
191        let mut sprite = Sprite::default();
192        let mut name = [0u8; 21];
193        input.read_exact(&mut name)?;
194        sprite.name =
195            String::from_utf8(name.iter().cloned().take_while(|b| *b != 0).collect()).unwrap();
196        let mut filename = [0u8; 13];
197        input.read_exact(&mut filename)?;
198        sprite.filename =
199            String::from_utf8(filename.iter().cloned().take_while(|b| *b != 0).collect()).unwrap();
200        sprite.slp_id = {
201            let num = input.read_i32::<LE>()?;
202            if num == -1 {
203                None
204            } else {
205                Some(num.try_into().unwrap())
206            }
207        };
208        sprite.is_loaded = input.read_u8()? != 0;
209        sprite.force_player_color = match input.read_u8()? {
210            0xFF => None,
211            id => Some(id),
212        };
213        sprite.layer = input.read_u8()?;
214        sprite.color_table = input.read_u16::<LE>()?;
215        sprite.transparent_selection = input.read_u8()? != 0;
216        sprite.bounding_box = (
217            input.read_i16::<LE>()?,
218            input.read_i16::<LE>()?,
219            input.read_i16::<LE>()?,
220            input.read_i16::<LE>()?,
221        );
222        let num_deltas = input.read_u16::<LE>()?;
223        sprite.sound_id = read_opt_u16(&mut input)?;
224        let attack_sounds_used = input.read_u8()? != 0;
225        sprite.num_frames = input.read_u16::<LE>()?;
226        sprite.num_angles = input.read_u16::<LE>()?;
227        sprite.base_speed = input.read_f32::<LE>()?;
228        sprite.frame_rate = input.read_f32::<LE>()?;
229        sprite.replay_delay = input.read_f32::<LE>()?;
230        sprite.sequence_type = input.read_u8()?;
231        sprite.id = input.read_u16::<LE>()?.into();
232        sprite.mirror_flag = input.read_i8()?;
233        sprite.other_flag = input.read_i8()?;
234
235        for _ in 0..num_deltas {
236            sprite.deltas.push(SpriteDelta::read_from(&mut input)?);
237        }
238        if attack_sounds_used {
239            for _ in 0..sprite.num_angles {
240                sprite
241                    .attack_sounds
242                    .push(SpriteAttackSound::read_from(&mut input)?);
243            }
244        }
245
246        Ok(sprite)
247    }
248
249    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
250        if !self.attack_sounds.is_empty() {
251            assert_eq!(self.attack_sounds.len(), usize::from(self.num_angles));
252        }
253        let mut name = [0u8; 21];
254        (&mut name[..]).write_all(self.name.as_bytes())?;
255        let mut filename = [0u8; 13];
256        (&mut filename[..]).write_all(self.filename.as_bytes())?;
257        output.write_all(&name)?;
258        output.write_all(&filename)?;
259        output.write_i32::<LE>(self.slp_id.map(|v| v.try_into().unwrap()).unwrap_or(-1))?;
260        output.write_u8(if self.is_loaded { 1 } else { 0 })?;
261        output.write_u8(self.force_player_color.unwrap_or(0xFF))?;
262        output.write_u8(self.layer)?;
263        output.write_u16::<LE>(self.color_table)?;
264        output.write_u8(if self.transparent_selection { 1 } else { 0 })?;
265        output.write_i16::<LE>(self.bounding_box.0)?;
266        output.write_i16::<LE>(self.bounding_box.1)?;
267        output.write_i16::<LE>(self.bounding_box.2)?;
268        output.write_i16::<LE>(self.bounding_box.3)?;
269
270        output.write_u16::<LE>(self.deltas.len().try_into().unwrap())?;
271        output.write_i16::<LE>(self.sound_id.map(|v| v.try_into().unwrap()).unwrap_or(-1))?;
272        output.write_u8(if self.attack_sounds.is_empty() { 0 } else { 1 })?;
273        output.write_u16::<LE>(self.num_frames)?;
274        output.write_u16::<LE>(self.num_angles)?;
275        output.write_f32::<LE>(self.base_speed)?;
276        output.write_f32::<LE>(self.frame_rate)?;
277        output.write_f32::<LE>(self.replay_delay)?;
278        output.write_u8(self.sequence_type)?;
279        output.write_u16::<LE>(self.id.into())?;
280        output.write_i8(self.mirror_flag)?;
281        output.write_i8(self.other_flag)?;
282
283        for delta in &self.deltas {
284            delta.write_to(output)?;
285        }
286        for sound in &self.attack_sounds {
287            sound.write_to(output)?;
288        }
289        Ok(())
290    }
291}