Skip to main content

genie_dat/
sound.rs

1use crate::FileVersion;
2use arrayvec::ArrayString;
3use byteorder::{ReadBytesExt, WriteBytesExt, LE};
4use genie_support::{fallible_try_from, fallible_try_into, infallible_try_into};
5use std::convert::TryInto;
6use std::io::{Read, Result, Write};
7
8/// An ID identifying a sound.
9#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
10pub struct SoundID(u16);
11impl From<u16> for SoundID {
12    fn from(n: u16) -> Self {
13        SoundID(n)
14    }
15}
16
17impl From<SoundID> for u16 {
18    fn from(n: SoundID) -> Self {
19        n.0
20    }
21}
22
23impl From<SoundID> for usize {
24    fn from(n: SoundID) -> Self {
25        n.0.into()
26    }
27}
28
29fallible_try_into!(SoundID, i16);
30infallible_try_into!(SoundID, i32);
31infallible_try_into!(SoundID, u32);
32fallible_try_from!(SoundID, i16);
33fallible_try_from!(SoundID, i32);
34fallible_try_from!(SoundID, u32);
35
36/// A "conceptual" sound, consisting of one or a group of sound files.
37///
38/// Items can be picked depending on the player's civilization, and depending on the probabilities
39/// for each file.
40#[derive(Debug, Default, Clone)]
41pub struct Sound {
42    /// Unique ID for this sound.
43    pub id: SoundID,
44    /// TODO document.
45    pub play_delay: i16,
46    /// TODO document.
47    pub cache_time: i32,
48    /// List of sound files in this sound.
49    pub items: Vec<SoundItem>,
50}
51
52/// A single sound file.
53#[derive(Debug, Default, Clone)]
54pub struct SoundItem {
55    /// Internal file name for this sound file.
56    pub filename: ArrayString<[u8; 13]>,
57    /// DRS file ID for this sound file.
58    pub resource_id: i32,
59    /// The probability out of 100% that this file will be used for any given playback.
60    pub probability: i16,
61    /// Use this file for this civilization ID only.
62    pub civilization: Option<i16>,
63    /// File icon set (TODO what does this do?)
64    pub icon_set: Option<i16>,
65}
66
67impl SoundItem {
68    /// Read this sound item from an input stream.
69    pub fn read_from<R: Read>(input: &mut R, _version: FileVersion) -> Result<Self> {
70        let mut item = SoundItem::default();
71        // TODO actually use this
72        let mut filename = [0u8; 13];
73        input.read_exact(&mut filename)?;
74        item.resource_id = input.read_i32::<LE>()?;
75        item.probability = input.read_i16::<LE>()?;
76        // AoK only
77        item.civilization = Some(input.read_i16::<LE>()?);
78        item.icon_set = Some(input.read_i16::<LE>()?);
79
80        Ok(item)
81    }
82
83    /// Write this sound item to an input stream.
84    pub fn write_to<W: Write>(&self, output: &mut W, _version: FileVersion) -> Result<()> {
85        output.write_all(&[0; 13])?;
86        output.write_i32::<LE>(self.resource_id)?;
87        output.write_i16::<LE>(self.probability)?;
88        // AoK only, must both be set
89        assert!(self.civilization.is_some());
90        assert!(self.icon_set.is_some());
91        output.write_i16::<LE>(self.civilization.unwrap())?;
92        output.write_i16::<LE>(self.icon_set.unwrap())?;
93        Ok(())
94    }
95}
96
97impl Sound {
98    /// Read this sound from an input stream.
99    pub fn read_from<R: Read>(input: &mut R, version: FileVersion) -> Result<Self> {
100        let mut sound = Sound::default();
101        sound.id = input.read_u16::<LE>()?.into();
102        sound.play_delay = input.read_i16::<LE>()?;
103        let num_items = input.read_u16::<LE>()?;
104        sound.cache_time = input.read_i32::<LE>()?;
105        if version.is_de2() {
106            let _total_probability = input.read_u16::<LE>()?;
107        }
108        for _ in 0..num_items {
109            sound.items.push(SoundItem::read_from(input, version)?);
110        }
111        Ok(sound)
112    }
113
114    /// Write this sound to an input stream.
115    pub fn write_to<W: Write>(&self, output: &mut W, version: FileVersion) -> Result<()> {
116        output.write_u16::<LE>(self.id.into())?;
117        output.write_i16::<LE>(self.play_delay)?;
118        output.write_u16::<LE>(self.len().try_into().unwrap())?;
119        output.write_i32::<LE>(self.cache_time)?;
120        for item in &self.items {
121            item.write_to(output, version)?;
122        }
123        Ok(())
124    }
125
126    /// Get the number of sound files that are part of this "conceptual" sound.
127    pub fn len(&self) -> usize {
128        self.items.len()
129    }
130
131    /// Returns true if there are no sound files.
132    pub fn is_empty(&self) -> bool {
133        self.items.is_empty()
134    }
135}