1use std::path::{Path, PathBuf};
2
3use either::Either;
4use nom::branch::alt;
5use nom::bytes::complete::tag;
6use nom::combinator::{cut, fail};
7use nom::error::context;
8use nom::sequence::{preceded, tuple};
9use nom::Parser;
10
11use crate::events::EventWithCommands;
12use crate::osu_file::{
13 FilePath, Position, Version, VersionedDefault, VersionedFromStr, VersionedToString,
14};
15use crate::parsers::{
16 comma, comma_field, comma_field_type, comma_field_versioned_type, consume_rest_versioned_type,
17 nothing,
18};
19use crate::{Integer, VersionedFrom, VersionedTryFrom};
20
21use super::cmds::*;
22use super::error::*;
23
24#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum Layer {
27 Background,
28 Fail,
29 Pass,
30 Foreground,
31 Overlay,
32}
33
34impl VersionedFromStr for Layer {
35 type Err = ParseLayerError;
36
37 fn from_str(s: &str, _: Version) -> std::result::Result<Option<Self>, Self::Err> {
38 match s {
39 "Background" => Ok(Some(Layer::Background)),
40 "Fail" => Ok(Some(Layer::Fail)),
41 "Pass" => Ok(Some(Layer::Pass)),
42 "Foreground" => Ok(Some(Layer::Foreground)),
43 "Overlay" => Ok(Some(Layer::Overlay)),
44 _ => Err(ParseLayerError::UnknownVariant),
45 }
46 }
47}
48
49impl VersionedToString for Layer {
50 fn to_string(&self, _: Version) -> Option<String> {
51 let layer = match self {
52 Layer::Background => "Background",
53 Layer::Fail => "Fail",
54 Layer::Pass => "Pass",
55 Layer::Foreground => "Foreground",
56 Layer::Overlay => "Overlay",
57 };
58
59 Some(layer.to_string())
60 }
61}
62
63#[derive(Clone, Debug, Hash, PartialEq, Eq)]
64pub struct Object {
65 pub layer: Layer,
66 pub origin: Origin,
67 pub position: Position,
68 pub object_type: ObjectType,
69 pub commands: Vec<Command>,
70}
71
72impl VersionedToString for Object {
73 fn to_string(&self, version: Version) -> Option<String> {
74 self.to_string_variables(version, &[])
75 }
76}
77
78impl EventWithCommands for Object {
79 fn commands(&self) -> &[Command] {
80 &self.commands
81 }
82
83 fn commands_mut(&mut self) -> &mut Vec<Command> {
84 &mut self.commands
85 }
86
87 fn to_string_cmd(&self, version: Version) -> Option<String> {
88 let pos_str = format!("{},{}", self.position.x, self.position.y);
89
90 let obj = match &self.object_type {
91 ObjectType::Sprite(sprite) => format!(
92 "Sprite,{},{},{},{}",
93 self.layer.to_string(version).unwrap(),
94 self.origin.to_string(version).unwrap(),
95 sprite.filepath.to_string(version).unwrap(),
96 pos_str
97 ),
98 ObjectType::Animation(anim) => {
99 format!(
100 "Animation,{},{},{},{},{},{},{}",
101 self.layer.to_string(version).unwrap(),
102 self.origin.to_string(version).unwrap(),
103 anim.filepath.to_string(version).unwrap(),
104 pos_str,
105 anim.frame_count,
106 anim.frame_delay,
107 anim.loop_type.to_string(version).unwrap()
108 )
109 }
110 };
111
112 Some(obj)
113 }
114}
115
116impl VersionedFromStr for Object {
118 type Err = ParseObjectError;
119
120 fn from_str(s: &str, version: Version) -> Result<Option<Self>, Self::Err> {
121 let layer = || {
122 preceded(
123 context(ParseObjectError::MissingLayer.into(), comma()),
124 context(
125 ParseObjectError::InvalidLayer.into(),
126 comma_field_versioned_type(version),
127 ),
128 )
129 };
130 let origin = || {
131 preceded(
132 context(ParseObjectError::MissingOrigin.into(), comma()),
133 context(
134 ParseObjectError::InvalidOrigin.into(),
135 comma_field_versioned_type(version),
136 ),
137 )
138 };
139 let file_path = || {
140 preceded(
141 context(ParseObjectError::MissingFilePath.into(), comma()),
142 comma_field(),
143 )
144 .map(|p| p.into())
145 };
146 let position = || {
147 tuple((
148 preceded(
149 context(ParseObjectError::MissingPositionX.into(), comma()),
150 context(
151 ParseObjectError::InvalidPositionX.into(),
152 comma_field_type(),
153 ),
154 ),
155 preceded(
156 context(ParseObjectError::MissingPositionY.into(), comma()),
157 context(
158 ParseObjectError::InvalidPositionY.into(),
159 comma_field_type(),
160 ),
161 ),
162 ))
163 .map(|(x, y)| Position { x, y })
164 };
165 let frame_count = preceded(
166 context(ParseObjectError::MissingFrameCount.into(), comma()),
167 context(
168 ParseObjectError::InvalidFrameCount.into(),
169 comma_field_type(),
170 ),
171 );
172 let frame_delay = preceded(
173 context(ParseObjectError::MissingFrameDelay.into(), comma()),
174 context(
175 ParseObjectError::InvalidFrameDelay.into(),
176 comma_field_type(),
177 ),
178 );
179 let loop_type = alt((
180 preceded(
181 context(ParseObjectError::MissingLoopType.into(), comma()),
182 context(
183 ParseObjectError::InvalidLoopType.into(),
184 consume_rest_versioned_type(version),
185 ),
186 ),
187 nothing().map(|_| LoopType::default(version).unwrap()),
188 ));
189
190 let (_, object) = alt((
191 preceded(
192 tag("Sprite"),
193 cut(tuple((layer(), origin(), file_path(), position()))).map(
194 |(layer, origin, filepath, position)| Object {
195 layer,
196 origin,
197 position,
198 object_type: ObjectType::Sprite(Sprite { filepath }),
199 commands: Vec::new(),
200 },
201 ),
202 ),
203 preceded(
204 tag("Animation"),
205 cut(tuple((
206 layer(),
207 origin(),
208 file_path(),
209 position(),
210 frame_count,
211 frame_delay,
212 loop_type,
213 ))),
214 )
215 .map(
216 |(layer, origin, filepath, position, frame_count, frame_delay, loop_type)| Object {
217 layer,
218 origin,
219 position,
220 object_type: ObjectType::Animation(Animation {
221 frame_count,
222 frame_delay,
223 loop_type,
224 filepath,
225 }),
226 commands: Vec::new(),
227 },
228 ),
229 context(ParseObjectError::UnknownObjectType.into(), fail),
230 ))(s)?;
231
232 Ok(Some(object))
233 }
234}
235
236#[derive(Clone, Debug, Hash, PartialEq, Eq)]
237pub struct Animation {
238 pub frame_count: u32,
239 pub frame_delay: rust_decimal::Decimal,
240 pub loop_type: LoopType,
241 pub filepath: FilePath,
242}
243
244impl Animation {
245 pub fn frame_file_names(&self) -> Vec<PathBuf> {
246 let mut file_names = Vec::with_capacity(self.frame_count as usize);
247
248 let filepath = self.filepath.get();
249 let mut file_name = filepath.file_name().unwrap().to_string_lossy().to_string();
250 let file_extension = match filepath.extension() {
251 Some(extension) => {
252 let extension = format!(".{}", extension.to_string_lossy());
253 file_name = file_name.strip_suffix(&extension).unwrap().to_string();
254 extension
255 }
256 None => String::new(),
257 };
258
259 for i in 0..self.frame_count {
260 file_names.push(format!("{file_name}{i}{file_extension}").into());
261 }
262
263 file_names
264 }
265}
266
267#[derive(Clone, Debug, Hash, PartialEq, Eq)]
268pub struct Sprite {
269 pub filepath: FilePath,
270}
271
272impl Sprite {
273 pub fn new(filepath: &Path) -> Result<Self, FilePathNotRelative> {
274 if filepath.is_absolute() {
275 Err(FilePathNotRelative)
276 } else {
277 Ok(Self {
278 filepath: filepath.into(),
279 })
280 }
281 }
282}
283
284#[derive(Clone, Debug, Hash, PartialEq, Eq)]
285#[non_exhaustive]
286pub enum ObjectType {
287 Sprite(Sprite),
288 Animation(Animation),
289}
290
291#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
292pub struct Origin {
293 pub type_: Either<OriginType, Integer>,
297 pub shorthand: bool,
298}
299
300#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
301#[non_exhaustive]
302pub enum OriginType {
303 TopLeft,
304 Centre,
305 CentreLeft,
306 TopRight,
307 BottomCentre,
308 TopCentre,
309 Custom,
310 CentreRight,
311 BottomLeft,
312 BottomRight,
313}
314
315impl From<OriginType> for Origin {
316 fn from(type_: OriginType) -> Self {
317 Self {
318 type_: Either::Left(type_),
319 shorthand: true,
320 }
321 }
322}
323
324impl VersionedToString for Origin {
325 fn to_string(&self, version: Version) -> Option<String> {
326 let origin = if self.shorthand {
327 let origin = match self.type_ {
328 Either::Left(type_) => {
329 <Integer as VersionedFrom<OriginType>>::from(type_, version).unwrap()
330 }
331 Either::Right(type_) => type_,
332 };
333
334 origin.to_string()
335 } else {
336 match self.type_ {
337 Either::Left(type_) => match type_ {
338 OriginType::TopLeft => "TopLeft",
339 OriginType::Centre => "Centre",
340 OriginType::CentreLeft => "CentreLeft",
341 OriginType::TopRight => "TopRight",
342 OriginType::BottomCentre => "BottomCentre",
343 OriginType::TopCentre => "TopCentre",
344 OriginType::Custom => "Custom",
345 OriginType::CentreRight => "CentreRight",
346 OriginType::BottomLeft => "BottomLeft",
347 OriginType::BottomRight => "BottomRight",
348 }
349 .to_string(),
350 Either::Right(type_) => type_.to_string(),
351 }
352 };
353
354 Some(origin)
355 }
356}
357
358impl VersionedFromStr for Origin {
359 type Err = ParseOriginError;
360
361 fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
362 match s {
363 "TopLeft" => Ok(Some(Origin {
364 shorthand: false,
365 type_: Either::Left(OriginType::TopLeft),
366 })),
367 "Centre" => Ok(Some(Origin {
368 shorthand: false,
369 type_: Either::Left(OriginType::Centre),
370 })),
371 "CentreLeft" => Ok(Some(Origin {
372 shorthand: false,
373 type_: Either::Left(OriginType::CentreLeft),
374 })),
375 "TopRight" => Ok(Some(Origin {
376 shorthand: false,
377 type_: Either::Left(OriginType::TopRight),
378 })),
379 "BottomCentre" => Ok(Some(Origin {
380 shorthand: false,
381 type_: Either::Left(OriginType::BottomCentre),
382 })),
383 "TopCentre" => Ok(Some(Origin {
384 shorthand: false,
385 type_: Either::Left(OriginType::TopCentre),
386 })),
387 "Custom" => Ok(Some(Origin {
388 shorthand: false,
389 type_: Either::Left(OriginType::Custom),
390 })),
391 "CentreRight" => Ok(Some(Origin {
392 shorthand: false,
393 type_: Either::Left(OriginType::CentreRight),
394 })),
395 "BottomLeft" => Ok(Some(Origin {
396 shorthand: false,
397 type_: Either::Left(OriginType::BottomLeft),
398 })),
399 "BottomRight" => Ok(Some(Origin {
400 shorthand: false,
401 type_: Either::Left(OriginType::BottomRight),
402 })),
403 _ => {
404 let s = s.parse()?;
405 let type_ = match <OriginType as VersionedTryFrom<Integer>>::try_from(s, version) {
406 Ok(type_) => Either::Left(type_.unwrap()),
407 Err(err) => match err {
408 OriginTryFromIntError::UnknownVariant => Either::Right(s),
409 },
410 };
411
412 Ok(Some(Origin {
413 type_,
414 shorthand: true,
415 }))
416 }
417 }
418 }
419}
420
421impl VersionedTryFrom<Integer> for OriginType {
422 type Error = OriginTryFromIntError;
423
424 fn try_from(value: Integer, _: Version) -> Result<Option<Self>, Self::Error> {
425 match value {
426 0 => Ok(Some(OriginType::TopLeft)),
427 1 => Ok(Some(OriginType::Centre)),
428 2 => Ok(Some(OriginType::CentreLeft)),
429 3 => Ok(Some(OriginType::TopRight)),
430 4 => Ok(Some(OriginType::BottomCentre)),
431 5 => Ok(Some(OriginType::TopCentre)),
432 6 => Ok(Some(OriginType::Custom)),
433 7 => Ok(Some(OriginType::CentreRight)),
434 8 => Ok(Some(OriginType::BottomLeft)),
435 9 => Ok(Some(OriginType::BottomRight)),
436 _ => Err(OriginTryFromIntError::UnknownVariant),
437 }
438 }
439}
440
441impl VersionedFrom<OriginType> for Integer {
442 fn from(value: OriginType, _: Version) -> Option<Self> {
443 match value {
444 OriginType::TopLeft => Some(0),
445 OriginType::Centre => Some(1),
446 OriginType::CentreLeft => Some(2),
447 OriginType::TopRight => Some(3),
448 OriginType::BottomCentre => Some(4),
449 OriginType::TopCentre => Some(5),
450 OriginType::Custom => Some(6),
451 OriginType::CentreRight => Some(7),
452 OriginType::BottomLeft => Some(8),
453 OriginType::BottomRight => Some(9),
454 }
455 }
456}
457
458#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
459#[non_exhaustive]
460pub enum LoopType {
461 LoopForever,
462 LoopOnce,
463}
464
465impl VersionedToString for LoopType {
466 fn to_string(&self, _: Version) -> Option<String> {
467 let loop_type = match self {
468 LoopType::LoopForever => "LoopForever",
469 LoopType::LoopOnce => "LoopOnce",
470 };
471
472 Some(loop_type.to_string())
473 }
474}
475
476impl VersionedFromStr for LoopType {
477 type Err = ParseLoopTypeError;
478
479 fn from_str(s: &str, _: Version) -> std::result::Result<Option<Self>, Self::Err> {
480 match s {
481 "LoopForever" => Ok(Some(LoopType::LoopForever)),
482 "LoopOnce" => Ok(Some(LoopType::LoopOnce)),
483 _ => Err(ParseLoopTypeError::UnknownVariant),
484 }
485 }
486}
487
488impl VersionedDefault for LoopType {
489 fn default(_: Version) -> Option<Self> {
490 Some(Self::LoopForever)
491 }
492}