1use crate::{
2 optional_data_line,
3 Exit,
4 ExitInstance,
5 Instance,
6 Position,
7 RoomType,
8 RoomFormat,
9 Transition
10};
11
12use std::collections::HashMap;
13
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct Room {
16 pub id: String,
17 pub palette_id: Option<String>,
19 pub name: Option<String>,
20 pub tiles: Vec<String>,
22 pub items: Vec<Instance>,
23 pub exits: Vec<ExitInstance>,
24 pub endings: Vec<Instance>,
25 pub walls: Option<Vec<String>>,
27}
28
29impl Room {
30 fn name_line(&self) -> String {
31 optional_data_line("NAME", self.name.as_ref())
32 }
33
34 fn wall_line(&self) -> String {
35 if let Some(walls) = &self.walls {
36 optional_data_line("WAL", Some(walls.join(",")))
37 } else {
38 "".to_string()
39 }
40 }
41
42 fn palette_line(&self) -> String {
43 match &self.palette_id {
44 Some(id) => optional_data_line("PAL", Some(id.clone())),
45 None => "".to_string(),
46 }
47 }
48}
49
50impl From<String> for Room {
51 fn from(string: String) -> Room {
52 let string = string.replace("ROOM ", "");
53 let string = string.replace("SET ", "");
54 let mut lines: Vec<&str> = string.lines().collect();
55 let id = lines[0].to_string();
56 let mut name = None;
57 let mut palette_id = None;
58 let mut items: Vec<Instance> = Vec::new();
59 let mut exits: Vec<ExitInstance> = Vec::new();
60 let mut endings: Vec<Instance> = Vec::new();
61 let mut walls = None;
62
63 loop {
64 let last_line = lines.pop().unwrap();
65
66 if last_line.starts_with("WAL") {
67 let last_line = last_line.replace("WAL ", "");
68 let ids: Vec<&str> = last_line.split(',').collect();
69 walls = Some(ids.iter().map(|&id| id.to_string()).collect());
70 } else if last_line.starts_with("NAME") {
71 name = Some(last_line.replace("NAME ", "").to_string());
72 } else if last_line.starts_with("PAL") {
73 palette_id = Some(last_line.replace("PAL ", ""));
74 } else if last_line.starts_with("ITM") {
75 let last_line = last_line.replace("ITM ", "");
76 let item_position: Vec<&str> = last_line.split(' ').collect();
77 let item_id = item_position[0];
78 let position = item_position[1];
79
80 if let Ok(position) = Position::from_str(position) {
81 items.push(Instance { position, id: item_id.to_string() });
82 }
83 } else if last_line.starts_with("EXT") {
84 let last_line = last_line.replace("EXT ", "");
85 let parts: Vec<&str> = last_line.split(' ').collect();
86 let position = Position::from_str(parts[0]);
87
88 if let Ok(position) = position {
89 let exit = Exit::from_str(
90 &format!("{} {}", parts[1], parts[2])
91 );
92
93 if let Ok(exit) = exit {
94 let mut transition = None;
95 let mut dialogue_id = None;
96
97 let chunks = parts[3..].chunks(2);
98
99 for chunk in chunks {
100 if chunk[0] == "FX" {
101 transition = Some(Transition::from_str(chunk[1]).unwrap());
102 } else if chunk[0] == "DLG" {
103 dialogue_id = Some(chunk[1].to_string());
104 }
105 }
106
107 exits.push(ExitInstance { position, exit, transition, dialogue_id });
108 }
109 }
110 } else if last_line.starts_with("END") {
111 let last_line = last_line.replace("END ", "");
112 let ending_position: Vec<&str> = last_line.split(' ').collect();
113 let ending = ending_position[0].to_string();
114 let position = ending_position[1];
115 let position = Position::from_str(position);
116
117 if let Ok(position) = position {
118 endings.push(Instance { position, id: ending });
119 }
120 } else {
121 lines.push(last_line);
122 break;
123 }
124 }
125
126 let lines = &lines[1..];
127 let dimension = lines.len(); let mut tiles: Vec<String> = Vec::new();
129
130 for line in lines.iter() {
131 let comma_separated = line.contains(','); let mut line: Vec<&str> = line
133 .split(if comma_separated {","} else {""})
134 .collect();
135
136 if ! comma_separated { line = line[1..].to_owned(); }
137 let line = line[..dimension].to_owned();
138
139 for tile_id in line {
140 tiles.push(tile_id.to_string());
141 }
142 }
143
144 items.reverse();
145 exits.reverse();
146 endings.reverse();
147
148 Room {
149 id,
150 palette_id,
151 name,
152 tiles,
153 items,
154 exits,
155 endings,
156 walls,
157 }
158 }
159}
160
161impl Room {
162 pub fn to_string(&self, room_format: RoomFormat, room_type: RoomType) -> String {
163 let mut tiles = String::new();
164 let mut items = String::new();
165 let mut exits = String::new();
166 let mut endings = String::new();
167
168 for line in self.tiles.chunks(16) {
169 for tile in line {
170 tiles.push_str(tile);
171 if room_format == RoomFormat::CommaSeparated {
172 tiles.push(',');
173 }
174 }
175
176 if room_format == RoomFormat::CommaSeparated {
177 tiles.pop(); }
179
180 tiles.push('\n');
181 }
182
183 tiles.pop(); for instance in &self.items {
186 items.push_str(&format!(
187 "\nITM {} {}",
188 instance.id,
189 instance.position.to_string()
190 ));
191 }
192
193 for instance in &self.exits {
194 exits.push_str(&format!(
195 "\nEXT {} {}{}{}{}",
196 instance.position.to_string(),
197 instance.exit.to_string(),
198 match &instance.transition {
199 Some(transition) => transition,
200 None => &Transition::None,
201 }.to_string(),
202 if instance.dialogue_id.is_some() {" DLG "} else {""},
203 instance.dialogue_id.as_ref().unwrap_or(&"".to_string()),
204 ));
205 }
206
207 for instance in &self.endings {
208 endings.push_str(&format!(
209 "\nEND {} {}",
210 instance.id,
211 instance.position.to_string()
212 ));
213 }
214
215 format!(
216 "{} {}\n{}{}{}{}{}{}{}",
217 room_type.to_string(),
218 self.id,
219 tiles,
220 self.name_line(),
221 self.wall_line(),
222 items,
223 exits,
224 endings,
225 self.palette_line()
226 )
227 }
228
229 pub fn change_tile_ids(&mut self, changes: &HashMap<String, String>) {
231 self.tiles = self.tiles.iter().map(|tile_id|
232 changes.get(tile_id).unwrap_or(tile_id).clone()
233 ).collect();
234 }
235}
236
237#[cfg(test)]
238mod test {
239 use crate::{Room, RoomType, RoomFormat};
240
241 #[test]
242 fn room_from_string() {
243 assert_eq!(
244 Room::from(include_str!("test-resources/room").to_string()),
245 crate::mock::room()
246 );
247 }
248
249 #[test]
250 fn room_to_string() {
251 assert_eq!(
252 crate::mock::room().to_string(RoomFormat::CommaSeparated, RoomType::Room),
253 include_str!("test-resources/room").to_string()
254 );
255 }
256
257 #[test]
258 fn room_walls_array() {
259 let output = Room::from(include_str!("test-resources/room-with-walls").to_string());
260
261 assert_eq!(output.walls, Some(vec!["a".to_string(), "f".to_string()]));
262 }
263}