use crate::{optional_data_line, Exit, ExitInstance, Instance, Position}; use crate::game::{RoomType, RoomFormat}; use crate::exit::Transition; use std::str::FromStr; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Room { pub id: String, /// palette ID was optional in very early versions pub palette_id: Option, pub name: Option, /// tile IDs pub tiles: Vec, pub items: Vec, pub exits: Vec, pub endings: Vec, /// old method of handling walls - a comma-separated list of tile IDs pub walls: Vec, } impl Room { #[inline] fn name_line(&self) -> String { optional_data_line("NAME", self.name.as_ref()) } #[inline] fn wall_line(&self) -> String { if self.walls.len() > 0 { optional_data_line("WAL", Some(self.walls.join(","))) } else { "".to_string() } } #[inline] fn palette_line(&self) -> String { if self.palette_id.is_some() { optional_data_line("PAL", Some(self.palette_id.as_ref().unwrap())) } else { "".to_string() } } } impl From for Room { #[inline] fn from(string: String) -> Room { let string = string.replace("ROOM ", ""); let string = string.replace("SET ", ""); let mut lines: Vec<&str> = string.lines().collect(); let id = lines[0].to_string(); let mut name = None; let mut palette_id = None; let mut items: Vec = Vec::new(); let mut exits: Vec = Vec::new(); let mut endings: Vec = Vec::new(); let mut walls: Vec = Vec::new(); loop { let last_line = lines.pop().unwrap(); if last_line.starts_with("WAL") { let last_line = last_line.replace("WAL ", ""); let ids: Vec<&str> = last_line.split(",").collect(); walls = ids.iter().map(|&id| id.to_string()).collect(); } else if last_line.starts_with("NAME") { name = Some(last_line.replace("NAME ", "").to_string()); } else if last_line.starts_with("PAL") { palette_id = Some(last_line.replace("PAL ", "")); } else if last_line.starts_with("ITM") { let last_line = last_line.replace("ITM ", ""); let item_position: Vec<&str> = last_line.split(' ').collect(); let item_id = item_position[0]; let position = item_position[1]; let position = Position::from_str(position); if position.is_ok() { let position = position.unwrap(); items.push(Instance { position, id: item_id.to_string() }); } } else if last_line.starts_with("EXT") { let last_line = last_line.replace("EXT ", ""); let parts: Vec<&str> = last_line.split(' ').collect(); let position = Position::from_str(parts[0]); if position.is_ok() { let position = position.unwrap(); let exit = Exit::from_str( &format!("{} {}", parts[1], parts[2]) ); if exit.is_ok() { let exit = exit.unwrap(); let mut transition = None; let mut dialogue_id = None; let chunks = parts[3..].chunks(2); for chunk in chunks { if chunk[0] == "FX" { transition = Some(Transition::from(chunk[1])); } else if chunk[0] == "DLG" { dialogue_id = Some(chunk[1].to_string()); } } exits.push(ExitInstance { position, exit, transition, dialogue_id }); } } } else if last_line.starts_with("END") { let last_line = last_line.replace("END ", ""); let ending_position: Vec<&str> = last_line.split(' ').collect(); let ending = ending_position[0].to_string(); let position = ending_position[1]; let position = Position::from_str(position); if position.is_ok() { let position = position.unwrap(); endings.push(Instance { position, id: ending }); } } else { lines.push(last_line); break; } } let lines = &lines[1..]; let dimension = lines.len(); // x or y, e.g. `16` for 16x16 let mut tiles: Vec = Vec::new(); for line in lines.into_iter() { let comma_separated = line.contains(","); // old room format? let mut line: Vec<&str> = line .split(if comma_separated {","} else {""}) .collect(); if ! comma_separated { line = line[1..].to_owned(); } let line = line[..dimension].to_owned(); for tile_id in line { tiles.push(tile_id.to_string()); } } items.reverse(); exits.reverse(); endings.reverse(); Room { id, palette_id, name, tiles, items, exits, endings, walls, } } } impl Room { #[inline] pub fn to_string(&self, room_format: RoomFormat, room_type: RoomType) -> String { let mut tiles = String::new(); let mut items = String::new(); let mut exits = String::new(); let mut endings = String::new(); for line in self.tiles.chunks(16) { for tile in line { tiles.push_str(tile); if room_format == RoomFormat::CommaSeparated { tiles.push(','); } } if room_format == RoomFormat::CommaSeparated { tiles.pop(); // remove trailing comma } tiles.push_str("\n"); } tiles.pop(); // remove trailing newline for instance in &self.items { items.push_str(&format!( "\nITM {} {}", instance.id, instance.position.to_string() )); } for instance in &self.exits { exits.push_str(&format!( "\nEXT {} {}{}{}{}", instance.position.to_string(), instance.exit.to_string(), if instance.transition.is_some() { instance.transition.as_ref().unwrap().to_string() } else {"".to_string()}, if instance.dialogue_id.is_some() {" DLG "} else {""}, instance.dialogue_id.as_ref().unwrap_or(&"".to_string()), )); } for instance in &self.endings { endings.push_str(&format!( "\nEND {} {}", instance.id, instance.position.to_string() )); } format!( "{} {}\n{}{}{}{}{}{}{}", room_type.to_string(), self.id, tiles, self.name_line(), self.wall_line(), items, exits, endings, self.palette_line() ) } } #[cfg(test)] mod test { use crate::room::Room; use crate::game::{RoomType, RoomFormat}; #[test] fn test_room_from_string() { assert_eq!( Room::from(include_str!("test-resources/room").to_string()), crate::mock::room() ); } #[test] fn test_room_to_string() { assert_eq!( crate::mock::room().to_string(RoomFormat::CommaSeparated, RoomType::Room), include_str!("test-resources/room").to_string() ); } #[test] fn test_room_walls_array() { let output = Room::from(include_str!("test-resources/room-with-walls").to_string()); assert_eq!(output.walls, vec!["a".to_string(), "f".to_string()]); } }