use std::collections::HashMap;
use super::chain::{self, Chain};
use super::lex::{Node, Token};
use super::{Parsed, Parser, check_name, duplicate};
use crate::{Animation, ErrorCode, Frame, Palette, Position, Sprite};
pub(crate) const MAX_TICKS: u32 = 65535;
pub(crate) struct AnimationDef<'a> {
name: Token<'a>,
looping: bool,
frames: Vec<(Chain<'a>, u32)>,
complete: bool,
}
impl<'a> Parser<'a, '_> {
pub(super) fn animation(&mut self, node: &Node<'a>) -> Parsed<()> {
let tokens = &node.line.tokens;
let name = *tokens
.get(1)
.ok_or_else(|| tokens[0].end_error("Expected a name for the animation".to_owned()))?;
check_name(name)?;
let looping = match tokens.get(2).map(|t| t.text) {
None | Some("loop") => true,
Some("once") => false,
Some(other) => {
return Err(tokens[2].error(
ErrorCode::Syntax,
format!("Expected `loop` or `once`, not `{other}`"),
));
}
};
if let Some(extra) = tokens.get(3) {
return Err(extra.error(
ErrorCode::Syntax,
format!("Unexpected `{}` after `{}`", extra.text, tokens[2].text),
));
}
if node.children.is_empty() {
return Err(name.error(
ErrorCode::Syntax,
format!(
"Animation `{}` has no frames; write one per line, such as `coin 6`",
name.text
),
));
}
let mut frames = Vec::new();
let mut complete = true;
for (i, child) in node.children.iter().enumerate() {
if i == self.limits.max_frames {
self.errors.push(child.line.first().error(
ErrorCode::LimitExceeded,
format!("More than the limit of {} frames", self.limits.max_frames),
));
complete = false;
break;
}
match self.frame(child) {
Ok(frame) => frames.push(frame),
Err(e) => {
self.errors.push(e);
complete = false;
}
}
}
self.animations.push(AnimationDef {
name,
looping,
frames,
complete,
});
Ok(())
}
fn frame(&mut self, node: &Node<'a>) -> Parsed<(Chain<'a>, u32)> {
let tokens = &node.line.tokens;
if let Some(child) = node.children.first() {
return Err(child.line.first().error(
ErrorCode::Syntax,
"Unexpected indented line under an animation frame",
));
}
let chain = chain::parse(tokens[0], self.limits)?;
let ticks = *tokens.get(1).ok_or_else(|| {
tokens[0].end_error(format!(
"Expected the frame's ticks after `{}`, such as `{} 6`",
tokens[0].text, tokens[0].text
))
})?;
if let Some(extra) = tokens.get(2) {
return Err(extra.error(
ErrorCode::Syntax,
format!(
"Unexpected `{}`; a frame is a sprite and its ticks, one per line",
extra.text
),
));
}
if ticks.text.is_empty() || !ticks.text.bytes().all(|b| b.is_ascii_digit()) {
return Err(ticks.error(
ErrorCode::Syntax,
format!("Expected a number of ticks, not `{}`", ticks.text),
));
}
match ticks.text.parse::<u32>() {
Ok(t) if (1..=MAX_TICKS).contains(&t) => Ok((chain, t)),
_ => Err(ticks.error(
ErrorCode::InvalidArgument,
format!("Ticks must be 1..={MAX_TICKS}, not {}", ticks.text),
)),
}
}
pub(super) fn resolve_animations(
&mut self,
palettes: &[Palette],
sprites: &[Sprite],
) -> (Vec<Animation>, Vec<(Sprite, Position)>) {
let defs = std::mem::take(&mut self.animations);
let mut animations = Vec::new();
let mut built: Vec<(Sprite, Position)> = Vec::new();
let mut seen: HashMap<&str, Position> = HashMap::new();
for def in &defs {
if let Some(&first) = seen.get(def.name.text) {
self.errors.push(duplicate(def.name, "Animation", first));
continue;
}
seen.insert(def.name.text, def.name.pos);
let mut frames = Vec::new();
let mut ok = def.complete;
for (written, ticks) in &def.frames {
let (chain, flip_x, flip_y) = written.split_flips();
let chain = &chain;
let name = chain.canonical();
let known = sprites.iter().any(|s| s.name == name)
|| built.iter().any(|(s, _)| s.name == name);
if !known {
let Some(base) = sprites.iter().find(|s| s.name == chain.base.text) else {
if !self.sprites.iter().any(|d| d.name.text == chain.base.text) {
self.errors.push(chain.base.error(
ErrorCode::UnknownName,
format!("Unknown sprite `{}`", chain.base.text),
));
}
ok = false;
continue;
};
match chain.apply(base, palettes, self.limits) {
Ok(mut sprite) => {
sprite.name = name.clone();
built.push((sprite, chain.base.pos));
}
Err(e) => {
self.errors.push(e);
ok = false;
continue;
}
}
}
frames.push(Frame {
sprite: name,
ticks: *ticks,
flip_x,
flip_y,
});
}
if ok {
animations.push(Animation {
name: def.name.text.to_owned(),
frames,
looping: def.looping,
});
}
}
self.animations = defs;
(animations, built)
}
}