genie_dat/
terrain.rs

1use crate::sound::SoundID;
2use crate::sprite::{GraphicID, SpriteID};
3use crate::unit_type::UnitTypeID;
4use crate::FileVersion;
5use arrayvec::ArrayString;
6use byteorder::{ReadBytesExt, WriteBytesExt, LE};
7use genie_support::{
8    fallible_try_from, fallible_try_into, infallible_try_into, read_opt_u16, read_opt_u32, MapInto,
9};
10use std::convert::TryInto;
11use std::io::{Read, Result, Write};
12
13/// An ID identifying a terrain.
14#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
15pub struct TerrainID(u16);
16
17impl From<u8> for TerrainID {
18    fn from(n: u8) -> Self {
19        TerrainID(n.into())
20    }
21}
22
23impl From<u16> for TerrainID {
24    fn from(n: u16) -> Self {
25        TerrainID(n)
26    }
27}
28
29impl From<TerrainID> for u16 {
30    fn from(n: TerrainID) -> Self {
31        n.0
32    }
33}
34
35impl From<TerrainID> for usize {
36    fn from(n: TerrainID) -> Self {
37        n.0.into()
38    }
39}
40
41fallible_try_into!(TerrainID, i16);
42infallible_try_into!(TerrainID, u32);
43fallible_try_from!(TerrainID, i32);
44fallible_try_from!(TerrainID, u32);
45
46type TerrainName = ArrayString<[u8; 13]>;
47
48#[derive(Debug, Default, Clone)]
49pub struct TerrainPassGraphic {
50    exit_tile_sprite: Option<SpriteID>,
51    enter_tile_sprite: Option<SpriteID>,
52    walk_tile_sprite: Option<SpriteID>,
53    walk_rate: Option<f32>,
54    replication_amount: Option<i32>,
55}
56
57#[derive(Debug, Clone)]
58pub struct TerrainRestriction {
59    passability: Vec<f32>,
60    pass_graphics: Vec<TerrainPassGraphic>,
61}
62
63#[derive(Debug, Default, Clone)]
64pub struct TileSize {
65    pub width: i16,
66    pub height: i16,
67    pub delta_z: i16,
68}
69
70#[derive(Debug, Default, Clone)]
71pub struct TerrainAnimation {
72    pub enabled: bool,
73    num_frames: i16,
74    num_pause_frames: i16,
75    frame_interval: f32,
76    replay_delay: f32,
77    frame: i16,
78    draw_frame: i16,
79    animate_last: f32,
80    frame_changed: bool,
81    drawn: bool,
82}
83
84#[derive(Debug, Default, Clone)]
85pub struct TerrainSpriteFrame {
86    pub num_frames: i16,
87    pub num_facets: i16,
88    pub frame_id: i16,
89}
90
91#[derive(Debug, Default, Clone)]
92pub struct TerrainObject {
93    pub object_id: UnitTypeID,
94    pub density: i16,
95    pub placement_flag: i8,
96}
97
98#[derive(Debug, Default, Clone)]
99pub struct Terrain {
100    /// Is this terrain enabled?
101    pub enabled: bool,
102    random: u8,
103    /// Internal name of the terrain.
104    name: TerrainName,
105    /// Internal name of the SLP graphic.
106    slp_name: TerrainName,
107    /// SLP graphic ID for this terrain.
108    pub slp_id: Option<GraphicID>,
109    /// The Sound ID for this terrain.
110    pub sound_id: Option<SoundID>,
111    wwise_sound_id: Option<u32>,
112    wwise_stop_sound_id: Option<u32>,
113    blend_priority: Option<i32>,
114    blend_mode: Option<i32>,
115    /// The colour tiles with this terrain will have on the minimap when on a downhill slope.
116    pub minimap_color_high: u8,
117    /// The colour tiles with this terrain will have on the minimap when on a flat tile.
118    pub minimap_color_medium: u8,
119    /// The colour tiles with this terrain will have on the minimap when on an uphill slope.
120    pub minimap_color_low: u8,
121    /// The colour tiles with this terrain will have on the minimap when next to a cliff.
122    pub minimap_color_cliff_lt: u8,
123    /// The colour tiles with this terrain will have on the minimap when next to a cliff.
124    pub minimap_color_cliff_rt: u8,
125    pub passable_terrain_id: Option<u8>,
126    pub impassable_terrain_id: Option<u8>,
127    pub animation: TerrainAnimation,
128    pub elevation_sprites: Vec<TerrainSpriteFrame>,
129    pub terrain_id_to_draw: Option<TerrainID>,
130    rows: i16,
131    cols: i16,
132    pub borders: Vec<i16>,
133    pub terrain_objects: Vec<TerrainObject>,
134}
135
136#[derive(Debug, Default, Clone)]
137pub struct TerrainBorder {
138    pub enabled: bool,
139    random: u8,
140    name: TerrainName,
141    slp_name: TerrainName,
142    pub slp_id: Option<GraphicID>,
143    pub sound_id: Option<SoundID>,
144    pub color: (u8, u8, u8),
145    pub animation: TerrainAnimation,
146    pub frames: Vec<Vec<TerrainSpriteFrame>>,
147    /// Unused according to Chariot.
148    draw_tile: i8,
149    pub underlay_terrain: Option<i16>,
150    pub border_style: i16,
151}
152
153impl TerrainPassGraphic {
154    pub fn read_from(mut input: impl Read, version: FileVersion) -> Result<Self> {
155        let mut pass = TerrainPassGraphic::default();
156        pass.exit_tile_sprite = read_opt_u32(&mut input)?;
157        pass.enter_tile_sprite = read_opt_u32(&mut input)?;
158        pass.walk_tile_sprite = read_opt_u32(&mut input)?;
159        if version.is_swgb() {
160            pass.walk_rate = Some(input.read_f32::<LE>()?);
161        } else {
162            pass.replication_amount = Some(input.read_i32::<LE>()?);
163        }
164        Ok(pass)
165    }
166
167    /// Serialize this object to a binary output stream.
168    pub fn write_to(&self, mut output: impl Write, version: FileVersion) -> Result<()> {
169        output.write_i32::<LE>(self.exit_tile_sprite.map_into().unwrap_or(-1))?;
170        output.write_i32::<LE>(self.enter_tile_sprite.map_into().unwrap_or(-1))?;
171        output.write_i32::<LE>(self.walk_tile_sprite.map_into().unwrap_or(-1))?;
172        // TODO decide on correct default values for these
173        if version.is_swgb() {
174            output.write_f32::<LE>(self.walk_rate.unwrap_or(0.0))?;
175        } else {
176            output.write_i32::<LE>(self.replication_amount.unwrap_or(-1))?;
177        }
178        Ok(())
179    }
180}
181
182impl TerrainRestriction {
183    pub fn read_from(
184        mut input: impl Read,
185        version: FileVersion,
186        num_terrains: u16,
187    ) -> Result<Self> {
188        let mut passability = vec![0.0; num_terrains as usize];
189        for value in passability.iter_mut() {
190            *value = input.read_f32::<LE>()?;
191        }
192
193        // Apparently AoK+ only
194        let mut pass_graphics = Vec::with_capacity(num_terrains as usize);
195        for _ in 0..num_terrains {
196            pass_graphics.push(TerrainPassGraphic::read_from(&mut input, version)?);
197        }
198
199        Ok(Self {
200            passability,
201            pass_graphics,
202        })
203    }
204
205    /// Serialize this object to a binary output stream.
206    pub fn write_to(
207        &self,
208        mut output: impl Write,
209        version: FileVersion,
210        num_terrains: u16,
211    ) -> Result<()> {
212        assert_eq!(self.passability.len(), num_terrains.into());
213        assert_eq!(self.pass_graphics.len(), num_terrains.into());
214        for value in &self.passability {
215            output.write_f32::<LE>(*value)?;
216        }
217        for graphic in &self.pass_graphics {
218            graphic.write_to(&mut output, version)?;
219        }
220        Ok(())
221    }
222}
223
224impl TileSize {
225    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
226        let width = input.read_i16::<LE>()?;
227        let height = input.read_i16::<LE>()?;
228        let delta_z = input.read_i16::<LE>()?;
229        Ok(Self {
230            width,
231            height,
232            delta_z,
233        })
234    }
235
236    /// Serialize this object to a binary output stream.
237    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
238        output.write_i16::<LE>(self.width)?;
239        output.write_i16::<LE>(self.height)?;
240        output.write_i16::<LE>(self.delta_z)?;
241        Ok(())
242    }
243}
244
245impl TerrainAnimation {
246    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
247        let mut anim = TerrainAnimation::default();
248        anim.enabled = input.read_u8()? != 0;
249        anim.num_frames = input.read_i16::<LE>()?;
250        anim.num_pause_frames = input.read_i16::<LE>()?;
251        anim.frame_interval = input.read_f32::<LE>()?;
252        anim.replay_delay = input.read_f32::<LE>()?;
253        anim.frame = input.read_i16::<LE>()?;
254        anim.draw_frame = input.read_i16::<LE>()?;
255        anim.animate_last = input.read_f32::<LE>()?;
256        anim.frame_changed = input.read_u8()? != 0;
257        anim.drawn = input.read_u8()? != 0;
258        Ok(anim)
259    }
260
261    /// Serialize this object to a binary output stream.
262    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
263        output.write_u8(if self.enabled { 1 } else { 0 })?;
264        output.write_i16::<LE>(self.num_frames)?;
265        output.write_i16::<LE>(self.num_pause_frames)?;
266        output.write_f32::<LE>(self.frame_interval)?;
267        output.write_f32::<LE>(self.replay_delay)?;
268        output.write_i16::<LE>(self.frame)?;
269        output.write_i16::<LE>(self.draw_frame)?;
270        output.write_f32::<LE>(self.animate_last)?;
271        output.write_u8(if self.frame_changed { 1 } else { 0 })?;
272        output.write_u8(if self.drawn { 1 } else { 0 })?;
273        Ok(())
274    }
275}
276
277impl TerrainSpriteFrame {
278    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
279        let num_frames = input.read_i16::<LE>()?;
280        let num_facets = input.read_i16::<LE>()?;
281        let frame_id = input.read_i16::<LE>()?;
282        Ok(Self {
283            num_frames,
284            num_facets,
285            frame_id,
286        })
287    }
288
289    /// Serialize this object to a binary output stream.
290    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
291        output.write_i16::<LE>(self.num_frames)?;
292        output.write_i16::<LE>(self.num_facets)?;
293        output.write_i16::<LE>(self.frame_id)?;
294        Ok(())
295    }
296}
297
298impl Terrain {
299    /// Get the internal name of this terrain.
300    pub fn name(&self) -> &str {
301        self.name.as_str()
302    }
303
304    /// Read a Terrain object from an input stream.
305    pub fn read_from(
306        mut input: impl Read,
307        version: FileVersion,
308        num_terrains: u16,
309    ) -> Result<Self> {
310        let mut terrain = Terrain::default();
311        terrain.enabled = input.read_u8()? != 0;
312        terrain.random = input.read_u8()?;
313        read_terrain_name(&mut input, &mut terrain.name)?;
314        read_terrain_name(&mut input, &mut terrain.slp_name)?;
315        // println!("{}", terrain.name);
316        terrain.slp_id = read_opt_u32(&mut input)?;
317        let _slp_pointer = input.read_i32::<LE>()?;
318        terrain.sound_id = read_opt_u32(&mut input)?;
319        if version.is_de2() {
320            terrain.wwise_sound_id = read_opt_u32(&mut input)?;
321            terrain.wwise_stop_sound_id = read_opt_u32(&mut input)?;
322        } else {
323            terrain.blend_priority = Some(input.read_i32::<LE>()?);
324            terrain.blend_mode = Some(input.read_i32::<LE>()?);
325        }
326        terrain.minimap_color_high = input.read_u8()?;
327        terrain.minimap_color_medium = input.read_u8()?;
328        terrain.minimap_color_low = input.read_u8()?;
329        terrain.minimap_color_cliff_lt = input.read_u8()?;
330        terrain.minimap_color_cliff_rt = input.read_u8()?;
331        terrain.passable_terrain_id = match input.read_u8()? {
332            0xFF => None,
333            id => Some(id),
334        };
335        terrain.impassable_terrain_id = match input.read_u8()? {
336            0xFF => None,
337            id => Some(id),
338        };
339        terrain.animation = TerrainAnimation::read_from(&mut input)?;
340        for _ in 0..19 {
341            terrain
342                .elevation_sprites
343                .push(TerrainSpriteFrame::read_from(&mut input)?);
344        }
345        terrain.terrain_id_to_draw = read_opt_u16(&mut input)?;
346        terrain.rows = input.read_i16::<LE>()?;
347        terrain.cols = input.read_i16::<LE>()?;
348        for _ in 0..num_terrains {
349            terrain.borders.push(input.read_i16::<LE>()?);
350        }
351
352        let mut terrain_objects = vec![TerrainObject::default(); 30];
353        for object in terrain_objects.iter_mut() {
354            object.object_id = input.read_u16::<LE>()?.into();
355        }
356        for object in terrain_objects.iter_mut() {
357            object.density = input.read_i16::<LE>()?;
358        }
359        for object in terrain_objects.iter_mut() {
360            object.placement_flag = input.read_i8()?;
361        }
362
363        let _num_terrain_objects = input.read_u16::<LE>()?;
364        // Why is num_terrain_objects always 0?
365        // terrain_objects.truncate(num_terrain_objects as usize);
366        terrain.terrain_objects = terrain_objects;
367
368        let _padding = input.read_u16::<LE>()?;
369
370        Ok(terrain)
371    }
372
373    /// Serialize this object to a binary output stream.
374    pub fn write_to<W: Write>(
375        &self,
376        output: &mut W,
377        _version: FileVersion,
378        num_terrains: u16,
379    ) -> Result<()> {
380        assert_eq!(self.borders.len(), num_terrains as usize);
381        output.write_u8(if self.enabled { 1 } else { 0 })?;
382        output.write_u8(self.random)?;
383        write_terrain_name(output, &self.name)?;
384        write_terrain_name(output, &self.slp_name)?;
385        output.write_i32::<LE>(self.slp_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
386        output.write_i32::<LE>(0)?; // slp pointer
387        output.write_i32::<LE>(self.sound_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
388        output.write_i32::<LE>(self.blend_priority.unwrap_or(-1))?;
389        output.write_i32::<LE>(self.blend_mode.unwrap_or(-1))?;
390        output.write_u8(self.minimap_color_high)?;
391        output.write_u8(self.minimap_color_medium)?;
392        output.write_u8(self.minimap_color_low)?;
393        output.write_u8(self.minimap_color_cliff_lt)?;
394        output.write_u8(self.minimap_color_cliff_rt)?;
395        output.write_u8(self.passable_terrain_id.unwrap_or(0xFF))?;
396        output.write_u8(self.impassable_terrain_id.unwrap_or(0xFF))?;
397        self.animation.write_to(output)?;
398        for frame in &self.elevation_sprites {
399            frame.write_to(output)?;
400        }
401        output.write_i16::<LE>(
402            self.terrain_id_to_draw
403                .map(|id| id.try_into().unwrap())
404                .unwrap_or(-1),
405        )?;
406        output.write_i16::<LE>(self.rows)?;
407        output.write_i16::<LE>(self.cols)?;
408        for border in &self.borders {
409            output.write_i16::<LE>(*border)?;
410        }
411
412        for index in 0..30 {
413            if let Some(object) = self.terrain_objects.get(index) {
414                output.write_u16::<LE>(object.object_id.into())?;
415            } else {
416                output.write_u16::<LE>(0)?;
417            }
418        }
419        for index in 0..30 {
420            if let Some(object) = self.terrain_objects.get(index) {
421                output.write_i16::<LE>(object.density)?;
422            } else {
423                output.write_i16::<LE>(0)?;
424            }
425        }
426        for index in 0..30 {
427            if let Some(object) = self.terrain_objects.get(index) {
428                output.write_i8(object.placement_flag)?;
429            } else {
430                output.write_i8(0)?;
431            }
432        }
433        output.write_u16::<LE>(self.terrain_objects.len() as u16)?;
434
435        output.write_u16::<LE>(0)?; // padding
436
437        Ok(())
438    }
439}
440
441impl TerrainBorder {
442    pub fn read_from(mut input: impl Read) -> Result<Self> {
443        let mut border = TerrainBorder::default();
444        border.enabled = input.read_u8()? != 0;
445        border.random = input.read_u8()?;
446        read_terrain_name(&mut input, &mut border.name)?;
447        read_terrain_name(&mut input, &mut border.slp_name)?;
448        border.slp_id = read_opt_u32(&mut input)?;
449        let _slp_pointer = input.read_i32::<LE>()?;
450        border.sound_id = read_opt_u32(&mut input)?;
451        border.color = (input.read_u8()?, input.read_u8()?, input.read_u8()?);
452        border.animation = TerrainAnimation::read_from(&mut input)?;
453        for _ in 0..19 {
454            let mut frames_list = vec![TerrainSpriteFrame::default(); 12];
455            for frame in frames_list.iter_mut() {
456                *frame = TerrainSpriteFrame::read_from(&mut input)?;
457            }
458            border.frames.push(frames_list);
459        }
460
461        border.draw_tile = input.read_i8()?;
462        // Padding
463        input.read_u8()?;
464        border.underlay_terrain = read_opt_u16(&mut input)?;
465        border.border_style = input.read_i16::<LE>()?;
466
467        Ok(border)
468    }
469
470    /// Serialize this object to a binary output stream.
471    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
472        output.write_u8(if self.enabled { 1 } else { 0 })?;
473        output.write_u8(self.random)?;
474        write_terrain_name(output, &self.name)?;
475        write_terrain_name(output, &self.slp_name)?;
476        output.write_i32::<LE>(self.slp_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
477        output.write_i32::<LE>(0)?; // slp pointer
478        output.write_i32::<LE>(self.sound_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
479        output.write_u8(self.color.0)?;
480        output.write_u8(self.color.1)?;
481        output.write_u8(self.color.2)?;
482        self.animation.write_to(output)?;
483        for frames_list in &self.frames {
484            for frame in frames_list {
485                frame.write_to(output)?;
486            }
487        }
488        output.write_i8(self.draw_tile)?;
489        output.write_u8(0)?; // padding
490        output.write_i16::<LE>(
491            self.underlay_terrain
492                .map(|id| id.try_into().unwrap())
493                .unwrap_or(-1),
494        )?;
495        output.write_i16::<LE>(self.border_style)?;
496        Ok(())
497    }
498}
499
500fn read_terrain_name<R: Read>(input: &mut R, output: &mut TerrainName) -> Result<()> {
501    let bytes = &mut [0; 13];
502    input.read_exact(bytes)?;
503    bytes
504        .iter()
505        .cloned()
506        .take_while(|b| *b != 0)
507        .map(char::from)
508        .for_each(|c| output.push(c));
509    Ok(())
510}
511
512fn write_terrain_name<W: Write>(output: &mut W, name: &TerrainName) -> Result<()> {
513    let bytes = &mut [0; 13];
514    (&mut bytes[..name.len()]).copy_from_slice(name.as_bytes());
515    output.write_all(bytes)?;
516    Ok(())
517}