1pub mod audio_sample;
2pub mod error;
3pub mod normal_event;
4pub mod storyboard;
5
6use nom::branch::alt;
7use nom::combinator::{cut, eof, peek, success};
8use nom::sequence::tuple;
9use nom::Parser;
10use nom::{bytes::complete::tag, combinator::rest, sequence::preceded};
11
12use crate::events::storyboard::cmds::CommandProperties;
13use crate::helper::trait_ext::MapOptStringNewLine;
14use crate::osb::Variable;
15use crate::parsers::comma;
16
17use self::storyboard::cmds::Command;
18use self::storyboard::error::CommandPushError;
19use self::storyboard::{error::ParseObjectError, sprites::Object};
20
21use super::Version;
22use super::{types::Error, Integer, VersionedDefault, VersionedFromStr, VersionedToString};
23
24pub use audio_sample::*;
25pub use error::*;
26pub use normal_event::*;
27
28#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
29pub struct Events(pub Vec<Event>);
30
31const OLD_VERSION_TIME_OFFSET: Integer = 24;
32
33impl VersionedFromStr for Events {
34 type Err = Error<ParseError>;
35
36 fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
37 Events::from_str_variables(s, version, &[])
38 }
39}
40
41impl Events {
42 pub fn from_str_variables(
43 s: &str,
44 version: Version,
45 variables: &[Variable],
46 ) -> std::result::Result<Option<Self>, Error<ParseError>> {
47 let mut events = Events(Vec::new());
48
49 #[derive(Clone)]
50 enum NormalEventType {
51 Background,
52 Video,
53 Break,
54 ColourTransformation,
55 SpriteLegacy,
56 AnimationLegacy,
57 SampleLegacy,
58 Other,
59 }
60
61 let mut comment = preceded::<_, _, _, nom::error::Error<_>, _, _>(tag("//"), rest);
62 let background = || {
63 peek(tuple((
64 tag::<_, _, nom::error::Error<_>>(normal_event::BACKGROUND_HEADER),
65 cut(alt((eof, comma()))),
66 )))
67 .map(|_| NormalEventType::Background)
68 };
69 let video = || {
70 peek(tuple((
71 alt((
72 tag(normal_event::VIDEO_HEADER),
73 tag(normal_event::VIDEO_HEADER_LONG),
74 )),
75 cut(alt((eof, comma()))),
76 )))
77 .map(|_| NormalEventType::Video)
78 };
79 let break_ = || {
80 peek(tuple((
81 alt((
82 tag(normal_event::BREAK_HEADER),
83 tag(normal_event::BREAK_HEADER_LONG),
84 )),
85 cut(alt((eof, comma()))),
86 )))
87 .map(|_| NormalEventType::Break)
88 };
89 let colour_transformation = || {
90 peek(tuple((
91 tag(normal_event::COLOUR_TRANSFORMATION_HEADER),
92 cut(alt((eof, comma()))),
93 )))
94 .map(|_| NormalEventType::ColourTransformation)
95 };
96 let sprite_legacy = || {
97 peek(tuple((
98 tag(normal_event::SPRITE_LEGACY_HEADER),
99 cut(alt((eof, comma()))),
100 )))
101 .map(|_| NormalEventType::SpriteLegacy)
102 };
103 let animation_legacy = || {
104 peek(tuple((
105 tag(normal_event::ANIMATION_LEGACY_HEADER),
106 cut(alt((eof, comma()))),
107 )))
108 .map(|_| NormalEventType::AnimationLegacy)
109 };
110 let sample_legacy = || {
111 peek(tuple((
112 tag(normal_event::SAMPLE_LEGACY_HEADER),
113 cut(alt((eof, comma()))),
114 )))
115 .map(|_| NormalEventType::SampleLegacy)
116 };
117
118 for (line_index, line) in s.lines().enumerate() {
119 if line.trim().is_empty() {
120 continue;
121 }
122
123 if let Ok((_, comment)) = comment(line) {
124 events.0.push(Event::Comment(comment.to_string()));
125 continue;
126 }
127
128 let indent = line.chars().take_while(|c| *c == ' ' || *c == '_').count();
129
130 if indent > 0 {
132 let cmd_parse = || {
133 let line_without_header = match line.chars().position(|c| c == ',') {
134 Some(i) => &line[i + 1..],
135 None => line,
136 };
137
138 let mut line_with_variable: Option<String> = None;
139 for variable in variables {
140 let variable_full = format!("${}", variable.name);
141
142 if line_without_header.contains(&variable_full) {
143 let new_line = match line_with_variable {
144 Some(line_with_variable) => {
145 line_with_variable.replace(&variable_full, &variable.value)
146 }
147 None => line.replace(&variable_full, &variable.value),
148 };
149
150 line_with_variable = Some(new_line);
151 }
152 }
153
154 match line_with_variable {
155 Some(line_with_variable) => Error::new_from_result_into(
156 Command::from_str(&line_with_variable, version),
157 line_index,
158 ),
159 None => Error::new_from_result_into(
160 Command::from_str(line, version),
161 line_index,
162 ),
163 }
164 };
165
166 match events.0.last_mut() {
167 Some(event) => match event {
168 Event::Background(bg) => {
169 if let Some(cmd) = cmd_parse()? {
170 Error::new_from_result_into(
171 bg.try_push_cmd(cmd, indent),
172 line_index,
173 )?
174 }
175 }
176 Event::Video(video) => {
177 if let Some(cmd) = cmd_parse()? {
178 Error::new_from_result_into(
179 video.try_push_cmd(cmd, indent),
180 line_index,
181 )?
182 }
183 }
184 Event::SpriteLegacy(sprite) => {
185 if let Some(cmd) = cmd_parse()? {
186 Error::new_from_result_into(
187 sprite.try_push_cmd(cmd, indent),
188 line_index,
189 )?
190 }
191 }
192 Event::AnimationLegacy(animation) => {
193 if let Some(cmd) = cmd_parse()? {
194 Error::new_from_result_into(
195 animation.try_push_cmd(cmd, indent),
196 line_index,
197 )?
198 }
199 }
200 Event::SampleLegacy(sample) => {
201 if let Some(cmd) = cmd_parse()? {
202 Error::new_from_result_into(
203 sample.try_push_cmd(cmd, indent),
204 line_index,
205 )?
206 }
207 }
208 Event::StoryboardObject(obj) => {
209 if let Some(cmd) = cmd_parse()? {
210 Error::new_from_result_into(
211 obj.try_push_cmd(cmd, indent),
212 line_index,
213 )?
214 }
215 }
216 _ => {
217 return Err(Error::new(
218 ParseError::StoryboardCmdWithNoSprite,
219 line_index,
220 ))
221 }
222 },
223 _ => {
224 return Err(Error::new(
225 ParseError::StoryboardCmdWithNoSprite,
226 line_index,
227 ))
228 }
229 }
230 continue;
231 }
232
233 let (_, type_) = alt((
235 background(),
236 video(),
237 break_(),
238 colour_transformation(),
239 sprite_legacy(),
240 animation_legacy(),
241 sample_legacy(),
242 success(NormalEventType::Other),
243 ))(line)
244 .unwrap();
245
246 let res = match type_ {
247 NormalEventType::Background => Background::from_str(line, version)
248 .map(|e| e.map(Event::Background))
249 .map_err(ParseError::ParseBackgroundError),
250 NormalEventType::Video => Video::from_str(line, version)
251 .map(|e| e.map(Event::Video))
252 .map_err(ParseError::ParseVideoError),
253 NormalEventType::Break => Break::from_str(line, version)
254 .map(|e| e.map(Event::Break))
255 .map_err(ParseError::ParseBreakError),
256 NormalEventType::ColourTransformation => {
257 ColourTransformation::from_str(line, version)
258 .map(|e| e.map(Event::ColourTransformation))
259 .map_err(ParseError::ParseColourTransformationError)
260 }
261 NormalEventType::SpriteLegacy => SpriteLegacy::from_str(line, version)
262 .map(|e| e.map(Event::SpriteLegacy))
263 .map_err(ParseError::ParseSpriteLegacyError),
264 NormalEventType::AnimationLegacy => AnimationLegacy::from_str(line, version)
265 .map(|e| e.map(Event::AnimationLegacy))
266 .map_err(ParseError::ParseAnimationLegacyError),
267 NormalEventType::SampleLegacy => SampleLegacy::from_str(line, version)
268 .map(|e| e.map(Event::SampleLegacy))
269 .map_err(ParseError::ParseSampleLegacyError),
270 NormalEventType::Other => {
271 match Object::from_str(line, version) {
273 Ok(e) => Ok(e.map(Event::StoryboardObject)),
274 Err(err) => {
275 if let ParseObjectError::UnknownObjectType = err {
276 AudioSample::from_str(line, version)
278 .map(|e| e.map(Event::AudioSample))
279 .map_err(|e| {
280 if let ParseAudioSampleError::WrongEvent = e {
281 ParseError::UnknownEventType
282 } else {
283 ParseError::ParseAudioSampleError(e)
284 }
285 })
286 } else {
287 Err(ParseError::ParseStoryboardObjectError(err))
288 }
289 }
290 }
291 }
292 };
293
294 match res {
295 Ok(event) => {
296 if let Some(event) = event {
297 events.0.push(event)
298 }
299 }
300 Err(e) => return Err(Error::new(e, line_index)),
301 }
302 }
303
304 Ok(Some(events))
305 }
306
307 pub fn to_string_variables(&self, version: Version, variables: &[Variable]) -> Option<String> {
308 let mut s = self
309 .0
310 .iter()
311 .map(|event| event.to_string_variables(version, variables));
312
313 Some(s.map_string_new_line())
314 }
315}
316
317impl VersionedToString for Events {
318 fn to_string(&self, version: Version) -> Option<String> {
319 self.to_string_variables(version, &[])
320 }
321}
322
323impl VersionedDefault for Events {
324 fn default(_: Version) -> Option<Self> {
325 Some(Events(Vec::new()))
326 }
327}
328
329#[derive(Clone, Debug, Hash, PartialEq, Eq)]
330#[non_exhaustive]
331pub enum Event {
333 Comment(String),
334 Background(Background),
335 Video(Video),
336 Break(Break),
337 ColourTransformation(ColourTransformation),
338 SpriteLegacy(SpriteLegacy),
339 AnimationLegacy(AnimationLegacy),
340 SampleLegacy(SampleLegacy),
341 StoryboardObject(Object),
342 AudioSample(AudioSample),
343}
344
345impl VersionedToString for Event {
346 fn to_string(&self, version: Version) -> Option<String> {
347 self.to_string_variables(version, &[])
348 }
349}
350
351impl Event {
352 pub fn to_string_variables(&self, version: Version, variables: &[Variable]) -> Option<String> {
353 match self {
354 Event::Comment(comment) => Some(format!("//{comment}")),
355 Event::Background(background) => background.to_string(version),
356 Event::Video(video) => video.to_string(version),
357 Event::Break(break_) => break_.to_string(version),
358 Event::ColourTransformation(colour_trans) => colour_trans.to_string(version),
359 Event::SpriteLegacy(sprite) => sprite.to_string_variables(version, variables),
360 Event::AnimationLegacy(animation) => animation.to_string_variables(version, variables),
361 Event::SampleLegacy(sample) => sample.to_string_variables(version, variables),
362 Event::StoryboardObject(object) => object.to_string_variables(version, variables),
363 Event::AudioSample(audio_sample) => Some(audio_sample.to_string(version).unwrap()),
364 }
365 }
366}
367
368fn commands_to_string_variables(
369 cmds: &[Command],
370 version: Version,
371 variables: &[Variable],
372) -> Option<String> {
373 let mut builder = Vec::new();
374 let mut indentation = 1usize;
375
376 for cmd in cmds {
377 builder.push(format!(
378 "{}{}",
379 " ".repeat(indentation),
380 cmd.to_string_variables(version, variables).unwrap()
381 ));
382
383 if let CommandProperties::Loop { commands, .. }
384 | CommandProperties::Trigger { commands, .. } = &cmd.properties
385 {
386 if commands.is_empty() {
387 continue;
388 }
389
390 let starting_indentation = indentation;
391 indentation += 1;
392
393 let mut current_cmds = commands;
394 let mut current_index = 0;
395 let mut cmds_stack = Vec::new();
397
398 loop {
399 let cmd = ¤t_cmds[current_index];
400 current_index += 1;
401
402 builder.push(format!(
403 "{}{}",
404 " ".repeat(indentation),
405 cmd.to_string_variables(version, variables).unwrap()
406 ));
407 match &cmd.properties {
408 CommandProperties::Loop { commands, .. }
409 | CommandProperties::Trigger { commands, .. }
410 if !commands.is_empty() =>
411 {
412 if current_index < current_cmds.len() {
415 cmds_stack.push((current_cmds, current_index, indentation));
416 }
417
418 current_cmds = commands;
419 current_index = 0;
420 indentation += 1;
421 }
422 _ => {
423 if current_index >= current_cmds.len() {
424 match cmds_stack.pop() {
426 Some((last_cmds, last_index, last_indentation)) => {
427 current_cmds = last_cmds;
428 current_index = last_index;
429 indentation = last_indentation;
430 }
431 None => break,
432 }
433 }
434 }
435 }
436 }
437
438 indentation = starting_indentation;
439 }
440 }
441
442 Some(builder.join("\n"))
443}
444
445pub trait EventWithCommands {
446 fn try_push_cmd(&mut self, cmd: Command, indentation: usize) -> Result<(), CommandPushError> {
447 if indentation == 1 {
448 self.commands_mut().push(cmd);
450 Ok(())
451 } else {
452 let mut last_cmd = match self.commands_mut().last_mut() {
453 Some(last_cmd) => last_cmd,
454 None => return Err(CommandPushError::InvalidIndentation(1, indentation)),
455 };
456
457 for i in 1..indentation {
458 last_cmd = if let CommandProperties::Loop { commands, .. }
459 | CommandProperties::Trigger { commands, .. } =
460 &mut last_cmd.properties
461 {
462 if i + 1 == indentation {
463 commands.push(cmd);
465 return Ok(());
466 } else {
467 match commands.last_mut() {
468 Some(sub_cmd) => sub_cmd,
469 None => {
470 return Err(CommandPushError::InvalidIndentation(
471 i - 1,
472 indentation,
473 ))
474 }
475 }
476 }
477 } else {
478 return Err(CommandPushError::InvalidIndentation(1, indentation));
479 };
480 }
481
482 unreachable!();
483 }
484 }
485
486 fn commands(&self) -> &[Command];
487
488 fn commands_mut(&mut self) -> &mut Vec<Command>;
489
490 fn to_string_cmd(&self, version: Version) -> Option<String>;
493
494 fn to_string_variables(&self, version: Version, variables: &[Variable]) -> Option<String> {
498 match self.to_string_cmd(version) {
499 Some(s) => {
500 let cmds = match commands_to_string_variables(self.commands(), version, variables) {
501 Some(mut cmds) => {
502 if !cmds.is_empty() {
503 cmds = format!("\n{cmds}");
504 }
505
506 cmds
507 }
508 None => return None,
509 };
510
511 Some(format!("{s}{cmds}"))
512 }
513 None => None,
514 }
515 }
516}