1use crate::Result;
2use arrayvec::ArrayVec;
3use byteorder::{ReadBytesExt, WriteBytesExt, LE};
4pub use genie_dat::AttributeCost;
5pub use genie_support::{StringKey, UnitTypeID};
6use std::cmp;
7use std::convert::{TryFrom, TryInto};
8
9use std::io::{Read, Write};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum UnitBaseClass {
13 Static = 10,
14 Animated = 20,
15 Doppelganger = 25,
16 Moving = 30,
17 Action = 40,
18 BaseCombat = 50,
19 Missile = 60,
20 Combat = 70,
21 Building = 80,
22 Tree = 90,
23}
24
25impl cmp::PartialOrd for UnitBaseClass {
26 fn partial_cmp(&self, other: &UnitBaseClass) -> Option<cmp::Ordering> {
27 if self == other {
28 return Some(cmp::Ordering::Equal);
29 }
30
31 let self_n = *self as u8;
32 let other_n = *other as u8;
33
34 match self {
36 Self::Doppelganger => {
37 if self_n > other_n {
38 Some(cmp::Ordering::Greater)
39 } else {
40 None
41 }
42 }
43 Self::Missile => {
44 if self_n > other_n {
45 Some(cmp::Ordering::Greater)
46 } else {
47 None
48 }
49 }
50 Self::Tree => match other {
51 Self::Static => Some(cmp::Ordering::Greater),
52 _ => None,
53 },
54 _ => match other {
55 Self::Doppelganger => {
56 if self_n < other_n {
57 Some(cmp::Ordering::Less)
58 } else {
59 None
60 }
61 }
62 Self::Missile => {
63 if self_n < other_n {
64 Some(cmp::Ordering::Less)
65 } else {
66 None
67 }
68 }
69 Self::Tree => match self {
70 Self::Static => Some(cmp::Ordering::Less),
71 _ => None,
72 },
73 _ => Some(self_n.cmp(&other_n)),
74 },
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, thiserror::Error)]
80#[error("unknown unit base class: {}", .0)]
81pub struct ParseUnitBaseClassError(u8);
82
83impl TryFrom<u8> for UnitBaseClass {
84 type Error = ParseUnitBaseClassError;
85
86 fn try_from(n: u8) -> std::result::Result<Self, Self::Error> {
87 match n {
88 10 => Ok(UnitBaseClass::Static),
89 20 => Ok(UnitBaseClass::Animated),
90 25 => Ok(UnitBaseClass::Doppelganger),
91 30 => Ok(UnitBaseClass::Moving),
92 40 => Ok(UnitBaseClass::Action),
93 50 => Ok(UnitBaseClass::BaseCombat),
94 60 => Ok(UnitBaseClass::Missile),
95 70 => Ok(UnitBaseClass::Combat),
96 80 => Ok(UnitBaseClass::Building),
97 90 => Ok(UnitBaseClass::Tree),
98 n => Err(ParseUnitBaseClassError(n)),
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
104pub struct CompactUnitType {
105 pub unit_base_class: UnitBaseClass,
106 pub static_: StaticUnitAttributes,
107 pub animated: Option<AnimatedUnitAttributes>,
108 pub moving: Option<MovingUnitAttributes>,
109 pub action: Option<ActionUnitAttributes>,
110 pub base_combat: Option<BaseCombatUnitAttributes>,
111 pub missile: Option<MissileUnitAttributes>,
112 pub combat: Option<CombatUnitAttributes>,
113 pub building: Option<BuildingUnitAttributes>,
114}
115
116impl CompactUnitType {
117 pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
118 let unit_base_class = input.read_u8()?.try_into().unwrap();
119 let static_ = StaticUnitAttributes::read_from(&mut input)?;
120 let mut unit = Self {
121 unit_base_class,
122 static_,
123 animated: None,
124 moving: None,
125 action: None,
126 base_combat: None,
127 missile: None,
128 combat: None,
129 building: None,
130 };
131 if unit_base_class >= UnitBaseClass::Animated {
132 unit.animated = Some(AnimatedUnitAttributes::read_from(&mut input)?);
133 }
134 if unit_base_class >= UnitBaseClass::Moving {
135 unit.moving = Some(MovingUnitAttributes::read_from(&mut input)?);
136 }
137 if unit_base_class >= UnitBaseClass::Action {
138 unit.action = Some(ActionUnitAttributes::read_from(&mut input)?);
139 }
140 if unit_base_class >= UnitBaseClass::BaseCombat {
141 unit.base_combat = Some(BaseCombatUnitAttributes::read_from(&mut input, version)?);
142 }
143 if unit_base_class >= UnitBaseClass::Missile {
144 unit.missile = Some(MissileUnitAttributes::read_from(&mut input)?);
145 }
146 if unit_base_class >= UnitBaseClass::Combat {
147 unit.combat = Some(CombatUnitAttributes::read_from(&mut input)?);
148 }
149 if unit_base_class >= UnitBaseClass::Building {
150 unit.building = Some(BuildingUnitAttributes::read_from(&mut input)?);
151 }
152 Ok(unit)
153 }
154}
155
156#[derive(Debug, Default, Clone)]
157pub struct StaticUnitAttributes {
158 id: UnitTypeID,
159 copy_id: UnitTypeID,
160 base_id: UnitTypeID,
161 unit_class: u16,
162 hotkey_id: u32,
163 available: bool,
164 death_object_id: Option<UnitTypeID>,
165 string_id: Option<StringKey>,
166 description_id: Option<StringKey>,
167 flags: Option<u32>,
168 help_string_id: Option<StringKey>,
169 terrain_restriction: Option<u16>,
170 hidden_in_editor: bool,
171 is_queueable_tech: bool,
172 hit_points: u16,
173 line_of_sight: f32,
174 garrison_capacity: u8,
175 radius: (f32, f32),
176 attribute_max_amount: u16,
177 attribute_amount_held: f32,
178 disabled: bool,
179}
180
181impl StaticUnitAttributes {
182 pub fn read_from(mut input: impl Read) -> Result<Self> {
183 let mut attrs = Self::default();
184 attrs.id = input.read_u16::<LE>()?.into();
185 attrs.copy_id = input.read_u16::<LE>()?.into();
186 attrs.base_id = input.read_u16::<LE>()?.into();
187 attrs.unit_class = input.read_u16::<LE>()?;
188 attrs.hotkey_id = input.read_u32::<LE>()?;
189 attrs.available = input.read_u8()? != 0;
190 let hidden_in_editor = input.read_i8()?;
191 let hidden_flags = if hidden_in_editor == -16 {
193 attrs.death_object_id = match input.read_i16::<LE>()? {
194 -1 => None,
195 id => Some(id.try_into().unwrap()),
196 };
197 attrs.string_id = Some(input.read_u16::<LE>()?.into());
198 attrs.description_id = Some(input.read_u16::<LE>()?.into());
199 attrs.flags = Some(input.read_u32::<LE>()?);
200 attrs.help_string_id = Some(input.read_u32::<LE>()?.into());
201 attrs.terrain_restriction = Some(input.read_u16::<LE>()?);
202 input.read_i8()?
203 } else {
204 hidden_in_editor
205 };
206 attrs.hidden_in_editor = hidden_flags != 0;
207 attrs.is_queueable_tech = (hidden_flags & 2) == 2;
209 attrs.hit_points = input.read_u16::<LE>()?;
210 attrs.line_of_sight = input.read_f32::<LE>()?;
211 attrs.garrison_capacity = input.read_u8()?;
212 attrs.radius = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
213 attrs.attribute_max_amount = input.read_u16::<LE>()?;
214 attrs.attribute_amount_held = input.read_f32::<LE>()?;
215 attrs.disabled = input.read_u8()? != 0;
216 Ok(attrs)
217 }
218}
219
220#[derive(Debug, Default, Clone)]
221pub struct AnimatedUnitAttributes {
222 pub speed: f32,
223}
224
225impl AnimatedUnitAttributes {
226 pub fn read_from(mut input: impl Read) -> Result<Self> {
227 let speed = input.read_f32::<LE>()?;
228 Ok(Self { speed })
229 }
230
231 pub fn write_to(&self, mut output: impl Write) -> Result<()> {
232 output.write_f32::<LE>(self.speed)?;
233 Ok(())
234 }
235}
236
237#[derive(Debug, Default, Clone)]
238pub struct MovingUnitAttributes {
239 pub turn_speed: f32,
240}
241
242impl MovingUnitAttributes {
243 pub fn read_from(mut input: impl Read) -> Result<Self> {
244 let turn_speed = input.read_f32::<LE>()?;
245 Ok(Self { turn_speed })
246 }
247
248 pub fn write_to(&self, mut output: impl Write) -> Result<()> {
249 output.write_f32::<LE>(self.turn_speed)?;
250 Ok(())
251 }
252}
253
254#[derive(Debug, Default, Clone)]
255pub struct ActionUnitAttributes {
256 pub search_radius: f32,
257 pub work_rate: f32,
258}
259
260impl ActionUnitAttributes {
261 pub fn read_from(mut input: impl Read) -> Result<Self> {
262 let search_radius = input.read_f32::<LE>()?;
263 let work_rate = input.read_f32::<LE>()?;
264 Ok(Self {
265 search_radius,
266 work_rate,
267 })
268 }
269
270 pub fn write_to(&self, mut output: impl Write) -> Result<()> {
271 output.write_f32::<LE>(self.search_radius)?;
272 output.write_f32::<LE>(self.work_rate)?;
273 Ok(())
274 }
275}
276
277#[derive(Debug, Default, Clone, Copy)]
278pub struct HitType {
279 pub hit_type: u16,
280 pub amount: i16,
281}
282
283impl HitType {
284 pub fn read_from(mut input: impl Read) -> Result<Self> {
285 let hit_type = input.read_u16::<LE>()?;
286 let amount = input.read_i16::<LE>()?;
287 Ok(Self { hit_type, amount })
288 }
289}
290
291#[derive(Debug, Default, Clone)]
292pub struct BaseCombatUnitAttributes {
293 pub base_armor: u16,
294 pub attacks: Vec<HitType>,
295 pub armors: Vec<HitType>,
296 pub attack_speed: f32,
297 pub weapon_range_max: f32,
298 pub base_hit_chance: u16,
299 pub projectile_object_id: Option<UnitTypeID>,
300 pub defense_terrain_bonus: Option<u16>,
301 pub weapon_range_max_2: f32,
302 pub area_of_effect: f32,
303 pub weapon_range_min: f32,
304}
305
306impl BaseCombatUnitAttributes {
307 pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
308 let mut attrs = Self::default();
309 attrs.base_armor = if version >= 11.52 {
310 input.read_u16::<LE>()?
311 } else {
312 input.read_u8()?.into()
313 };
314 let num_attacks = input.read_u16::<LE>()?;
315 for _ in 0..num_attacks {
316 attrs.attacks.push(HitType::read_from(&mut input)?);
317 }
318 let num_armors = input.read_u16::<LE>()?;
319 for _ in 0..num_armors {
320 attrs.armors.push(HitType::read_from(&mut input)?);
321 }
322 attrs.attack_speed = input.read_f32::<LE>()?;
323 attrs.weapon_range_max = input.read_f32::<LE>()?;
324 attrs.base_hit_chance = input.read_u16::<LE>()?;
325 attrs.projectile_object_id = match input.read_i16::<LE>()? {
326 -1 => None,
327 id => Some(id.try_into().unwrap()),
328 };
329 attrs.defense_terrain_bonus = match input.read_i16::<LE>()? {
330 -1 => None,
331 id => Some(id.try_into().unwrap()),
332 };
333 attrs.weapon_range_max_2 = input.read_f32::<LE>()?;
334 attrs.area_of_effect = input.read_f32::<LE>()?;
335 attrs.weapon_range_min = input.read_f32::<LE>()?;
336 Ok(attrs)
337 }
338
339 pub fn write_to(&self, _output: impl Write) -> Result<()> {
340 todo!()
341 }
342}
343
344#[derive(Debug, Default, Clone)]
345pub struct MissileUnitAttributes {
346 pub targeting_type: u8,
347}
348
349impl MissileUnitAttributes {
350 pub fn read_from(mut input: impl Read) -> Result<Self> {
351 let targeting_type = input.read_u8()?;
352 Ok(Self { targeting_type })
353 }
354
355 pub fn write_to(&self, mut output: impl Write) -> Result<()> {
356 output.write_u8(self.targeting_type)?;
357 Ok(())
358 }
359}
360
361#[derive(Debug, Default, Clone)]
362pub struct CombatUnitAttributes {
363 pub costs: ArrayVec<[AttributeCost; 3]>,
364 pub create_time: u16,
365 pub original_pierce_armor: Option<u16>,
366 pub original_armor: Option<u16>,
367 pub original_weapon: Option<u16>,
368 pub original_weapon_range: Option<f32>,
369 pub area_effect_level: Option<u8>,
370 pub frame_delay: Option<u16>,
371 pub create_at_building: Option<UnitTypeID>,
372 pub create_button: Option<i8>,
373 pub rear_attack_modifier: Option<f32>,
374 pub hero_flag: Option<u8>,
375 pub volley_fire_amount: f32,
376 pub max_attacks_in_volley: u8,
377}
378
379impl CombatUnitAttributes {
380 pub fn read_from(mut input: impl Read) -> Result<Self> {
381 let mut attrs = Self::default();
382 for _ in 0..3 {
383 let attr = AttributeCost::read_from(&mut input)?;
384 if attr.attribute_type >= 0 {
385 attrs.costs.push(attr);
386 }
387 }
388 let create_time = input.read_u16::<LE>()?;
389 if create_time == u16::max_value() - 15 {
391 attrs.original_pierce_armor = Some(input.read_u16::<LE>()?);
392 attrs.original_armor = Some(input.read_u16::<LE>()?);
393 attrs.original_weapon = Some(input.read_u16::<LE>()?);
394 attrs.original_weapon_range = Some(input.read_f32::<LE>()?);
395 attrs.area_effect_level = Some(input.read_u8()?);
396 attrs.frame_delay = Some(input.read_u16::<LE>()?);
397 attrs.create_at_building = match input.read_i16::<LE>()? {
398 -1 => None,
399 id => Some(id.try_into().unwrap()),
400 };
401 attrs.create_button = Some(input.read_i8()?);
402 attrs.rear_attack_modifier = Some(input.read_f32::<LE>()?);
403 attrs.hero_flag = Some(input.read_u8()?);
404 attrs.create_time = input.read_u16::<LE>()?;
405 } else {
406 attrs.create_time = create_time;
407 };
408 attrs.volley_fire_amount = input.read_f32::<LE>()?;
409 attrs.max_attacks_in_volley = input.read_u8()?;
410 Ok(attrs)
411 }
412
413 pub fn write_to(&self, _output: impl Write) -> Result<()> {
414 todo!()
415 }
416}
417
418#[derive(Debug, Default, Clone)]
419pub struct BuildingUnitAttributes {
420 pub facet: i16,
421 needs_a_name: Option<u8>,
423 needs_a_name_2: Option<u8>,
425 pub garrison_heal_rate: Option<f32>,
426}
427
428impl BuildingUnitAttributes {
429 pub fn read_from(mut input: impl Read) -> Result<Self> {
430 let mut attrs = Self::default();
431 let facet = input.read_i16::<LE>()?;
432 if facet == 32767 {
434 attrs.needs_a_name = Some(input.read_u8()?);
435 attrs.needs_a_name_2 = Some(input.read_u8()?);
436 attrs.garrison_heal_rate = Some(input.read_f32::<LE>()?);
437 attrs.facet = input.read_i16::<LE>()?;
438 } else {
439 attrs.facet = facet;
440 }
441 Ok(attrs)
442 }
443
444 pub fn write_to(&self, _output: impl Write) -> Result<()> {
445 todo!()
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn cmp_unit_base_class() {
455 assert!(UnitBaseClass::Static == UnitBaseClass::Static);
456 assert!(UnitBaseClass::Static < UnitBaseClass::Animated);
457 assert!(UnitBaseClass::Static < UnitBaseClass::Doppelganger);
458 assert!(UnitBaseClass::Animated < UnitBaseClass::Doppelganger);
459 assert!(!(UnitBaseClass::Moving < UnitBaseClass::Doppelganger));
460 assert!(!(UnitBaseClass::Moving > UnitBaseClass::Doppelganger));
461 }
462}