Skip to content

Commit

Permalink
Add entity animation sounds
Browse files Browse the repository at this point in the history
  • Loading branch information
hasenbanck committed Dec 27, 2024
1 parent 9507e02 commit 015cfb3
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 153 deletions.
68 changes: 59 additions & 9 deletions korangar/src/loaders/action/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use std::sync::Arc;

use cgmath::{Array, Vector2};
use derive_new::new;
use korangar_audio::{AudioEngine, SoundEffectKey};
#[cfg(feature = "debug")]
use korangar_debug::logging::{print_debug, Colorize, Timer};
use korangar_interface::elements::PrototypeElement;
use korangar_interface::elements::{ElementCell, PrototypeElement};
use korangar_util::container::{Cacheable, SimpleCache};
use korangar_util::FileLoader;
use ragnarok_bytes::{ByteReader, FromBytes};
Expand All @@ -25,11 +26,17 @@ use crate::renderer::SpriteRenderer;
const MAX_CACHE_COUNT: u32 = 256;
const MAX_CACHE_SIZE: usize = 64 * 1024 * 1024;

// TODO: NHA The numeric value of action types are based on the EntityType!
// For example "Dead" is 8 for the PC and 4 for a monster.
// This means we need to refactor the AnimationState, so that the mouse
// uses a different animation state struct (since we can't do simple usize
// conversions).
#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ActionType {
#[default]
Idle = 0,
Walk = 1,
Flinch = 2,
Dead = 8,
}

