1use crate::lparse::{
2 Error,
3 LParse
4};
5
6use self::cnmb_types::{BackgroundLayer, TileProperties};
7
8pub mod cnmb_types;
11pub mod cnms_types;
14pub mod consts;
16
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[derive(Debug, Default, Copy, Clone)]
22pub struct Duration(pub i32);
23
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[derive(Debug, Default, Copy, Clone)]
27pub struct Point(pub f32, pub f32);
28
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[derive(Debug)]
32pub struct VersionSpecs {
33 version: u32,
34 num_teleports: usize,
35 num_spawns: usize,
36 num_spawn_modes: usize,
37 teleport_name_size: usize,
38 max_tile_frames: usize,
39 ending_text_lines: usize,
40 ending_text_line_len: usize,
41 background_layers: usize,
42 title_ending_text_line: usize,
43 preview_tile_index: usize,
44}
45
46impl VersionSpecs {
47 pub fn from_version(version: u32) -> Result<Self, Error> {
50 match version {
51 1 => Ok(Self {
52 version,
53 num_teleports: 512,
54 num_spawns: 128,
55 num_spawn_modes: 3,
56 teleport_name_size: 41,
57 max_tile_frames: 32,
58 ending_text_lines: 48,
59 ending_text_line_len: 32,
60 background_layers: 32,
61 title_ending_text_line: 47,
62 preview_tile_index: 256,
63 }),
64 _ => Err(Error::UnknownVersion(version))
65 }
66 }
67
68 pub fn get_version(&self) -> u32 {
70 self.version
71 }
72
73 pub fn get_num_teleports(&self) -> usize {
75 self.num_teleports
76 }
77
78 pub fn get_num_spawns(&self) -> usize {
81 self.num_spawns
82 }
83
84 pub fn get_num_spawn_modes(&self) -> usize {
86 self.num_spawn_modes
87 }
88
89 pub fn get_teleport_name_size(&self) -> usize {
92 self.teleport_name_size
93 }
94
95 pub fn get_max_tile_frames(&self) -> usize {
97 self.max_tile_frames
98 }
99
100 pub fn get_ending_text_lines(&self) -> usize {
102 self.ending_text_lines
103 }
104
105 pub fn get_ending_text_line_len(&self) -> usize {
107 self.ending_text_line_len
108 }
109
110 pub fn get_background_layers(&self) -> usize {
112 self.background_layers
113 }
114
115 pub fn get_title_ending_text_line(&self) -> usize {
117 self.title_ending_text_line
118 }
119
120 pub fn get_preview_tile_index(&self) -> usize {
122 self.preview_tile_index
123 }
124}
125
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[derive(Debug, PartialEq, Eq, num_derive::FromPrimitive, num_derive::ToPrimitive)]
129pub enum DifficultyRating {
130 Tutorial,
132 ReallyEasy,
134 Easy,
136 Normal,
138 KindaHard,
140 Hard,
142 Ultra,
144 Extreme,
146 Dealth,
148 UltraDeath,
150}
151
152impl DifficultyRating {
153 pub fn from_difficulty_id(id: u8) -> Option<Self> {
155 num_traits::FromPrimitive::from_u8(id)
156 }
157
158 pub fn get_difficulty_id(&self) -> u8 {
160 num_traits::ToPrimitive::to_u8(self).unwrap_or(3)
161 }
162
163 pub fn to_string_pretty(&self) -> String {
165 match self {
166 &Self::Tutorial => "Tutorial".to_string(),
167 &Self::ReallyEasy => "Really Easy".to_string(),
168 &Self::Easy => "Easy".to_string(),
169 &Self::Normal => "Normal".to_string(),
170 &Self::KindaHard => "Kinda Hard".to_string(),
171 &Self::Hard => "Hard".to_string(),
172 &Self::Ultra => "Ultra!".to_string(),
173 &Self::Extreme => "Extreme!".to_string(),
174 &Self::Dealth => "Death!!!".to_string(),
175 &Self::UltraDeath => "ULTRA DEATH!".to_string(),
176 }
177 }
178}
179
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184#[derive(Debug)]
185pub struct LevelMetaData {
186 pub title: String,
188 pub subtitle: Option<String>,
191 pub preview_loc: (u32, u32),
193 pub difficulty_rating: DifficultyRating,
195}
196
197impl LevelMetaData {
198 pub fn from_lparse(cnmb: &LParse, cnms: &LParse, version: &VersionSpecs, ignore_warnings: bool) -> Result<Self, Error> {
200 let title_full = cnms_types::get_ending_text_line(cnms, version, version.title_ending_text_line)?;
201 let title = title_full.split('\\').next().unwrap_or("").to_string();
202 let subtitle = match title_full.split('\\').nth(1) {
203 Some(s) => Some(s.to_string()),
204 None => None,
205 };
206
207 let tile_properties = cnmb_types::TileProperties::from_lparse(cnmb, version, version.preview_tile_index, ignore_warnings)?;
208 let preview_loc = (tile_properties.frames[0].0 as u32, tile_properties.frames[0].1 as u32);
209 let difficulty_rating = DifficultyRating::from_difficulty_id(
210 cnmb.try_get_entry("BP_DMG")?
211 .try_get_i32()?[version.preview_tile_index] as u8
212 ).unwrap_or(DifficultyRating::Normal);
213
214 Ok(Self {
215 title,
216 subtitle,
217 preview_loc,
218 difficulty_rating,
219 })
220 }
221
222 pub fn get_full_title(&self) -> String {
224 let subtitle = "\\".to_string() + self.subtitle.as_ref().unwrap_or(&"".to_string()).as_str();
225 self.title.clone() + match self.subtitle {
226 Some(_) => subtitle.as_str(),
227 None => "",
228 }
229 }
230
231 fn get_tile_property(&self) -> cnmb_types::TileProperties {
232 cnmb_types::TileProperties {
233 solid: false,
234 transparency: consts::CLEAR,
235 damage_type: cnmb_types::DamageType::Lava(self.difficulty_rating.get_difficulty_id() as i32),
236 anim_speed: Duration(1),
237 frames: vec![(self.preview_loc.0 as i32, self.preview_loc.1 as i32)],
238 collision_data: cnmb_types::CollisionType::Box(crate::Rect { x: 0, y: 0, w: 0, h: 0 }),
239 }
240 }
241}
242
243#[cfg_attr(any(feature = "level_data", doc), derive(serde::Serialize, serde::Deserialize))]
245#[derive(Debug)]
246pub struct LevelData {
247 pub version: VersionSpecs,
249 pub spawners: Vec<cnms_types::Spawner>,
251 pub cells: cnmb_types::Cells,
253 pub tile_properties: Vec<cnmb_types::TileProperties>,
255 pub metadata: LevelMetaData,
257 pub background_layers: Vec<BackgroundLayer>,
260}
261
262impl LevelData {
263 pub fn from_version(version: u32) -> Result<Self, Error> {
267 let version = VersionSpecs::from_version(version)?;
268 let background_layers = (0..version.background_layers).map(|_| cnmb_types::BackgroundLayer::default()).collect();
269
270 Ok(Self {
271 version,
272 spawners: Vec::new(),
273 tile_properties: Vec::new(),
274 cells: cnmb_types::Cells::new(512, 256),
275 metadata: LevelMetaData {
276 title: "Untitled".to_string(),
277 subtitle: None,
278 preview_loc: (0, 0),
279 difficulty_rating: DifficultyRating::Normal,
280 },
281 background_layers,
282 })
283 }
284
285 pub fn from_lparse(cnmb: &LParse, cnms: &LParse, ignore_warnings: bool) -> Result<Self, Error> {
295 if cnmb.version.version != cnms.version.version {
296 return Err(Error::MismatchedVersions(cnmb.version.version, cnms.version.version));
297 }
298
299 let version = VersionSpecs::from_version(cnmb.version.version)?;
300 let tile_properties = Self::tile_properties_from_lparse(cnmb, &version, ignore_warnings)?;
301 let cells = cnmb_types::Cells::from_lparse(cnmb, tile_properties.len())?;
302 let spawners = Self::spawners_from_lparse(cnms, &version, ignore_warnings)?;
303 let metadata = LevelMetaData::from_lparse(cnmb, cnms, &version, ignore_warnings)?;
304 let background_layers = Self::background_layers_from_lparse(cnmb, &version)?;
305
306 Ok(Self {
307 version,
308 tile_properties,
309 cells,
310 spawners,
311 metadata,
312 background_layers,
313 })
314 }
315
316 pub fn save(&self, cnmb: &mut LParse, cnms: &mut LParse) {
318 cnms_types::save_spawner_vec(cnms, &self.version, self.metadata.get_full_title(), &self.spawners);
319 cnmb_types::save_background_vec(cnmb, &self.version, &self.background_layers);
320 cnmb_types::save_tile_properties_vec(cnmb, &self.version, &self.metadata.get_tile_property(), &self.tile_properties);
321 self.cells.save(cnmb, self.tile_properties.len() + 1, &self.version);
322 }
323
324 fn tile_properties_from_lparse(cnmb: &LParse, version: &VersionSpecs, ignore_warnings: bool) -> Result<Vec<cnmb_types::TileProperties>, Error> {
325 let mut tile_properties = Vec::new();
326
327 for index in 0..cnmb.try_get_entry("BLOCKS_HEADER")?.try_get_i32()?[2] as usize {
328 if index == version.preview_tile_index {
329 continue;
330 }
331 let tile = cnmb_types::TileProperties::from_lparse(cnmb, version, index, ignore_warnings)?;
332 match tile {
333 TileProperties {
334 damage_type: cnmb_types::DamageType::None,
335 anim_speed: Duration(1),
336 frames,
337 collision_data: cnmb_types::CollisionType::Box(crate::Rect { x: 0, y: 0, w: 32, h: 32 }),
338 ..
339 } if frames.len() == 1 && frames[0] == (0, 0) => {},
340 tile => tile_properties.push(tile),
341 };
342 }
343
344 Ok(tile_properties)
345 }
346
347 fn spawners_from_lparse(cnms: &LParse, version: &VersionSpecs, ignore_warnings: bool) -> Result<Vec<cnms_types::Spawner>, Error> {
348 let mut spawners = Vec::new();
349
350 for index in 0..cnms.try_get_entry("NUM_SPAWNERS")?.try_get_i32()?[0] as usize {
351 spawners.push(cnms_types::Spawner::from_lparse(cnms, version, index, ignore_warnings)?);
352 }
353
354 Ok(spawners)
355 }
356
357 fn background_layers_from_lparse(cnmb: &LParse, version: &VersionSpecs) -> Result<Vec<BackgroundLayer>, Error> {
358 let mut background_layers = Vec::new();
359
360 for index in 0..version.background_layers {
361 background_layers.push(cnmb_types::BackgroundLayer::from_lparse(cnmb, version, index)?);
362 }
363
364 Ok(background_layers)
365 }
366}