genie_dat/
tech.rs

1use crate::civ::CivilizationID;
2use crate::unit_type::UnitTypeID;
3use arrayvec::{ArrayString, ArrayVec};
4use byteorder::{ReadBytesExt, WriteBytesExt, LE};
5use encoding_rs::WINDOWS_1252;
6pub use genie_support::TechID;
7use genie_support::{read_opt_u16, read_opt_u32, MapInto, StringKey};
8use std::io::{Read, Result, Write};
9
10/// An effect command specifies an attribute change when a tech effect is triggered.
11#[derive(Debug, Default, Clone)]
12pub struct EffectCommand {
13    /// The command.
14    pub command_type: u8,
15    /// Command-dependent parameters.
16    pub params: (i16, i16, i16, f32),
17}
18
19type TechEffectName = ArrayString<[u8; 31]>;
20
21/// A tech effect is a group of attribute changes that are applied when the effect is triggered.
22#[derive(Debug, Default, Clone)]
23pub struct TechEffect {
24    /// Name for the effect.
25    name: TechEffectName,
26    /// Attribute commands to execute when this effect is triggered.
27    pub commands: Vec<EffectCommand>,
28}
29
30#[derive(Debug, Default, Clone, Copy)]
31pub struct TechEffectRef {
32    pub effect_type: u16,
33    pub amount: u16,
34    pub enabled: bool,
35}
36
37#[derive(Debug, Default, Clone)]
38pub struct Tech {
39    required_techs: ArrayVec<[TechID; 6]>,
40    effects: ArrayVec<[TechEffectRef; 3]>,
41    civilization_id: Option<CivilizationID>,
42    full_tech_mode: u16,
43    location: Option<UnitTypeID>,
44    language_dll_name: Option<StringKey>,
45    language_dll_description: Option<StringKey>,
46    time: u16,
47    time2: u16,
48    type_: u16,
49    icon_id: Option<u16>,
50    button_id: u8,
51    language_dll_help: Option<StringKey>,
52    help_page_id: u32,
53    hotkey: Option<u32>,
54    name: String,
55}
56
57impl EffectCommand {
58    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
59        let command_type = input.read_u8()?;
60        let params = (
61            input.read_i16::<LE>()?,
62            input.read_i16::<LE>()?,
63            input.read_i16::<LE>()?,
64            input.read_f32::<LE>()?,
65        );
66        Ok(EffectCommand {
67            command_type,
68            params,
69        })
70    }
71
72    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
73        output.write_u8(self.command_type)?;
74        output.write_i16::<LE>(self.params.0)?;
75        output.write_i16::<LE>(self.params.1)?;
76        output.write_i16::<LE>(self.params.2)?;
77        output.write_f32::<LE>(self.params.3)?;
78        Ok(())
79    }
80}
81
82impl TechEffect {
83    /// Get the name of this effect.
84    pub fn name(&self) -> &str {
85        self.name.as_str()
86    }
87
88    /// Set the name of this effect.
89    ///
90    /// # Panics
91    /// This function panics if `name` requires more than 31 bytes of storage.
92    pub fn set_name(&mut self, name: &str) {
93        self.name = TechEffectName::from(name).unwrap();
94    }
95
96    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
97        let mut effect = Self::default();
98        let mut bytes = [0; 31];
99        input.read_exact(&mut bytes)?;
100        let bytes = &bytes[..bytes.iter().position(|&c| c == 0).unwrap_or(bytes.len())];
101        let (name, _encoding, _failed) = WINDOWS_1252.decode(&bytes);
102        effect.name = TechEffectName::from(&name).unwrap();
103
104        let num_commands = input.read_u16::<LE>()?;
105        for _ in 0..num_commands {
106            effect.commands.push(EffectCommand::read_from(input)?);
107        }
108
109        Ok(effect)
110    }
111
112    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
113        let mut buffer = [0; 31];
114        (&mut buffer[..self.name.len()]).copy_from_slice(self.name.as_bytes());
115        output.write_all(&buffer)?;
116
117        output.write_u16::<LE>(self.commands.len() as u16)?;
118        for effect in &self.commands {
119            effect.write_to(output)?;
120        }
121        Ok(())
122    }
123}
124
125impl TechEffectRef {
126    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
127        Ok(Self {
128            effect_type: input.read_u16::<LE>()?,
129            amount: input.read_u16::<LE>()?,
130            enabled: input.read_u8()? != 0,
131        })
132    }
133
134    pub fn write_to<W: Write>(self, output: &mut W) -> Result<()> {
135        output.write_u16::<LE>(self.effect_type)?;
136        output.write_u16::<LE>(self.amount)?;
137        output.write_u8(if self.enabled { 1 } else { 0 })?;
138        Ok(())
139    }
140}
141
142impl Tech {
143    /// Get the name of this tech.
144    pub fn name(&self) -> &str {
145        self.name.as_str()
146    }
147
148    pub fn read_from(mut input: impl Read) -> Result<Self> {
149        let mut tech = Self::default();
150        for _ in 0..6 {
151            // 4 on some versions
152            if let Some(tech_id) = read_opt_u16(&mut input)? {
153                tech.required_techs.push(tech_id);
154            }
155        }
156        for _ in 0..3 {
157            let effect = TechEffectRef::read_from(&mut input)?;
158            if effect.effect_type != 0xFFFF {
159                tech.effects.push(effect);
160            }
161        }
162        let _num_required_techs = input.read_u16::<LE>()?;
163        tech.civilization_id = read_opt_u16(&mut input)?;
164        tech.full_tech_mode = input.read_u16::<LE>()?;
165        tech.location = read_opt_u16(&mut input)?;
166        tech.language_dll_name = read_opt_u16(&mut input)?;
167        tech.language_dll_description = read_opt_u16(&mut input)?;
168        tech.time = input.read_u16::<LE>()?;
169        tech.time2 = input.read_u16::<LE>()?;
170        tech.type_ = input.read_u16::<LE>()?;
171        tech.icon_id = read_opt_u16(&mut input)?;
172        tech.button_id = input.read_u8()?;
173        tech.language_dll_help = read_opt_u32(&mut input)?;
174        tech.help_page_id = input.read_u32::<LE>()?;
175        tech.hotkey = read_opt_u32(&mut input)?;
176        tech.name = {
177            let name_len = input.read_u16::<LE>()?;
178            let mut bytes = vec![0; name_len as usize];
179            input.read_exact(&mut bytes)?;
180            let bytes = &bytes[..bytes.iter().position(|&c| c == 0).unwrap_or(bytes.len())];
181            let (name, _encoding, _failed) = WINDOWS_1252.decode(&bytes);
182            name.to_string()
183        };
184        Ok(tech)
185    }
186
187    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
188        for i in 0..6 {
189            match self.required_techs.get(i) {
190                Some(&id) => output.write_u16::<LE>(id.into())?,
191                None => output.write_i16::<LE>(-1)?,
192            }
193        }
194        for i in 0..3 {
195            match self.effects.get(i) {
196                Some(effect) => effect.write_to(&mut output)?,
197                None => TechEffectRef {
198                    effect_type: 0xFFFF,
199                    amount: 0,
200                    enabled: false,
201                }
202                .write_to(&mut output)?,
203            }
204        }
205        output.write_u16::<LE>(self.required_techs.len() as u16)?;
206        output.write_u16::<LE>(self.civilization_id.map_into().unwrap_or(0xFFFF))?;
207        output.write_u16::<LE>(self.full_tech_mode)?;
208        output.write_u16::<LE>(self.location.map_into().unwrap_or(0xFFFF))?;
209        output.write_u16::<LE>(match self.language_dll_name {
210            Some(StringKey::Num(id)) => id as u16,
211            Some(_) => unreachable!("cannot use named string keys in dat files"),
212            None => 0xFFFF,
213        })?;
214        output.write_u16::<LE>(match self.language_dll_description {
215            Some(StringKey::Num(id)) => id as u16,
216            Some(_) => unreachable!("cannot use named string keys in dat files"),
217            None => 0xFFFF,
218        })?;
219        output.write_u16::<LE>(self.time)?;
220        output.write_u16::<LE>(self.time2)?;
221        output.write_u16::<LE>(self.type_)?;
222        output.write_u16::<LE>(self.icon_id.map_into().unwrap_or(0xFFFF))?;
223        output.write_u8(self.button_id)?;
224        output.write_u32::<LE>(match self.language_dll_help {
225            Some(StringKey::Num(id)) => id,
226            Some(_) => unreachable!("cannot use named string keys in dat files"),
227            None => 0xFFFF_FFFF,
228        })?;
229        output.write_u32::<LE>(self.help_page_id)?;
230        output.write_u32::<LE>(self.hotkey.map_into().unwrap_or(0xFFFF_FFFF))?;
231        let (encoded, _encoding, _failed) = WINDOWS_1252.encode(&self.name);
232        output.write_u16::<LE>(encoded.len() as u16)?;
233        output.write_all(encoded.as_ref())?;
234        Ok(())
235    }
236}