Skip to content
Snippets Groups Projects
query.rs 10.7 KiB
Newer Older
Louis's avatar
Louis committed
use std::ops::{Deref, DerefMut};
use std::time::Duration;

use anyhow::{Error, Result};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;

use crate::definitions::{
	AnimationFrames, AnimationMode, AnimationOverride, AnimationPaused, AnimationSet,
	AnimationStatus, HasAnimations, HasDirectionalityAnimation, HasSimpleAnimations,
	SyncAnimationsToParent,
};
use crate::directionality::{Directionality, Horizontal, Vertical};
use crate::systems::AnimationCompleted;

/// Manage animated entities
#[derive(SystemParam)]
pub struct AnimationQuery<'w, 's> {
	commands: Commands<'w, 's>,
	animations: Res<'w, Assets<AnimationSet>>,
	inner: Query<
		'w,
		's,
		(
			Entity,
			&'static Handle<AnimationSet>,
			&'static mut AnimationMode,
			&'static mut AnimationStatus,
		),
		(
			Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>,
			Without<HasSimpleAnimations>,
			Without<SyncAnimationsToParent>,
		),
	>,
	inner_child: Query<
		'w,
		's,
		(
			Entity,
			&'static Parent,
			&'static Handle<AnimationSet>,
			&'static mut AnimationStatus,
		),
		(
			With<SyncAnimationsToParent>,
			Without<HasAnimations>,
			Without<HasDirectionalityAnimation>,
			Without<HasSimpleAnimations>,
		),
	>,
	direction: Query<'w, 's, &'static mut Directionality>,
	action_animation: Query<'w, 's, &'static mut AnimationOverride>,
Louis's avatar
Louis committed
	tile_sprite: Query<'w, 's, &'static mut TextureAtlasSprite>,
	paused: Query<'w, 's, Entity, With<AnimationPaused>>,
	events: EventWriter<'w, 's, AnimationCompleted>,
}

impl<'w, 's> Deref for AnimationQuery<'w, 's> {
	type Target = Query<
		'w,
		's,
		(
			Entity,
			&'static Handle<AnimationSet>,
			&'static mut AnimationMode,
			&'static mut AnimationStatus,
		),
		(
			Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>,
			Without<HasSimpleAnimations>,
			Without<SyncAnimationsToParent>,
		),
	>;

	fn deref(&self) -> &Self::Target {
		&self.inner
	}
}

impl<'w, 's> DerefMut for AnimationQuery<'w, 's> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.inner
	}
}