Expand All @@ -39,6 +46,26 @@ impl From<ActionType> for usize {
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum ActionEvent {
/// Start playing a WAV sound file.
Sound { key: SoundEffectKey },
/// An attack event when the "flinch" animation is played.
Attack,
/// Start playing a WAV sound file.
Unknown,
}

impl PrototypeElement<InterfaceSettings> for ActionEvent {
fn to_element(&self, display: String) -> ElementCell<InterfaceSettings> {
match self {
Self::Sound { .. } => PrototypeElement::to_element(&"Sound", display),
Self::Attack => PrototypeElement::to_element(&"Attack", display),
Self::Unknown => PrototypeElement::to_element(&"Unknown", display),
}
}
}

#[derive(Clone, Debug, new)]
pub struct AnimationState<T = ActionType> {
pub action: T,
Expand Down Expand Up @@ -100,6 +127,8 @@ impl<T> AnimationState<T> {
pub struct Actions {
pub actions: Vec<Action>,
pub delays: Vec<f32>,
#[hidden_element]
pub events: Vec<ActionEvent>,
#[cfg(feature = "debug")]
actions_data: ActionsData,
}
Expand All @@ -118,9 +147,9 @@ impl Actions {
T: Into<usize> + Copy,
{
let direction = camera_direction % 8;
let aa = animation_state.action.into() * 8 + direction;
let a = &self.actions[aa % self.actions.len()];
let delay = self.delays[aa % self.delays.len()];
let animation_action = animation_state.action.into() * 8 + direction;
let action = &self.actions[animation_action % self.actions.len()];
let delay = self.delays[animation_action % self.delays.len()];

let factor = animation_state
.factor
Expand All @@ -129,14 +158,14 @@ impl Actions {

let frame = animation_state
.duration
.map(|duration| animation_state.time * a.motions.len() as u32 / duration)
.map(|duration| animation_state.time * action.motions.len() as u32 / duration)
.unwrap_or_else(|| (animation_state.time as f32 / factor) as u32);
// TODO: work out how to avoid losing digits when casting timg to an f32. When
// TODO: work out how to avoid losing digits when casting timing to an f32. When
// fixed remove set_start_time in MouseCursor.

let fs = &a.motions[frame as usize % a.motions.len()];
let motion = &action.motions[frame as usize % action.motions.len()];

for sprite_clip in &fs.sprite_clips {
for sprite_clip in &motion.sprite_clips {
// `get` instead of a direct index in case a fallback was loaded
let Some(texture) = sprite.textures.get(sprite_clip.sprite_number as usize) else {
return;
Expand Down Expand Up @@ -186,13 +215,15 @@ impl Cacheable for Actions {

pub struct ActionLoader {
game_file_loader: Arc<GameFileLoader>,
audio_engine: Arc<AudioEngine<GameFileLoader>>,
cache: SimpleCache<String, Arc<Actions>>,
}

impl ActionLoader {
pub fn new(game_file_loader: Arc<GameFileLoader>) -> Self {
pub fn new(game_file_loader: Arc<GameFileLoader>, audio_engine: Arc<AudioEngine<GameFileLoader>>) -> Self {
Self {
game_file_loader,
audio_engine,
cache: SimpleCache::new(
NonZeroU32::new(MAX_CACHE_COUNT).unwrap(),
NonZeroUsize::new(MAX_CACHE_SIZE).unwrap(),
Expand Down Expand Up @@ -231,6 +262,24 @@ impl ActionLoader {
}
};

let events: Vec<ActionEvent> = actions_data
.events
.iter()
.enumerate()
.map(|(_index, event)| {
if event.name.ends_with(".wav") {
let key = self.audio_engine.load(&event.name);
ActionEvent::Sound { key }
} else if event.name == "atk" || event.name == "atk.txt" {
ActionEvent::Attack
} else {
#[cfg(feature = "debug")]
print_debug!("Found unknown event at index `{}`: {:?}", _index, event.name);
ActionEvent::Unknown
}
})
.collect();

#[cfg(feature = "debug")]
let saved_actions_data = actions_data.clone();

Expand All @@ -241,6 +290,7 @@ impl ActionLoader {
let sprite = Arc::new(Actions {
actions: actions_data.actions,
delays,
events,
#[cfg(feature = "debug")]
actions_data: saved_actions_data,
});
Expand Down
27 changes: 22 additions & 5 deletions korangar/src/loaders/animation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use korangar_util::container::SimpleCache;
use num::Zero;

use super::error::LoadError;
use crate::loaders::{ActionLoader, SpriteLoader};
use crate::loaders::{ActionEvent, ActionLoader, SpriteLoader};
use crate::world::{Animation, AnimationData, AnimationFrame, AnimationFramePart, AnimationPair};
use crate::{Color, EntityType};

Expand Down Expand Up @@ -155,7 +155,18 @@ impl AnimationLoader {
color,
..Default::default()
};

let event: Option<ActionEvent> = if let Some(event_id) = motion.event_id
&& event_id != -1
&& let Some(event) = animation_pair.actions.events.get(event_id as usize).copied()
{
Some(event)
} else {
None
};

let frame = AnimationFrame {
event,
size,
top_left: Vector2::zero(),
offset,
Expand Down Expand Up @@ -224,7 +235,7 @@ impl AnimationLoader {
let mut min_left = i32::MAX;
let mut max_right = 0;

// The player can change the sprite and causes an offset from a image to
// The player can change the sprite and causes an offset from an image to
// another.
if entity_type == EntityType::Player {
// Create a bounding box to standardize the size of the player sprite, ensuring
Expand Down Expand Up @@ -325,7 +336,7 @@ fn calculate_new_size(min_top: i32, max_bottom: i32, min_left: i32, max_right: i
}
let size_y = max_bottom - min_top + padding + 1;

return Vector2::new(size_x, size_y);
Vector2::new(size_x, size_y)
}

#[cfg(feature = "debug")]
Expand Down Expand Up @@ -380,11 +391,11 @@ fn calculate_scale_matrix(
) -> Matrix4<f32> {
// Scale the vertices (-1, 2), (-1, 0), (1, 2), (1, 0) to
// match the texture coordinates as specified above.
return Matrix4::from_nonuniform_scale(
Matrix4::from_nonuniform_scale(
(texture_bottom_right.x - texture_bottom_left.x) / 2.0,
(texture_top_left.y - texture_bottom_left.y) / 2.0,
1.0,
);
)
}

fn calculate_translation_matrix(
Expand Down Expand Up @@ -434,7 +445,9 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame {
},
..Default::default()
};

let frame = AnimationFrame {
event: None,
size: Vector2::new(1, 1),
top_left: Vector2::zero(),
offset: Vector2::zero(),
Expand All @@ -446,6 +459,7 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame {
#[cfg(feature = "debug")]
vertical_matrix: Matrix4::identity(),
};

return frame;
}

Expand All @@ -467,6 +481,8 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame {
new_frame_parts.append(&mut frame.frame_parts);
}

let event = frames.iter().filter_map(|frame| frame.event).next();

// The origin is set at (0,0).
//
// The top-left point of the rectangle is calculated as
Expand All @@ -478,6 +494,7 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame {
// The new offset is calculated as
// center_point - origin.
AnimationFrame {
event,
size: Vector2::new(new_width, new_height),
top_left: Vector2::zero(),
offset: Vector2::new(top_left_x + (new_width - 1) / 2, top_left_y + (new_height - 1) / 2),
Expand Down
Loading

0 comments on commit 015cfb3

Please sign in to comment.