1use crate::{optional_data_line, AnimationFrames, Image, Position};
2use crate::image::animation_frames_from_str;
3
4use std::fmt;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct Sprite {
8 pub id: String,
9 pub name: Option<String>,
10 pub animation_frames: Vec<Image>,
11 pub dialogue_id: Option<String>,
12 pub room_id: Option<String>,
13 pub position: Option<Position>,
14 pub colour_id: Option<u64>,
15 pub items: Vec<String>,
16}
17
18impl Sprite {
19 fn name_line(&self) -> String {
20 optional_data_line("NAME", self.name.as_ref())
21 }
22
23 fn dialogue_line(&self) -> String {
24 optional_data_line("DLG", self.dialogue_id.as_ref())
25 }
26
27 fn room_position_line(&self) -> String {
28 if self.room_id.is_some() && self.position.is_some() {
29 format!(
30 "\nPOS {} {}",
31 self.room_id.as_ref().unwrap(),
32 self.position.as_ref().unwrap().to_string()
33 )
34 } else {
35 "".to_string()
36 }
37 }
38
39 fn colour_line(&self) -> String {
40 optional_data_line("COL", self.colour_id.as_ref())
41 }
42
43 fn item_lines(&self) -> String {
44 if self.items.is_empty() {
45 "".to_string()
46 } else {
47 let lines: Vec<String> = self.items.iter().map(
48 |item| format!("ITM {}", item)
49 ).collect();
50
51 format!("\n{}", lines.join("\n"))
52 }
53 }
54
55 pub fn from_str(str: &str) -> Result<Sprite, crate::Error> {
56 let mut lines: Vec<&str> = str.lines().collect();
57
58 if lines.is_empty() || !lines[0].starts_with("SPR ") {
59 return Err(crate::Error::Sprite);
60 }
61
62 let id = lines[0].replace("SPR ", "");
63 let mut name = None;
64 let mut dialogue_id: Option<String> = None;
65 let mut room_id: Option<String> = None;
66 let mut position: Option<Position> = None;
67 let mut colour_id: Option<u64> = None;
68 let mut items: Vec<String> = Vec::new();
69
70 loop {
71 let last_line = lines.pop().unwrap();
72
73 if last_line.starts_with("NAME") {
74 name = Some(last_line.replace("NAME ", "").to_string());
75 } else if last_line.starts_with("DLG") {
76 dialogue_id = Some(last_line.replace("DLG ", "").to_string());
77 } else if last_line.starts_with("POS") {
78 let last_line = last_line.replace("POS ", "");
79 let room_position: Vec<&str> = last_line.split(' ').collect();
80 room_id = Some(room_position[0].to_string());
81
82 if room_position.len() < 2 {
83 return Err(crate::Error::Sprite);
84 }
85
86 if let Ok(pos) = Position::from_str(room_position[1]) {
87 position = Some(pos);
88 } else {
89 return Err(crate::Error::Sprite);
90 }
91 } else if last_line.starts_with("COL") {
92 colour_id = Some(last_line.replace("COL ", "").parse().unwrap());
93 } else if last_line.starts_with("ITM") {
94 items.push(last_line.replace("ITM ", ""));
95 } else {
96 lines.push(last_line);
97 break;
98 }
99 }
100
101 items.reverse();
102
103 let animation_frames = animation_frames_from_str(
104 &lines[1..].join("\n")
105 );
106
107 Ok(Sprite {
108 id,
109 name,
110 animation_frames,
111 dialogue_id,
112 room_id,
113 position,
114 colour_id,
115 items
116 })
117 }
118}
119
120impl fmt::Display for Sprite {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(
123 f,
124 "SPR {}\n{}{}{}{}{}{}",
125 self.id,
126 self.animation_frames.to_string(),
127 self.name_line(),
128 self.dialogue_line(),
129 self.room_position_line(),
130 self.colour_line(),
131 self.item_lines(),
132 )
133 }
134}
135
136#[cfg(test)]
137mod test {
138 use crate::{mock, Sprite};
139
140 #[test]
141 fn sprite_from_string() {
142 let output = Sprite::from_str(include_str!("test-resources/sprite")).unwrap();
143 let expected = mock::sprite();
144
145 assert_eq!(output, expected);
146 }
147
148 #[test]
149 fn sprite_to_string() {
150 assert_eq!(mock::sprite().to_string(), include_str!("test-resources/sprite"));
151 }
152}