aoe_djin/dat/
mod.rs

1mod zlib;
2
3use crate::dat::civilization::Civilizations;
4use crate::dat::color::ColorTable;
5use crate::dat::effect::Effects;
6use crate::dat::random_map::RandomMap;
7use crate::dat::sound::SoundTable;
8use crate::dat::sprite::SpriteTable;
9use crate::dat::tech::Techs;
10use crate::dat::tech_tree::TechTree;
11use crate::dat::terrain::{TerrainHeader, TerrainRestrictions};
12use crate::dat::terrain_block::TerrainBlock;
13use crate::dat::unit::Units;
14use djin_protocol::Parcel;
15use eyre::Result;
16use std::path::Path;
17
18pub mod civilization;
19pub mod color;
20pub mod common;
21pub mod effect;
22pub mod random_map;
23pub mod sound;
24pub mod sprite;
25pub mod tech;
26pub mod tech_tree;
27pub mod terrain;
28pub mod terrain_block;
29pub mod unit;
30
31#[derive(Protocol, Debug, Clone, PartialEq)]
32pub struct ResourceUsage {
33    /// The kind of resource to give or take
34    pub attribute: ResourceUsageType,
35    /// The amount give or take
36    pub amount: i16,
37    /// How and when this is counted
38    pub flag: ResourceUsageTrigger,
39}
40
41#[derive(Protocol, Debug, Clone, PartialEq, PartialOrd)]
42#[protocol(discriminant = "integer")]
43#[repr(u16)]
44pub enum ResourceUsageTrigger {
45    OnCreate = 0,
46    OnQueue = 1,
47}
48
49#[derive(Protocol, Debug, Clone, PartialEq, PartialOrd)]
50#[protocol(discriminant = "integer")]
51#[repr(u16)]
52pub enum ResourceUsageType {
53    /// Take or give an amount of food to the player
54    Food = 0,
55    /// Take or give an amount of wood to the player
56    Wood = 1,
57    /// Take or give an amount of stone to the player
58    Stone = 2,
59    /// Take or give an amount of gold to the player
60    Gold = 3,
61    /// Take or give an amount of population to the player
62    Pop = 4,
63    /// A free unit (Elite Kipchak)
64    Free = 214,
65    /// Two units in the game use this attribute : Elite Kipchak and Urus Khan (migth be creatable on some campaingn scenario)
66    DecreaseSharedUnitCount = 215,
67    /// A town center slot either in dark age (UNKOWN RTWC1X) or in feudal age for Cumans (UNKOWN RTWC2X)
68    TownCenter = 218,
69    /// Also for Elite Kipchak and Urus Khan, decrease the number of available unit (10 For Kipchak)
70    TeamBonusCounter,
71    // We cannot use i16 as enum discriminant but this is actually -1
72    /// This can be ignored
73    None = 65535,
74}
75
76pub struct DatFile {
77    pub game_version: GameVersion,
78    pub terrain_header: TerrainHeader,
79    pub terrain_restrictions: TerrainRestrictions,
80    pub color_table: ColorTable,
81    pub sound_table: SoundTable,
82    pub sprite_table: SpriteTable,
83    pub terrain_block: TerrainBlock,
84    pub random_map: RandomMap,
85    pub effect_table: Effects,
86    pub unit_table: Units,
87    pub civilization_table: Civilizations,
88    pub tech_table: Techs,
89    pub misc: Misc,
90    pub tech_tree: TechTree,
91}
92
93#[derive(Protocol, Debug, Clone, PartialEq)]
94pub struct GameVersion {
95    #[protocol(fixed_length(8))]
96    pub game_version: String,
97}
98
99#[derive(Protocol, Debug, Clone, PartialEq)]
100pub struct Misc {
101    time_slice: u32,
102    unit_kill_rate: u32,
103    unit_kill_total: u32,
104    unit_hit_point_rate: u32,
105    unit_hit_point_total: u32,
106    razing_kill_rate: u32,
107    razing_kill_total: u32,
108}
109
110impl DatFile {
111    pub fn from_file<S: AsRef<Path> + ?Sized>(path: &S) -> Result<DatFile> {
112        let mut buf = zlib::decompress(path)?;
113
114        let settings = djin_protocol::Settings {
115            byte_order: djin_protocol::ByteOrder::LittleEndian,
116        };
117
118        let game_version = GameVersion::read(&mut buf, &settings).expect("Read error");
119        let terrain_header = TerrainHeader::read(&mut buf, &settings).expect("Read error");
120        let terrain_restrictions = TerrainRestrictions::read(
121            &mut buf,
122            terrain_header.terrain_restriction_size as usize,
123            terrain_header.restriction_size as usize,
124            &settings,
125        );
126        let color_table = ColorTable::read(&mut buf, &settings).expect("Error reading color_table");
127        let sound_table = SoundTable::read(&mut buf, &settings).expect("Error reading sound_table");
128        let sprite_table =
129            SpriteTable::read(&mut buf, &settings).expect("Error reading sprite_table");
130        let terrain_block =
131            TerrainBlock::read(&mut buf, &settings).expect("Error reading terrain_block");
132        let random_map = RandomMap::read(&mut buf, &settings).expect("Error reading random_map");
133        let effect_table = Effects::read(&mut buf, &settings).expect("Error reading effect_table");
134        let unit_table = Units::read(&mut buf, &settings).expect("Error reading unit_table");
135        let civilization_table =
136            Civilizations::read(&mut buf, &settings).expect("Error reading civilization_table");
137        let tech_table = Techs::read(&mut buf, &settings).expect("Error reading tech_table");
138        let misc = Misc::read(&mut buf, &settings).expect("Error reading misc");
139        let tech_tree = TechTree::read(&mut buf, &settings).expect("Error reading tech_tree");
140
141        Ok(DatFile {
142            game_version,
143            terrain_header,
144            terrain_restrictions,
145            color_table,
146            sound_table,
147            sprite_table,
148            terrain_block,
149            random_map,
150            effect_table,
151            unit_table,
152            civilization_table,
153            tech_table,
154            misc,
155            tech_tree,
156        })
157    }
158}
159
160#[cfg(test)]
161mod test {
162    use crate::dat::tech::ResourceCostType;
163    use crate::dat::tech::{ResourceCostTrigger, TechResourcesCost};
164    use crate::dat::DatFile;
165    use eyre::Result;
166    use spectral::prelude::*;
167
168    type TestResult = Result<()>;
169
170    #[test]
171    fn should_read_dat_file() -> TestResult {
172        let dat_file = DatFile::from_file("tests/game_assets/empires2_x2_p1.dat").unwrap();
173        // Version
174        assert_that(&dat_file.game_version.game_version).is_equal_to("VER 7.4\0".to_string());
175
176        // Terrain Header
177        assert_that(&dat_file.terrain_header.terrain_restriction_size).is_equal_to(31);
178        assert_that(&dat_file.terrain_header.restriction_size).is_equal_to(110);
179        assert_that(&dat_file.terrain_header.terrain_restriction_size).is_equal_to(31);
180        assert_that(&dat_file.terrain_header.terrain_tables_pointer).has_length(31);
181        assert_that(&dat_file.terrain_header.terrains_pointer).has_length(31);
182
183        // Terrain restrictions
184        assert_that(&dat_file.terrain_restrictions.inner).has_length(31);
185
186        dat_file.terrain_restrictions.inner.iter().for_each(|el| {
187            assert_that(&el.pass_graphics).has_length(110);
188            assert_that(&el.passability).has_length(110);
189        });
190
191        // Colors
192        assert_that(&dat_file.color_table.colors).has_length(16);
193        assert_that(&dat_file.sound_table.sounds).has_length(685);
194        assert_that(&dat_file.civilization_table.civilizations).has_length(38);
195
196        // Tech
197        let fletching = dat_file
198            .tech_table
199            .techs
200            .iter()
201            .find(|tech| tech.name == "Fletching")
202            .expect("Could not find fletching");
203
204        // Fletching cost 100 Food and 50 gold
205        assert_that(&fletching.research_resource_cost).contains_all_of(
206            &vec![
207                TechResourcesCost {
208                    amount: 100,
209                    flag: ResourceCostTrigger::OnQueue,
210                    resource_type: ResourceCostType::Food,
211                },
212                TechResourcesCost {
213                    amount: 50,
214                    flag: ResourceCostTrigger::OnQueue,
215                    resource_type: ResourceCostType::Gold,
216                },
217                TechResourcesCost {
218                    amount: 0,
219                    flag: ResourceCostTrigger::OnCreate,
220                    resource_type: ResourceCostType::None,
221                },
222            ]
223            .iter(),
224        );
225
226        Ok(())
227    }
228}