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#[derive(Debug, Default, Clone)]
12pub struct EffectCommand {
13 pub command_type: u8,
15 pub params: (i16, i16, i16, f32),
17}
18
19type TechEffectName = ArrayString<[u8; 31]>;
20
21#[derive(Debug, Default, Clone)]
23pub struct TechEffect {
24 name: TechEffectName,
26 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 pub fn name(&self) -> &str {
85 self.name.as_str()
86 }
87
88 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 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 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}