1use crate::sound::SoundID;
2use crate::sprite::{GraphicID, SpriteID};
3use crate::unit_type::UnitTypeID;
4use crate::FileVersion;
5use arrayvec::ArrayString;
6use byteorder::{ReadBytesExt, WriteBytesExt, LE};
7use genie_support::{
8 fallible_try_from, fallible_try_into, infallible_try_into, read_opt_u16, read_opt_u32, MapInto,
9};
10use std::convert::TryInto;
11use std::io::{Read, Result, Write};
12
13#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
15pub struct TerrainID(u16);
16
17impl From<u8> for TerrainID {
18 fn from(n: u8) -> Self {
19 TerrainID(n.into())
20 }
21}
22
23impl From<u16> for TerrainID {
24 fn from(n: u16) -> Self {
25 TerrainID(n)
26 }
27}
28
29impl From<TerrainID> for u16 {
30 fn from(n: TerrainID) -> Self {
31 n.0
32 }
33}
34
35impl From<TerrainID> for usize {
36 fn from(n: TerrainID) -> Self {
37 n.0.into()
38 }
39}
40
41fallible_try_into!(TerrainID, i16);
42infallible_try_into!(TerrainID, u32);
43fallible_try_from!(TerrainID, i32);
44fallible_try_from!(TerrainID, u32);
45
46type TerrainName = ArrayString<[u8; 13]>;
47
48#[derive(Debug, Default, Clone)]
49pub struct TerrainPassGraphic {
50 exit_tile_sprite: Option<SpriteID>,
51 enter_tile_sprite: Option<SpriteID>,
52 walk_tile_sprite: Option<SpriteID>,
53 walk_rate: Option<f32>,
54 replication_amount: Option<i32>,
55}
56
57#[derive(Debug, Clone)]
58pub struct TerrainRestriction {
59 passability: Vec<f32>,
60 pass_graphics: Vec<TerrainPassGraphic>,
61}
62
63#[derive(Debug, Default, Clone)]
64pub struct TileSize {
65 pub width: i16,
66 pub height: i16,
67 pub delta_z: i16,
68}
69
70#[derive(Debug, Default, Clone)]
71pub struct TerrainAnimation {
72 pub enabled: bool,
73 num_frames: i16,
74 num_pause_frames: i16,
75 frame_interval: f32,
76 replay_delay: f32,
77 frame: i16,
78 draw_frame: i16,
79 animate_last: f32,
80 frame_changed: bool,
81 drawn: bool,
82}
83
84#[derive(Debug, Default, Clone)]
85pub struct TerrainSpriteFrame {
86 pub num_frames: i16,
87 pub num_facets: i16,
88 pub frame_id: i16,
89}
90
91#[derive(Debug, Default, Clone)]
92pub struct TerrainObject {
93 pub object_id: UnitTypeID,
94 pub density: i16,
95 pub placement_flag: i8,
96}
97
98#[derive(Debug, Default, Clone)]
99pub struct Terrain {
100 pub enabled: bool,
102 random: u8,
103 name: TerrainName,
105 slp_name: TerrainName,
107 pub slp_id: Option<GraphicID>,
109 pub sound_id: Option<SoundID>,
111 wwise_sound_id: Option<u32>,
112 wwise_stop_sound_id: Option<u32>,
113 blend_priority: Option<i32>,
114 blend_mode: Option<i32>,
115 pub minimap_color_high: u8,
117 pub minimap_color_medium: u8,
119 pub minimap_color_low: u8,
121 pub minimap_color_cliff_lt: u8,
123 pub minimap_color_cliff_rt: u8,
125 pub passable_terrain_id: Option<u8>,
126 pub impassable_terrain_id: Option<u8>,
127 pub animation: TerrainAnimation,
128 pub elevation_sprites: Vec<TerrainSpriteFrame>,
129 pub terrain_id_to_draw: Option<TerrainID>,
130 rows: i16,
131 cols: i16,
132 pub borders: Vec<i16>,
133 pub terrain_objects: Vec<TerrainObject>,
134}
135
136#[derive(Debug, Default, Clone)]
137pub struct TerrainBorder {
138 pub enabled: bool,
139 random: u8,
140 name: TerrainName,
141 slp_name: TerrainName,
142 pub slp_id: Option<GraphicID>,
143 pub sound_id: Option<SoundID>,
144 pub color: (u8, u8, u8),
145 pub animation: TerrainAnimation,
146 pub frames: Vec<Vec<TerrainSpriteFrame>>,
147 draw_tile: i8,
149 pub underlay_terrain: Option<i16>,
150 pub border_style: i16,
151}
152
153impl TerrainPassGraphic {
154 pub fn read_from(mut input: impl Read, version: FileVersion) -> Result<Self> {
155 let mut pass = TerrainPassGraphic::default();
156 pass.exit_tile_sprite = read_opt_u32(&mut input)?;
157 pass.enter_tile_sprite = read_opt_u32(&mut input)?;
158 pass.walk_tile_sprite = read_opt_u32(&mut input)?;
159 if version.is_swgb() {
160 pass.walk_rate = Some(input.read_f32::<LE>()?);
161 } else {
162 pass.replication_amount = Some(input.read_i32::<LE>()?);
163 }
164 Ok(pass)
165 }
166
167 pub fn write_to(&self, mut output: impl Write, version: FileVersion) -> Result<()> {
169 output.write_i32::<LE>(self.exit_tile_sprite.map_into().unwrap_or(-1))?;
170 output.write_i32::<LE>(self.enter_tile_sprite.map_into().unwrap_or(-1))?;
171 output.write_i32::<LE>(self.walk_tile_sprite.map_into().unwrap_or(-1))?;
172 if version.is_swgb() {
174 output.write_f32::<LE>(self.walk_rate.unwrap_or(0.0))?;
175 } else {
176 output.write_i32::<LE>(self.replication_amount.unwrap_or(-1))?;
177 }
178 Ok(())
179 }
180}
181
182impl TerrainRestriction {
183 pub fn read_from(
184 mut input: impl Read,
185 version: FileVersion,
186 num_terrains: u16,
187 ) -> Result<Self> {
188 let mut passability = vec![0.0; num_terrains as usize];
189 for value in passability.iter_mut() {
190 *value = input.read_f32::<LE>()?;
191 }
192
193 let mut pass_graphics = Vec::with_capacity(num_terrains as usize);
195 for _ in 0..num_terrains {
196 pass_graphics.push(TerrainPassGraphic::read_from(&mut input, version)?);
197 }
198
199 Ok(Self {
200 passability,
201 pass_graphics,
202 })
203 }
204
205 pub fn write_to(
207 &self,
208 mut output: impl Write,
209 version: FileVersion,
210 num_terrains: u16,
211 ) -> Result<()> {
212 assert_eq!(self.passability.len(), num_terrains.into());
213 assert_eq!(self.pass_graphics.len(), num_terrains.into());
214 for value in &self.passability {
215 output.write_f32::<LE>(*value)?;
216 }
217 for graphic in &self.pass_graphics {
218 graphic.write_to(&mut output, version)?;
219 }
220 Ok(())
221 }
222}
223
224impl TileSize {
225 pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
226 let width = input.read_i16::<LE>()?;
227 let height = input.read_i16::<LE>()?;
228 let delta_z = input.read_i16::<LE>()?;
229 Ok(Self {
230 width,
231 height,
232 delta_z,
233 })
234 }
235
236 pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
238 output.write_i16::<LE>(self.width)?;
239 output.write_i16::<LE>(self.height)?;
240 output.write_i16::<LE>(self.delta_z)?;
241 Ok(())
242 }
243}
244
245impl TerrainAnimation {
246 pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
247 let mut anim = TerrainAnimation::default();
248 anim.enabled = input.read_u8()? != 0;
249 anim.num_frames = input.read_i16::<LE>()?;
250 anim.num_pause_frames = input.read_i16::<LE>()?;
251 anim.frame_interval = input.read_f32::<LE>()?;
252 anim.replay_delay = input.read_f32::<LE>()?;
253 anim.frame = input.read_i16::<LE>()?;
254 anim.draw_frame = input.read_i16::<LE>()?;
255 anim.animate_last = input.read_f32::<LE>()?;
256 anim.frame_changed = input.read_u8()? != 0;
257 anim.drawn = input.read_u8()? != 0;
258 Ok(anim)
259 }
260
261 pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
263 output.write_u8(if self.enabled { 1 } else { 0 })?;
264 output.write_i16::<LE>(self.num_frames)?;
265 output.write_i16::<LE>(self.num_pause_frames)?;
266 output.write_f32::<LE>(self.frame_interval)?;
267 output.write_f32::<LE>(self.replay_delay)?;
268 output.write_i16::<LE>(self.frame)?;
269 output.write_i16::<LE>(self.draw_frame)?;
270 output.write_f32::<LE>(self.animate_last)?;
271 output.write_u8(if self.frame_changed { 1 } else { 0 })?;
272 output.write_u8(if self.drawn { 1 } else { 0 })?;
273 Ok(())
274 }
275}
276
277impl TerrainSpriteFrame {
278 pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
279 let num_frames = input.read_i16::<LE>()?;
280 let num_facets = input.read_i16::<LE>()?;
281 let frame_id = input.read_i16::<LE>()?;
282 Ok(Self {
283 num_frames,
284 num_facets,
285 frame_id,
286 })
287 }
288
289 pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
291 output.write_i16::<LE>(self.num_frames)?;
292 output.write_i16::<LE>(self.num_facets)?;
293 output.write_i16::<LE>(self.frame_id)?;
294 Ok(())
295 }
296}
297
298impl Terrain {
299 pub fn name(&self) -> &str {
301 self.name.as_str()
302 }
303
304 pub fn read_from(
306 mut input: impl Read,
307 version: FileVersion,
308 num_terrains: u16,
309 ) -> Result<Self> {
310 let mut terrain = Terrain::default();
311 terrain.enabled = input.read_u8()? != 0;
312 terrain.random = input.read_u8()?;
313 read_terrain_name(&mut input, &mut terrain.name)?;
314 read_terrain_name(&mut input, &mut terrain.slp_name)?;
315 terrain.slp_id = read_opt_u32(&mut input)?;
317 let _slp_pointer = input.read_i32::<LE>()?;
318 terrain.sound_id = read_opt_u32(&mut input)?;
319 if version.is_de2() {
320 terrain.wwise_sound_id = read_opt_u32(&mut input)?;
321 terrain.wwise_stop_sound_id = read_opt_u32(&mut input)?;
322 } else {
323 terrain.blend_priority = Some(input.read_i32::<LE>()?);
324 terrain.blend_mode = Some(input.read_i32::<LE>()?);
325 }
326 terrain.minimap_color_high = input.read_u8()?;
327 terrain.minimap_color_medium = input.read_u8()?;
328 terrain.minimap_color_low = input.read_u8()?;
329 terrain.minimap_color_cliff_lt = input.read_u8()?;
330 terrain.minimap_color_cliff_rt = input.read_u8()?;
331 terrain.passable_terrain_id = match input.read_u8()? {
332 0xFF => None,
333 id => Some(id),
334 };
335 terrain.impassable_terrain_id = match input.read_u8()? {
336 0xFF => None,
337 id => Some(id),
338 };
339 terrain.animation = TerrainAnimation::read_from(&mut input)?;
340 for _ in 0..19 {
341 terrain
342 .elevation_sprites
343 .push(TerrainSpriteFrame::read_from(&mut input)?);
344 }
345 terrain.terrain_id_to_draw = read_opt_u16(&mut input)?;
346 terrain.rows = input.read_i16::<LE>()?;
347 terrain.cols = input.read_i16::<LE>()?;
348 for _ in 0..num_terrains {
349 terrain.borders.push(input.read_i16::<LE>()?);
350 }
351
352 let mut terrain_objects = vec![TerrainObject::default(); 30];
353 for object in terrain_objects.iter_mut() {
354 object.object_id = input.read_u16::<LE>()?.into();
355 }
356 for object in terrain_objects.iter_mut() {
357 object.density = input.read_i16::<LE>()?;
358 }
359 for object in terrain_objects.iter_mut() {
360 object.placement_flag = input.read_i8()?;
361 }
362
363 let _num_terrain_objects = input.read_u16::<LE>()?;
364 terrain.terrain_objects = terrain_objects;
367
368 let _padding = input.read_u16::<LE>()?;
369
370 Ok(terrain)
371 }
372
373 pub fn write_to<W: Write>(
375 &self,
376 output: &mut W,
377 _version: FileVersion,
378 num_terrains: u16,
379 ) -> Result<()> {
380 assert_eq!(self.borders.len(), num_terrains as usize);
381 output.write_u8(if self.enabled { 1 } else { 0 })?;
382 output.write_u8(self.random)?;
383 write_terrain_name(output, &self.name)?;
384 write_terrain_name(output, &self.slp_name)?;
385 output.write_i32::<LE>(self.slp_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
386 output.write_i32::<LE>(0)?; output.write_i32::<LE>(self.sound_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
388 output.write_i32::<LE>(self.blend_priority.unwrap_or(-1))?;
389 output.write_i32::<LE>(self.blend_mode.unwrap_or(-1))?;
390 output.write_u8(self.minimap_color_high)?;
391 output.write_u8(self.minimap_color_medium)?;
392 output.write_u8(self.minimap_color_low)?;
393 output.write_u8(self.minimap_color_cliff_lt)?;
394 output.write_u8(self.minimap_color_cliff_rt)?;
395 output.write_u8(self.passable_terrain_id.unwrap_or(0xFF))?;
396 output.write_u8(self.impassable_terrain_id.unwrap_or(0xFF))?;
397 self.animation.write_to(output)?;
398 for frame in &self.elevation_sprites {
399 frame.write_to(output)?;
400 }
401 output.write_i16::<LE>(
402 self.terrain_id_to_draw
403 .map(|id| id.try_into().unwrap())
404 .unwrap_or(-1),
405 )?;
406 output.write_i16::<LE>(self.rows)?;
407 output.write_i16::<LE>(self.cols)?;
408 for border in &self.borders {
409 output.write_i16::<LE>(*border)?;
410 }
411
412 for index in 0..30 {
413 if let Some(object) = self.terrain_objects.get(index) {
414 output.write_u16::<LE>(object.object_id.into())?;
415 } else {
416 output.write_u16::<LE>(0)?;
417 }
418 }
419 for index in 0..30 {
420 if let Some(object) = self.terrain_objects.get(index) {
421 output.write_i16::<LE>(object.density)?;
422 } else {
423 output.write_i16::<LE>(0)?;
424 }
425 }
426 for index in 0..30 {
427 if let Some(object) = self.terrain_objects.get(index) {
428 output.write_i8(object.placement_flag)?;
429 } else {
430 output.write_i8(0)?;
431 }
432 }
433 output.write_u16::<LE>(self.terrain_objects.len() as u16)?;
434
435 output.write_u16::<LE>(0)?; Ok(())
438 }
439}
440
441impl TerrainBorder {
442 pub fn read_from(mut input: impl Read) -> Result<Self> {
443 let mut border = TerrainBorder::default();
444 border.enabled = input.read_u8()? != 0;
445 border.random = input.read_u8()?;
446 read_terrain_name(&mut input, &mut border.name)?;
447 read_terrain_name(&mut input, &mut border.slp_name)?;
448 border.slp_id = read_opt_u32(&mut input)?;
449 let _slp_pointer = input.read_i32::<LE>()?;
450 border.sound_id = read_opt_u32(&mut input)?;
451 border.color = (input.read_u8()?, input.read_u8()?, input.read_u8()?);
452 border.animation = TerrainAnimation::read_from(&mut input)?;
453 for _ in 0..19 {
454 let mut frames_list = vec![TerrainSpriteFrame::default(); 12];
455 for frame in frames_list.iter_mut() {
456 *frame = TerrainSpriteFrame::read_from(&mut input)?;
457 }
458 border.frames.push(frames_list);
459 }
460
461 border.draw_tile = input.read_i8()?;
462 input.read_u8()?;
464 border.underlay_terrain = read_opt_u16(&mut input)?;
465 border.border_style = input.read_i16::<LE>()?;
466
467 Ok(border)
468 }
469
470 pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
472 output.write_u8(if self.enabled { 1 } else { 0 })?;
473 output.write_u8(self.random)?;
474 write_terrain_name(output, &self.name)?;
475 write_terrain_name(output, &self.slp_name)?;
476 output.write_i32::<LE>(self.slp_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
477 output.write_i32::<LE>(0)?; output.write_i32::<LE>(self.sound_id.map(|id| id.try_into().unwrap()).unwrap_or(-1))?;
479 output.write_u8(self.color.0)?;
480 output.write_u8(self.color.1)?;
481 output.write_u8(self.color.2)?;
482 self.animation.write_to(output)?;
483 for frames_list in &self.frames {
484 for frame in frames_list {
485 frame.write_to(output)?;
486 }
487 }
488 output.write_i8(self.draw_tile)?;
489 output.write_u8(0)?; output.write_i16::<LE>(
491 self.underlay_terrain
492 .map(|id| id.try_into().unwrap())
493 .unwrap_or(-1),
494 )?;
495 output.write_i16::<LE>(self.border_style)?;
496 Ok(())
497 }
498}
499
500fn read_terrain_name<R: Read>(input: &mut R, output: &mut TerrainName) -> Result<()> {
501 let bytes = &mut [0; 13];
502 input.read_exact(bytes)?;
503 bytes
504 .iter()
505 .cloned()
506 .take_while(|b| *b != 0)
507 .map(char::from)
508 .for_each(|c| output.push(c));
509 Ok(())
510}
511
512fn write_terrain_name<W: Write>(output: &mut W, name: &TerrainName) -> Result<()> {
513 let bytes = &mut [0; 13];
514 (&mut bytes[..name.len()]).copy_from_slice(name.as_bytes());
515 output.write_all(bytes)?;
516 Ok(())
517}