genie_dat/
tech_tree.rs

1use crate::civ::CivilizationID;
2use crate::unit_type::UnitTypeID;
3use arrayvec::ArrayVec;
4use byteorder::{ReadBytesExt, WriteBytesExt, LE};
5use genie_support::{read_opt_u32, TechID};
6use std::convert::{TryFrom, TryInto};
7use std::io::{self, Read, Result, Write};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum TechTreeStatus {
11    None,
12    /// This building/unit/technology is available to the player.
13    AvailablePlayer,
14    /// This building/unit/technology is not available to the player.
15    NotAvailablePlayer,
16    /// Researching or constructing or creating.
17    Researching,
18    /// Researched or built.
19    ResearchedCompleted,
20    /// This building/unit/technology is available to the player if someone on their team is this
21    /// civilization.
22    AvailableTeam {
23        civilization_id: CivilizationID,
24    },
25}
26
27impl std::default::Default for TechTreeStatus {
28    fn default() -> Self {
29        Self::None
30    }
31}
32
33#[derive(Debug, Clone, Copy, thiserror::Error)]
34#[error("invalid tech tree node status {} (must be 1-5)", .0)]
35pub struct ParseTechTreeStatusError(u8);
36
37impl TryFrom<u8> for TechTreeStatus {
38    type Error = ParseTechTreeStatusError;
39
40    fn try_from(n: u8) -> std::result::Result<Self, Self::Error> {
41        match n {
42            1 => Ok(Self::None),
43            2 => Ok(Self::AvailablePlayer),
44            3 => Ok(Self::NotAvailablePlayer),
45            4 => Ok(Self::Researching),
46            5 => Ok(Self::ResearchedCompleted),
47            10..=255 => Ok(Self::AvailableTeam {
48                civilization_id: (n - 10).into(),
49            }),
50            n => Err(ParseTechTreeStatusError(n as u8)),
51        }
52    }
53}
54
55impl From<TechTreeStatus> for u8 {
56    fn from(status: TechTreeStatus) -> Self {
57        match status {
58            TechTreeStatus::None => 1,
59            TechTreeStatus::AvailablePlayer => 2,
60            TechTreeStatus::NotAvailablePlayer => 3,
61            TechTreeStatus::Researching => 4,
62            TechTreeStatus::ResearchedCompleted => 5,
63            TechTreeStatus::AvailableTeam { civilization_id } => u8::from(civilization_id) + 10,
64        }
65    }
66}
67
68/// Kinds of tech tree nodes.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum TechTreeType {
71    None = 0,
72    Age = 1,
73    Unit = 2,
74    UnitUpgrade = 3,
75    Research = 4,
76    BuildingTech = 5,
77    BuildingNonTech = 6,
78    UniqueUnit = 7,
79}
80
81impl std::default::Default for TechTreeType {
82    fn default() -> Self {
83        Self::None
84    }
85}
86
87#[derive(Debug, Clone, Copy, thiserror::Error)]
88#[error("invalid tech tree node type {} (must be 0-7)", .0)]
89pub struct ParseTechTreeTypeError(i32);
90
91impl TryFrom<i32> for TechTreeType {
92    type Error = ParseTechTreeTypeError;
93
94    fn try_from(n: i32) -> std::result::Result<Self, Self::Error> {
95        match n {
96            0 => Ok(Self::None),
97            1 => Ok(Self::Age),
98            2 => Ok(Self::Unit),
99            3 => Ok(Self::UnitUpgrade),
100            4 => Ok(Self::Research),
101            5 => Ok(Self::BuildingTech),
102            6 => Ok(Self::BuildingNonTech),
103            7 => Ok(Self::UniqueUnit),
104            n => Err(ParseTechTreeTypeError(n)),
105        }
106    }
107}
108
109impl From<TechTreeType> for u32 {
110    fn from(ty: TechTreeType) -> Self {
111        ty as u32
112    }
113}
114
115impl From<TechTreeType> for i32 {
116    fn from(ty: TechTreeType) -> Self {
117        ty as i32
118    }
119}
120
121#[derive(Debug, Default, Clone)]
122pub struct TechTree {
123    pub ages: Vec<TechTreeAge>,
124    pub buildings: Vec<TechTreeBuilding>,
125    pub units: Vec<TechTreeUnit>,
126    pub techs: Vec<TechTreeTech>,
127    num_groups: i32,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum TechTreeDependency {
132    /// A dependency on an age being researched.
133    ///
134    /// This can typically only be 0-4, for Dark Age through Post-Imperial Age, or up to 5 in mods
135    /// that add a new age. However the dat file format has space for 32 bits, and some data files
136    /// in the wild contain incorrect data with much higher "age" IDs, so we have to follow suit.
137    Age(i32),
138    /// A dependency on a building.
139    Building(UnitTypeID),
140    /// A dependency on a unit.
141    Unit(UnitTypeID),
142    /// A dependency on a research/tech.
143    Research(TechID),
144}
145
146impl TechTreeDependency {
147    fn dependency_type(&self) -> TechTreeDependencyType {
148        match self {
149            Self::Age(_) => TechTreeDependencyType::Age,
150            Self::Building(_) => TechTreeDependencyType::Building,
151            Self::Unit(_) => TechTreeDependencyType::Unit,
152            Self::Research(_) => TechTreeDependencyType::Research,
153        }
154    }
155
156    fn raw_id(&self) -> i32 {
157        match *self {
158            Self::Age(id) => id.into(),
159            Self::Building(id) => id.into(),
160            Self::Unit(id) => id.into(),
161            Self::Research(id) => {
162                let id: u16 = id.into();
163                id as i32
164            }
165        }
166    }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170enum TechTreeDependencyType {
171    Age = 0,
172    Building = 1,
173    Unit = 2,
174    Research = 3,
175}
176
177#[derive(Debug, Clone, Copy, thiserror::Error)]
178#[error("invalid tech tree dependency type {} (must be 0-3)", .0)]
179pub struct ParseTechTreeDependencyTypeError(i32);
180
181impl TryFrom<i32> for TechTreeDependencyType {
182    type Error = ParseTechTreeDependencyTypeError;
183
184    fn try_from(n: i32) -> std::result::Result<Self, Self::Error> {
185        match n {
186            0 => Ok(Self::Age),
187            1 => Ok(Self::Building),
188            2 => Ok(Self::Unit),
189            3 => Ok(Self::Research),
190            n => Err(ParseTechTreeDependencyTypeError(n)),
191        }
192    }
193}
194
195impl Into<i32> for TechTreeDependencyType {
196    fn into(self) -> i32 {
197        self as i32
198    }
199}
200
201#[derive(Debug, Default, Clone)]
202pub struct TechTreeDependencies(ArrayVec<[TechTreeDependency; 10]>);
203
204#[derive(Debug, Default, Clone)]
205pub struct TechTreeAge {
206    age_id: i32,
207    status: TechTreeStatus,
208    node_type: TechTreeType,
209    /// The buildings that become available in this age.
210    pub dependent_buildings: Vec<UnitTypeID>,
211    /// The units that become available in this age.
212    pub dependent_units: Vec<UnitTypeID>,
213    /// The techs that become available in this age.
214    pub dependent_techs: Vec<TechID>,
215    prerequisites: TechTreeDependencies,
216    building_levels: u8,
217    buildings_per_zone: [u8; 10],
218    group_length_per_zone: [u8; 10],
219    max_age_length: u8,
220}
221
222#[derive(Debug, Default, Clone)]
223pub struct TechTreeBuilding {
224    building_id: UnitTypeID,
225    status: TechTreeStatus,
226    node_type: TechTreeType,
227    /// The tech ID that makes this building available. `None` if the building is available without
228    /// requiring any techs.
229    pub depends_tech_id: Option<TechID>,
230    /// The buildings that become available by building this building.
231    pub dependent_buildings: Vec<UnitTypeID>,
232    /// The units that become available by building this building.
233    pub dependent_units: Vec<UnitTypeID>,
234    /// The techs that become available by building this building.
235    pub dependent_techs: Vec<TechID>,
236    prerequisites: TechTreeDependencies,
237    /// ?
238    level_no: u8,
239    /// Total units and techs at this building by age, including ones that require research to
240    /// unlock.
241    total_children_by_age: [u8; 5],
242    /// Initial units and techs at this building by age, excluding ones that require research to
243    /// unlock.
244    initial_children_by_age: [u8; 5],
245}
246
247#[derive(Debug, Default, Clone)]
248pub struct TechTreeUnit {
249    unit_id: UnitTypeID,
250    status: TechTreeStatus,
251    node_type: TechTreeType,
252    depends_tech_id: Option<TechID>,
253    building: UnitTypeID,
254    requires_tech_id: Option<TechID>,
255    dependent_units: Vec<UnitTypeID>,
256    prerequisites: TechTreeDependencies,
257    group_id: i32,
258    level_no: i32,
259}
260
261#[derive(Debug, Default, Clone)]
262pub struct TechTreeTech {
263    tech_id: TechID,
264    status: TechTreeStatus,
265    node_type: TechTreeType,
266    building: UnitTypeID,
267    dependent_buildings: Vec<UnitTypeID>,
268    dependent_units: Vec<UnitTypeID>,
269    dependent_techs: Vec<TechID>,
270    prerequisites: TechTreeDependencies,
271    group_id: i32,
272    level_no: i32,
273}
274
275impl TechTree {
276    pub fn read_from(mut input: impl Read) -> Result<Self> {
277        let num_ages = input.read_u8()?;
278        let num_buildings = input.read_u8()?;
279        let num_units = input.read_u8()?;
280        let num_techs = input.read_u8()?;
281        let num_groups = input.read_i32::<LE>()?;
282
283        let mut ages = vec![];
284        for _ in 0..num_ages {
285            ages.push(TechTreeAge::read_from(&mut input)?);
286        }
287
288        let mut buildings = vec![];
289        for _ in 0..num_buildings {
290            buildings.push(TechTreeBuilding::read_from(&mut input)?);
291        }
292
293        let mut units = vec![];
294        for _ in 0..num_units {
295            units.push(TechTreeUnit::read_from(&mut input)?);
296        }
297
298        let mut techs = vec![];
299        for _ in 0..num_techs {
300            techs.push(TechTreeTech::read_from(&mut input)?);
301        }
302
303        Ok(Self {
304            ages,
305            buildings,
306            units,
307            techs,
308            num_groups,
309        })
310    }
311
312    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
313        output.write_u8(self.ages.len() as u8)?;
314        output.write_u8(self.buildings.len() as u8)?;
315        output.write_u8(self.units.len() as u8)?;
316        output.write_u8(self.techs.len() as u8)?;
317        output.write_i32::<LE>(self.num_groups)?;
318
319        for age in &self.ages {
320            age.write_to(&mut output)?;
321        }
322        for building in &self.buildings {
323            building.write_to(&mut output)?;
324        }
325        for unit in &self.units {
326            unit.write_to(&mut output)?;
327        }
328        for tech in &self.techs {
329            tech.write_to(&mut output)?;
330        }
331
332        Ok(())
333    }
334}
335
336impl TechTreeDependencies {
337    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
338        let mut deps = Self::default();
339        let num = input.read_u8()?;
340        let _padding = input.read_u8()?;
341        let _padding = input.read_u8()?;
342        let _padding = input.read_u8()?;
343
344        let mut ids = [-1i32; 10];
345        for id in ids.iter_mut() {
346            *id = input.read_i32::<LE>()?;
347        }
348        let mut types = [-1i32; 10];
349        for ty in types.iter_mut() {
350            *ty = input.read_i32::<LE>()?;
351        }
352
353        for (&id, &ty) in ids.iter().zip(types.iter()).take(num as usize) {
354            let dep_type: TechTreeDependencyType = ty.try_into().map_err(invalid_data)?;
355            deps.0.push(match dep_type {
356                TechTreeDependencyType::Age => {
357                    TechTreeDependency::Age(id.try_into().map_err(invalid_data)?)
358                }
359                TechTreeDependencyType::Building => {
360                    TechTreeDependency::Building(id.try_into().map_err(invalid_data)?)
361                }
362                TechTreeDependencyType::Unit => {
363                    TechTreeDependency::Unit(id.try_into().map_err(invalid_data)?)
364                }
365                TechTreeDependencyType::Research => {
366                    TechTreeDependency::Research(id.try_into().map_err(invalid_data)?)
367                }
368            });
369        }
370
371        Ok(deps)
372    }
373
374    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
375        assert!(self.len() <= 10);
376        output.write_u8(self.len() as u8)?;
377        output.write_all(&[0, 0, 0])?;
378        for i in 0..10 {
379            output.write_i32::<LE>(self.0.get(i).map(TechTreeDependency::raw_id).unwrap_or(0))?;
380        }
381        for i in 0..10 {
382            output.write_i32::<LE>(
383                self.0
384                    .get(i)
385                    .map(TechTreeDependency::dependency_type)
386                    .map(Into::into)
387                    .unwrap_or(0),
388            )?;
389        }
390        Ok(())
391    }
392
393    pub fn len(&self) -> usize {
394        self.0.len()
395    }
396
397    pub fn is_empty(&self) -> bool {
398        self.0.is_empty()
399    }
400
401    pub fn iter(&self) -> impl Iterator<Item = &TechTreeDependency> {
402        self.0.iter()
403    }
404}
405
406/// Read a list of dependent "Thing" IDs for a tech tree node entry. The "Thing"
407/// may be unit, building, tech IDs.
408fn read_dependents<R, T>(input: &mut R) -> Result<Vec<T>>
409where
410    R: Read,
411    T: TryFrom<i32>,
412    // so `invalid_data` can convert it
413    T::Error: std::error::Error + Send + Sync + 'static,
414{
415    let num = input.read_u8()?;
416    let mut list = vec![];
417    for _ in 0..num {
418        list.push(input.read_i32::<LE>()?.try_into().map_err(invalid_data)?);
419    }
420    Ok(list)
421}
422
423impl TechTreeAge {
424    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
425        let mut age = Self::default();
426        age.age_id = input.read_i32::<LE>()?;
427        age.status = input.read_u8()?.try_into().map_err(invalid_data)?;
428        age.dependent_buildings = read_dependents(input)?;
429        age.dependent_units = read_dependents(input)?;
430        age.dependent_techs = read_dependents(input)?;
431        age.prerequisites = TechTreeDependencies::read_from(input)?;
432        age.building_levels = input.read_u8()?;
433        assert!(age.building_levels <= 10);
434        for building in age.buildings_per_zone.iter_mut() {
435            *building = input.read_u8()?;
436        }
437        for group_length in age.group_length_per_zone.iter_mut() {
438            *group_length = input.read_u8()?;
439        }
440        age.max_age_length = input.read_u8()?;
441        age.node_type = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
442        assert_eq!(age.node_type, TechTreeType::Age);
443        Ok(age)
444    }
445
446    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
447        output.write_i32::<LE>(self.age_id)?;
448        output.write_u8(self.status.into())?;
449        output.write_u8(self.dependent_buildings.len() as u8)?;
450        for dependent in &self.dependent_buildings {
451            output.write_u32::<LE>((*dependent).into())?;
452        }
453        output.write_u8(self.dependent_units.len() as u8)?;
454        for dependent in &self.dependent_units {
455            output.write_u32::<LE>((*dependent).into())?;
456        }
457        output.write_u8(self.dependent_techs.len() as u8)?;
458        for dependent in &self.dependent_techs {
459            output.write_u32::<LE>(u16::from(*dependent) as u32)?;
460        }
461        self.prerequisites.write_to(&mut output)?;
462        output.write_u8(self.building_levels)?;
463        output.write_all(&self.buildings_per_zone)?;
464        output.write_all(&self.group_length_per_zone)?;
465        output.write_u8(self.max_age_length)?;
466        output.write_u32::<LE>(self.node_type.into())?;
467        Ok(())
468    }
469}
470
471impl TechTreeBuilding {
472    pub fn read_from(mut input: impl Read) -> Result<Self> {
473        let mut building = Self::default();
474        building.building_id = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
475        building.status = input.read_u8()?.try_into().map_err(invalid_data)?;
476        building.dependent_buildings = read_dependents(&mut input)?;
477        building.dependent_units = read_dependents(&mut input)?;
478        building.dependent_techs = read_dependents(&mut input)?;
479        building.prerequisites = TechTreeDependencies::read_from(&mut input)?;
480        building.level_no = input.read_u8()?;
481        for children in building.total_children_by_age.iter_mut() {
482            *children = input.read_u8()?;
483        }
484        for children in building.initial_children_by_age.iter_mut() {
485            *children = input.read_u8()?;
486        }
487        building.node_type = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
488        building.depends_tech_id = read_opt_u32(&mut input)?;
489        Ok(building)
490    }
491
492    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
493        output.write_u32::<LE>(self.building_id.into())?;
494        output.write_u8(self.status.into())?;
495        output.write_u8(self.dependent_buildings.len() as u8)?;
496        for dependent in &self.dependent_buildings {
497            output.write_u32::<LE>((*dependent).into())?;
498        }
499        output.write_u8(self.dependent_units.len() as u8)?;
500        for dependent in &self.dependent_units {
501            output.write_u32::<LE>((*dependent).into())?;
502        }
503        output.write_u8(self.dependent_techs.len() as u8)?;
504        for dependent in &self.dependent_techs {
505            output.write_u32::<LE>(u16::from(*dependent) as u32)?;
506        }
507        self.prerequisites.write_to(&mut output)?;
508        output.write_u8(self.level_no)?;
509        output.write_all(&self.total_children_by_age)?;
510        output.write_all(&self.initial_children_by_age)?;
511        output.write_u32::<LE>(self.node_type.into())?;
512        output.write_u32::<LE>(
513            self.depends_tech_id
514                .map(|tech_id| u16::from(tech_id) as u32)
515                .unwrap_or(0xFFFF_FFFF),
516        )?;
517        Ok(())
518    }
519}
520
521impl TechTreeUnit {
522    pub fn read_from(mut input: impl Read) -> Result<Self> {
523        let mut unit = Self::default();
524        unit.unit_id = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
525        unit.status = input.read_u8()?.try_into().map_err(invalid_data)?;
526        unit.building = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
527        unit.prerequisites = TechTreeDependencies::read_from(&mut input)?;
528        unit.group_id = input.read_i32::<LE>()?;
529        unit.dependent_units = read_dependents(&mut input)?;
530        unit.level_no = input.read_i32::<LE>()?;
531        unit.requires_tech_id = read_opt_u32(&mut input)?;
532        unit.node_type = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
533        unit.depends_tech_id = read_opt_u32(&mut input)?;
534        Ok(unit)
535    }
536
537    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
538        output.write_u32::<LE>(u16::from(self.unit_id).into())?;
539        output.write_u8(self.status.into())?;
540        output.write_u32::<LE>(self.building.into())?;
541        self.prerequisites.write_to(&mut output)?;
542        output.write_i32::<LE>(self.group_id)?;
543        output.write_u8(self.dependent_units.len() as u8)?;
544        for dependent in &self.dependent_units {
545            output.write_u32::<LE>((*dependent).into())?;
546        }
547        output.write_i32::<LE>(self.level_no)?;
548        output.write_u32::<LE>(
549            self.requires_tech_id
550                .map(|tech_id| u16::from(tech_id) as u32)
551                .unwrap_or(0xFFFF_FFFF),
552        )?;
553        output.write_u32::<LE>(self.node_type.into())?;
554        output.write_u32::<LE>(
555            self.depends_tech_id
556                .map(|tech_id| u16::from(tech_id) as u32)
557                .unwrap_or(0xFFFF_FFFF),
558        )?;
559        Ok(())
560    }
561}
562
563impl TechTreeTech {
564    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
565        let mut tech = Self::default();
566        tech.tech_id = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
567        tech.status = input.read_u8()?.try_into().map_err(invalid_data)?;
568        tech.building = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
569        tech.dependent_buildings = read_dependents(input)?;
570        tech.dependent_units = read_dependents(input)?;
571        tech.dependent_techs = read_dependents(input)?;
572        tech.prerequisites = TechTreeDependencies::read_from(input)?;
573        tech.group_id = input.read_i32::<LE>()?;
574        tech.level_no = input.read_i32::<LE>()?;
575        tech.node_type = input.read_i32::<LE>()?.try_into().map_err(invalid_data)?;
576        Ok(tech)
577    }
578
579    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
580        output.write_u32::<LE>(u16::from(self.tech_id).into())?;
581        output.write_u8(self.status.into())?;
582        output.write_u32::<LE>(self.building.into())?;
583        output.write_u8(self.dependent_buildings.len() as u8)?;
584        for dependent in &self.dependent_buildings {
585            output.write_u32::<LE>((*dependent).into())?;
586        }
587        output.write_u8(self.dependent_units.len() as u8)?;
588        for dependent in &self.dependent_units {
589            output.write_u32::<LE>((*dependent).into())?;
590        }
591        output.write_u8(self.dependent_techs.len() as u8)?;
592        for dependent in &self.dependent_techs {
593            output.write_u32::<LE>(u16::from(*dependent) as u32)?;
594        }
595        self.prerequisites.write_to(&mut output)?;
596        output.write_i32::<LE>(self.group_id)?;
597        output.write_i32::<LE>(self.level_no)?;
598        output.write_u32::<LE>(self.node_type.into())?;
599        Ok(())
600    }
601}
602
603fn invalid_data<E: std::error::Error + Sized + Send + Sync + 'static>(err: E) -> io::Error {
604    io::Error::new(io::ErrorKind::InvalidData, err)
605}