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#[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 pub slp_id: Option<GraphicID>,
64 pub is_loaded: bool,
65 force_player_color: Option<u8>,
67 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 pub num_frames: u16,
93 pub num_angles: u16,
98 pub base_speed: f32,
100 pub frame_rate: f32,
102 pub replay_delay: f32,
104 pub sequence_type: u8,
105 pub mirror_flag: i8,
106 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 output.write_i16::<LE>(0)?;
130 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 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}