impl<'w, 's> AnimationQuery<'w, 's> {
	/// Given an entity, ensure that the currently playing animation matches
	/// the provided name. This method does not change the current animation
	/// step
	pub fn assert_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
		let (_, _, _, mut status) = self.inner.get_mut(e)?;
		status.assert_animation(name);
		Ok(())
	}

	/// Given an entity, start an animation from that entity's animation set
	/// from frame 0. If the currently playing animation is requested, it
	/// will be restarted
	pub fn start_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
		let (_, _, _, mut status) = self.inner.get_mut(e)?;
		status.start_animation(name);
		Ok(())
	}

	/// Given an entity, start an animation from that entity's animation set
	/// from frame 0. If the currently playing animation is requested, no change
	/// will happen and it will continue to play uninterrupted
	pub fn start_or_continue_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
		let (_, _, _, mut status) = self.inner.get_mut(e)?;
		status.start_or_continue(name);
		Ok(())
	}

	/// Given an entity, start playing an animation once and then switch
	/// to a looped version of another animation. If the currently playing
	/// animation is requested, it will be restarted
	pub fn start_animation_and_then<T: ToString, Y: ToString>(
		&mut self,
		e: Entity,
		name: T,
		next: Y,
	) -> Result<()> {
		let (_, _, mut mode, mut status) = self.inner.get_mut(e)?;

		*mode = AnimationMode::OnceThenPlay(next.to_string());
		status.active_name = name.to_string();
		status.active_step = 0;

		Ok(())
	}

	/// Given an entity, start playing an animation once and then switch
	/// to a looped version of another animation. If the currently playing
	/// animation is requested, it will be restarted
	pub fn continue_animation_and_then<T: ToString, Y: ToString>(
		&mut self,
		e: Entity,
		name: T,
		next: Y,
	) -> Result<()> {
		let (_, _, mut mode, mut status) = self.inner.get_mut(e)?;
		let name = name.to_string();

		*mode = AnimationMode::OnceThenPlay(next.to_string());
		if status.active_name != name {
			status.active_name = name;
			status.active_step = 0;
		}
		Ok(())
	}

	/// Set an entity's animations to play one time. The animation
	/// will pause on the last frame
	pub fn play_once(&mut self, e: Entity) -> Result<()> {
		let (_, _, mut mode, _) = self.inner.get_mut(e)?;
		*mode = AnimationMode::Once;
		Ok(())
	}

	/// Set an entity's animations to loop continuously. The
	/// animation will reset to the start once it has reached the
	/// last frame
	pub fn play_loop(&mut self, e: Entity) -> Result<()> {
		let (_, _, mut mode, _) = self.inner.get_mut(e)?;
		*mode = AnimationMode::Loop;
		Ok(())
	}

	/// Set an entity's animations to play one time. The animation
	/// will be changed to the specified set once the current animation
	/// has reached the last frame
	pub fn play_next<T: ToString>(&mut self, e: Entity, next: T) -> Result<()> {
		let (_, _, mut mode, _) = self.inner.get_mut(e)?;
		*mode = AnimationMode::OnceThenPlay(next.to_string());
		Ok(())
	}

	/// Measure the amount of time it will take an animation to run.
	/// The exact time that an animation runs for will differ slightly
	/// from this, due to the minimum time increment being 1 frame
	/// (16ms when running at 60fps)
	pub fn get_animation_duration<T: ToString>(
		&self,
		handle: Handle<AnimationSet>,
		name: T,
	) -> Result<Duration> {
		let sheet = self
			.animations
			.get(&handle)
			.ok_or_else(|| anyhow::Error::msg("Failed to fetch animation set"))?;

		let details = sheet.get(name.to_string().as_str()).ok_or_else(|| {
			anyhow::Error::msg(format!("Missing animation from set: {}", name.to_string()))
		})?;

		Ok(Duration::from_secs_f32(
			details.frame_secs * details.frames.len() as f32,
		))
	}

	pub fn resolve_animation(
		&self,
		handle: &Handle<AnimationSet>,
		name: &String,
	) -> Option<&AnimationFrames> {
		self.animations
			.get(handle)
			.and_then(|sheet| sheet.get(name))
	}

	/// Check whether or not an entity is currently paused. If so,
	/// the entity will not tick its animation state
	pub fn is_paused(&self, e: Entity) -> bool {
		self.paused.get(e).is_ok()
	}

	/// Attach a "paused" marker to this entity that will cause
	/// standard animations to not tick. This is safe to call
	/// with an entity that might already be paused; no effect will
	/// take place
	pub fn pause(&mut self, e: Entity) {
		self.commands.entity(e).insert(AnimationPaused);
	}

	/// Remove any "paused" markers from this entity, which will cause
	/// standard animations to resume ticking. This is safe to call
	/// with an entity that might already be paused; no effect will
	/// take place
	pub fn unpause(&mut self, e: Entity) {
		self.commands.entity(e).remove::<AnimationPaused>();
	}

	pub fn set_direction(&mut self, e: Entity, horizontal: Horizontal, vertical: Vertical) {
		if let Ok(mut direction) = self.direction.get_mut(e) {
			direction.horizontal = horizontal;
			direction.vertical = vertical;
		}
	}
	pub fn set_partial_direction(
		&mut self,
		e: Entity,
		horizontal: Option<Horizontal>,
		vertical: Option<Vertical>,
	) {
		if let Ok(mut direction) = self.direction.get_mut(e) {
			if let Some(horizontal) = horizontal {
				direction.horizontal = horizontal;
			}
			if let Some(vertical) = vertical {
				direction.vertical = vertical;
			}
		}
	}

	/// Apply a number of delta seconds to all unpaused animations
	pub fn tick_all(&mut self, dt: f32) -> Result<()> {
		for (entity, handle, mut mode, mut status) in &mut self.inner {
			if self.paused.get(entity).is_ok() {
				continue;
			}
			let sheet = match self.animations.get(handle) {
				Some(sheet) => sheet,
				None => continue,
			};

			if let Ok(mut status) = self.action_animation.get_mut(entity) {
				let current = match sheet.get(&status.name) {
					Some(set) => set,
					None => {
						self.commands.entity(entity).remove::<AnimationOverride>();
						continue;
Louis's avatar
Louis committed
					}
				};

				status.frame_time += dt;
				while status.frame_time >= current.frame_secs {
					status.frame_time -= current.frame_secs;
					status.frame_step += 1;
				if status.frame_step >= current.frames.len() {
Louis's avatar
Louis committed
					self.commands.entity(entity).remove::<AnimationOverride>();
					self.events.send(AnimationCompleted {
						entity,
						user_data: status.user_data,
Louis's avatar
Louis committed
					});
Louis's avatar
Louis committed
					status.frame_step = current.frames.len() - 1;
				if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) {
					sprite.index = current.frames[status.frame_step];
				}
			} else {
				let mut current = match sheet.get(&status.active_name) {
					Some(set) => set,
					None => continue,
				};

				status.frame_time += dt;
				while status.frame_time >= current.frame_secs {
					status.frame_time -= current.frame_secs;
					status.active_step += 1;
				}

				if status.active_step >= current.frames.len() {
					match mode.clone() {
						AnimationMode::Loop => {
							status.active_step = 0;
						}
						AnimationMode::Once => {
							status.active_step = current.frames.len() - 1;
						}
						AnimationMode::OnceThenPlay(next) => {
							*mode = AnimationMode::Loop;
							status.active_name = next.clone();
							status.frame_time = 0.0;
							status.active_step = 0;

							current = match sheet.get(&status.active_name) {
								Some(set) => set,
								None => continue,
							};
						}
					}
				}

				if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) {
					sprite.index = current.frames[status.active_step];
				}
Louis's avatar
Louis committed
			}
		}

		Ok(())
	}

	/// Syncs all animations that are set to track their parents
	pub fn sync_parent_animations(&mut self) {
		for (entity, parent, handle, mut status) in &mut self.inner_child {
			if let Ok((_, _, _, parent_status)) = self.inner.get(**parent) {
				*status = parent_status.clone();
			}

			if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) {
				if let Some(current) = self
					.animations
					.get(handle)
					.and_then(|sheet| sheet.get(&status.active_name))
				{
					sprite.index = current.frames[status.active_step];
				}
			}
		}
	}

	pub fn apply_directionality_to<T: ToString>(&mut self, action: T, e: Entity) -> Result<()> {
		if self.paused.get(e).is_ok() {
			return Ok(());
		}

		let (_, animations, _, mut status) = self.inner.get_mut(e)?;
		let direction = self.direction.get(e)?;
		let over = self.action_animation.get(e).ok();
		let set = self
			.animations
			.get(animations)
			.ok_or_else(|| Error::msg("Missing Animation"))?;

		let fallback = over
			.map(|o| o.name.clone())
			.unwrap_or_else(|| action.to_string());

		let directed = format!("{}_{}", &fallback, direction);

		// log::info!("Directed - {}", directed);

		if set.contains_key(&directed) {
			status.start_or_continue(directed);
		} else {
			status.start_or_continue(fallback);
		}

		Ok(())
	}
}