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 pub attribute: ResourceUsageType,
35 pub amount: i16,
37 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 Food = 0,
55 Wood = 1,
57 Stone = 2,
59 Gold = 3,
61 Pop = 4,
63 Free = 214,
65 DecreaseSharedUnitCount = 215,
67 TownCenter = 218,
69 TeamBonusCounter,
71 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 assert_that(&dat_file.game_version.game_version).is_equal_to("VER 7.4\0".to_string());
175
176 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 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 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 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 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}