1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms, unreachable_pub)]
3#![deny(rustdoc::bare_urls, rustdoc::broken_intra_doc_links)]
4#![forbid(unused_must_use, unsafe_code)]
5
6use anyhow::{bail, Context};
105use basic_toml as toml;
106use log::debug;
107use mission2teegarden_b_models::AvailableCards;
108use ron::error::SpannedError;
109use serde::{
110 ser::{SerializeMap, Serializer},
111 Deserialize, Serialize
112};
113use std::{
114 f32::consts::PI,
115 ffi::OsStr,
116 fs::read_to_string,
117 io, iter,
118 path::{Path, PathBuf}
119};
120use story::Story;
121use thiserror::Error;
122use tiled::{LayerTile, LayerType, Loader, Properties};
123
124pub mod commands;
125pub mod story;
126pub mod tiles;
127use tiles::{InvalidTile, MapBaseTile, ObjectTile, Passable, PlayerTile, Tile};
128
129pub const MAP_FILE_EXTENSION: &str = "m2tb_map";
130
131struct PropertiesSerde(Properties);
133impl Serialize for PropertiesSerde {
134 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135 where
136 S: Serializer
137 {
138 let mut map = serializer.serialize_map(Some(self.0.len()))?;
139 for (key, value) in self.0.clone() {
140 match value {
141 tiled::PropertyValue::IntValue(value) => {
142 map.serialize_entry(&key, &value)
143 },
144 tiled::PropertyValue::BoolValue(value) => {
145 map.serialize_entry(&key, &value)
146 },
147 tiled::PropertyValue::FileValue(value) => {
148 map.serialize_entry(&key, &value)
149 },
150 tiled::PropertyValue::FloatValue(value) => {
151 map.serialize_entry(&key, &value)
152 },
153 tiled::PropertyValue::ColorValue(_) => Ok(()), tiled::PropertyValue::ObjectValue(value) => {
155 map.serialize_entry(&key, &value)
156 },
157 tiled::PropertyValue::StringValue(value) => {
158 map.serialize_entry(&key, &value)
159 },
160 }?;
161 }
162 map.end()
163 }
164}
165
166#[derive(Clone, Debug, Deserialize, Serialize)]
167struct MapProperties {
168 #[serde(flatten)]
169 cards: AvailableCards,
170 name: Option<String>,
171 story: Option<String>
172}
173
174#[derive(Clone, Debug, Deserialize, Serialize)]
175pub struct Player {
176 pub position: (u8, u8),
177 pub orientation: Orientation,
178 pub goal: Option<(u8, u8)>
179}
180
181#[derive(Clone, Debug, Deserialize, Serialize)]
182pub struct Map {
183 pub name: String,
184 pub width: u8,
185 pub height: u8,
186 pub base_layer: Vec<Vec<(MapBaseTile, Orientation)>>,
187 pub object_layer: Vec<Vec<Option<(ObjectTile, Orientation)>>>,
188 pub global_goal: Option<(u8, u8)>,
189 pub player_1: Player,
194 pub player_2: Option<Player>,
195 pub player_3: Option<Player>,
196 pub player_4: Option<Player>,
197 pub cards: AvailableCards,
198 #[serde(default)]
199 pub story: Story
200}
201
202#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
203pub enum Orientation {
204 #[default]
205 North,
206 South,
207 East,
208 West
209}
210
211impl Orientation {
212 pub fn rotation(&self) -> f32 {
214 match self {
215 Self::North => 0.0,
216 Self::South => PI,
217 Self::East => 0.5 * PI,
218 Self::West => 1.5 * PI
219 }
220 }
221}
222
223#[derive(Error, Debug)]
224#[error("Invalid Tile Oritation (horizontally flip: {}, vertically flip: {}, diagonally flip: {})\nKeep in mind that only rotation is supported", .filp_h, .filp_v, .filp_d)]
225pub struct InvalidOritation {
226 filp_h: bool,
228 filp_v: bool,
230 filp_d: bool
232}
233
234impl TryFrom<&LayerTile<'_>> for Orientation {
235 type Error = InvalidOritation;
236 fn try_from(value: &LayerTile<'_>) -> Result<Self, Self::Error> {
237 match (value.flip_h, value.flip_v, value.flip_d) {
238 (false, false, false) => Ok(Orientation::North),
239 (true, true, false) => Ok(Orientation::South),
240 (true, false, true) => Ok(Orientation::East),
241 (false, true, true) => Ok(Orientation::West),
242 _ => Err(InvalidOritation {
243 filp_h: value.flip_h,
244 filp_v: value.flip_v,
245 filp_d: value.flip_d
246 })
247 }
248 }
249}
250
251#[derive(Error, Debug)]
252pub enum MapError {
253 #[error("error loading file {0}")]
254 TieledError(#[from] tiled::Error),
255 #[error("map has to many layers")]
256 ToManyLayers,
257 #[error("{0}. Layer should be a {1}")]
258 WrongLayerType(usize, String),
259 #[error("{0}. Layer Infinite")]
260 InfiniteTileLayer(String),
261 #[error("Map is to widht. Max size is 255x255 tiles")]
262 ToWidth,
263 #[error("Map is to hight. Max size is 255x255 tiles")]
264 ToHight,
265 #[error("Found invalid Tile at Layes {0}: {1}")]
266 InvalidTile(usize, InvalidTile),
267 #[error("Player is missing")]
268 PlayerMissing(usize),
269 #[error("{0}")]
270 InvalidOritation(#[from] InvalidOritation),
271 #[error("Failed to load Map Properties:\n{}\n{}", .str, .err)]
272 MapProperty { str: String, err: serde_json::Error },
273 #[error("failed to read file story file {1:?}:\n{0}")]
274 IoError(io::Error, PathBuf),
275 #[error("could not prase story toml file:\n{0}")]
276 TomlError(#[from] toml::Error)
277}
278
279impl Map {
280 pub fn passable(&self, x: u8, y: u8) -> bool {
282 if x >= self.width || y >= self.height {
283 return true;
285 }
286 self.base_layer[x as usize][y as usize].0.passable()
287 && self.object_layer[x as usize][y as usize]
288 .map(|obejct| obejct.0.passable())
289 .unwrap_or(true)
290 }
291
292 pub fn from_string(str: &str) -> Result<Self, SpannedError> {
295 ron::from_str(str)
296 }
297
298 #[allow(clippy::inherent_to_string)]
300 pub fn to_string(&self) -> String {
301 ron::to_string(self).unwrap()
302 }
303
304 pub fn from_tmx(path: impl AsRef<Path>) -> Result<Self, MapError> {
305 let path = path.as_ref();
306 let map = Loader::new().load_tmx_map(path)?;
307 let width: u8 = map.width.try_into().map_err(|_| MapError::ToWidth)?;
308 let height: u8 = map.height.try_into().map_err(|_| MapError::ToHight)?;
309 let map_properties =
310 serde_json::to_string_pretty(&PropertiesSerde(map.properties.clone()))
311 .unwrap();
312 debug!("load Map Properties: {map_properties}");
313 let map_properties: MapProperties = serde_json::from_str(&map_properties)
317 .map_err(|err| MapError::MapProperty {
318 str: map_properties,
319 err
320 })?;
321 let cards = map_properties.cards;
322 let name = map_properties
323 .name
324 .unwrap_or_else(|| path.to_string_lossy().into());
325 let mut base_layer = Vec::with_capacity(height as usize);
326 let mut object_layer = Vec::with_capacity(height as usize);
327 let mut global_goal = None;
328 let mut player_1 = None;
329 let mut player_2 = None;
330 let mut player_3 = None;
331 let mut player_4 = None;
332 for (i, layer) in map.layers().enumerate() {
333 match i {
335 0 => match layer.layer_type() {
336 LayerType::Tiles(tile_layer) => {
337 for x in 0..width {
338 let mut column = Vec::with_capacity(width as usize);
339 for y in 0..height {
340 let tile_and_orientation = match tile_layer
341 .get_tile(x.into(), y.into())
342 {
343 Some(tile) => (
344 MapBaseTile::try_from(&tile).map_err(|err| {
345 MapError::InvalidTile(i, err)
346 })?,
347 Orientation::try_from(&tile)?
348 ),
349 None => (MapBaseTile::default(), Default::default())
350 };
351 column.push(tile_and_orientation);
352 }
353 base_layer.push(column);
354 }
355 },
356 _ => return Err(MapError::WrongLayerType(i, "TileLayer".to_owned()))
357 },
358 1 => match layer.layer_type() {
359 LayerType::Tiles(tile_layer) => {
360 for x in 0..width {
361 let mut column = Vec::with_capacity(width as usize);
362 for y in 0..height {
363 let tile = match tile_layer.get_tile(x.into(), y.into()) {
364 Some(tile) => Some((
365 ObjectTile::try_from(&tile).map_err(|err| {
366 MapError::InvalidTile(i, err)
367 })?,
368 Orientation::try_from(&tile)?
369 )),
370 None => None
371 };
372 column.push(tile);
373 }
374 object_layer.push(column);
375 }
376 },
377 _ => return Err(MapError::WrongLayerType(i, "TileLayer".to_owned()))
378 },
379 2 => match layer.layer_type() {
380 LayerType::Tiles(tile_layer) => {
381 let mut player1_goal = None;
382 let mut player2_goal = None;
383 let mut player3_goal = None;
384 let mut player4_goal = None;
385 for x in 0..width {
386 for y in 0..height {
387 if let Some(tile) =
388 tile_layer.get_tile(x.into(), y.into())
389 {
390 let orientation = Orientation::try_from(&tile)?;
391 let tile = PlayerTile::try_from(&tile)
392 .map_err(|err| MapError::InvalidTile(i, err))?;
393 let player = Some(Player {
394 position: (x, y),
395 orientation,
396 goal: None
397 });
398 let goal = Some((x, y));
399 match tile {
400 PlayerTile::Car1 => player_1 = player,
401 PlayerTile::Car2 => player_2 = player,
402 PlayerTile::Car3 => player_3 = player,
403 PlayerTile::Car4 => player_4 = player,
404 PlayerTile::Goal1 => player1_goal = goal,
405 PlayerTile::Goal2 => player2_goal = goal,
406 PlayerTile::Goal3 => player3_goal = goal,
407 PlayerTile::Goal4 => player4_goal = goal,
408 PlayerTile::GlobalGoal => global_goal = goal
409 }
410 }
411 }
412 }
413 player_1 = player_1.map(|mut f| {
416 f.goal = player1_goal;
417 f
418 });
419 player_2 = player_2.map(|mut f| {
420 f.goal = player2_goal;
421 f
422 });
423 player_3 = player_3.map(|mut f| {
424 f.goal = player3_goal;
425 f
426 });
427 player_4 = player_4.map(|mut f| {
428 f.goal = player4_goal;
429 f
430 });
431 },
432 _ => return Err(MapError::WrongLayerType(i, "TileLayer".to_owned()))
433 },
434 _ => return Err(MapError::ToManyLayers)
435 }
436 }
437 let player_1 = player_1.ok_or(MapError::PlayerMissing(1))?;
438 if (player_4.is_some() && player_3.is_none())
440 || (player_3.is_some() && player_2.is_none())
441 {
442 player_2.as_ref().ok_or(MapError::PlayerMissing(2))?;
443 player_3.as_ref().ok_or(MapError::PlayerMissing(3))?;
444 player_4.as_ref().ok_or(MapError::PlayerMissing(4))?;
445 }
446
447 let story: Story = if let Some(story_toml) = map_properties.story {
448 toml::from_str(&story_toml)?
449 } else {
450 Default::default()
451 };
452
453 Ok(Map {
454 name,
455 width,
456 height,
457 base_layer,
458 object_layer,
459 global_goal,
460 player_1,
461 player_2,
462 player_3,
463 player_4,
464 cards,
465 story
466 })
467 }
468
469 pub fn load_from_file<P>(path: P) -> anyhow::Result<Self>
472 where
473 P: AsRef<Path>
474 {
475 let path = path.as_ref();
476 if path.extension() == Some(OsStr::new(MAP_FILE_EXTENSION)) {
477 let file = read_to_string(path)
478 .with_context(|| format!("failed to read file {path:?}"))?;
479 let map = Self::from_string(&file).with_context(|| "failed to prase file")?;
480 return Ok(map);
481 }
482 if path.extension() == Some(OsStr::new("tmx")) {
483 let map = Self::from_tmx(path)?;
484 return Ok(map);
485 }
486 bail!(
487 "unsupported file extension {:?}",
488 path.extension().unwrap_or_else(|| OsStr::new("None"))
489 )
490 }
491
492 pub fn iter_player(&self) -> impl Iterator<Item = &Player> {
493 iter::once(&self.player_1)
494 .chain(iter::once(&self.player_2).flatten())
495 .chain(iter::once(&self.player_3).flatten())
496 .chain(iter::once(&self.player_4).flatten())
497 }
498
499 pub fn iter_mut_player(&mut self) -> impl Iterator<Item = &mut Player> {
500 iter::once(&mut self.player_1)
501 .chain(iter::once(&mut self.player_2).flatten())
502 .chain(iter::once(&mut self.player_3).flatten())
503 .chain(iter::once(&mut self.player_4).flatten())
504 }
505
506 pub fn iter_base_layer(
508 &self
509 ) -> impl Iterator<Item = (u8, u8, MapBaseTile, Orientation)> + '_ {
510 self.base_layer.iter().enumerate().flat_map(|(x, y_vec)| {
511 y_vec
512 .iter()
513 .enumerate()
514 .map(move |(y, item)| (x as u8, y as u8, item.0, item.1))
515 })
516 }
517
518 pub fn iter_object_layer(
520 &self
521 ) -> impl Iterator<Item = (u8, u8, ObjectTile, Orientation)> + '_ {
522 self.object_layer.iter().enumerate().flat_map(|(x, y_vec)| {
523 y_vec.iter().enumerate().filter_map(move |(y, item)| {
524 item.map(|item| (x as u8, y as u8, item.0, item.1))
525 })
526 })
527 }
528
529 pub fn iter_player_goals(&self) -> impl Iterator<Item = (u8, u8, PlayerTile)> + '_ {
531 iter::once(self.global_goal)
532 .flatten()
533 .map(|(x, y)| (x, y, PlayerTile::GlobalGoal))
534 .chain(
535 iter::once(&self.player_1)
536 .filter_map(|player| player.goal)
537 .map(|(x, y)| (x, y, PlayerTile::Goal1))
538 )
539 .chain(
540 iter::once(&self.player_2)
541 .flatten()
542 .filter_map(|player| player.goal)
543 .map(|(x, y)| (x, y, PlayerTile::Goal2))
544 )
545 .chain(
546 iter::once(&self.player_3)
547 .flatten()
548 .filter_map(|player| player.goal)
549 .map(|(x, y)| (x, y, PlayerTile::Goal3))
550 )
551 .chain(
552 iter::once(&self.player_4)
553 .flatten()
554 .filter_map(|player| player.goal)
555 .map(|(x, y)| (x, y, PlayerTile::Goal4))
556 )
557 }
558
559 pub fn iter_all(&self) -> impl Iterator<Item = (u8, u8, Tile, Orientation)> + '_ {
562 let base = self.iter_base_layer().map(|(x, y, tile, orientation)| {
563 (x, y, Tile::MapBaseTile(tile.to_owned()), orientation)
564 });
565 let objects = self.iter_object_layer().map(|(x, y, tile, orientation)| {
566 (x, y, Tile::MapObjectTile(tile.to_owned()), orientation)
567 });
568 let goals = self
569 .iter_player_goals()
570 .map(|(x, y, tile)| (x, y, Tile::PlayerTile(tile), Orientation::default()));
571 base.chain(objects).chain(goals)
572 }
573}