diff --git a/korangar/src/input/mode.rs b/korangar/src/input/mode.rs index 3a82f7bd..0d56c036 100644 --- a/korangar/src/input/mode.rs +++ b/korangar/src/input/mode.rs @@ -9,7 +9,8 @@ use crate::graphics::Texture; use crate::interface::application::InterfaceSettings; use crate::interface::resource::{ItemSource, SkillSource}; use crate::inventory::Skill; -use crate::loaders::{Actions, AnimationState, ResourceMetadata, Sprite}; +use crate::loaders::{ResourceMetadata, Sprite}; +use crate::world::{Actions, SpriteAnimationState}; #[derive(Default)] pub enum MouseInputMode { @@ -27,7 +28,7 @@ pub enum MouseInputMode { pub enum Grabbed { Texture(Arc), - Action(Arc, Arc, AnimationState), + Action(Arc, Arc, SpriteAnimationState), } impl MouseInputMode { diff --git a/korangar/src/interface/cursor/mod.rs b/korangar/src/interface/cursor/mod.rs index df4d418b..b0de3519 100644 --- a/korangar/src/interface/cursor/mod.rs +++ b/korangar/src/interface/cursor/mod.rs @@ -7,26 +7,27 @@ use super::application::InterfaceSettings; use super::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::graphics::Color; use crate::input::Grabbed; -use crate::loaders::{ActionLoader, Actions, AnimationState, Sprite, SpriteLoader}; +use crate::loaders::{ActionLoader, Sprite, SpriteLoader}; use crate::renderer::{GameInterfaceRenderer, SpriteRenderer}; +use crate::world::{Actions, SpriteAnimationState}; #[allow(dead_code)] -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum MouseCursorState { - Default, - Dialog, - Click, - Unsure0, - RotateCamera, - Attack, - Attack1, - Warp, - NoAction, - Grab, - Unsure1, - Unsure2, - WarpFast, - Unsure3, + Default = 0, + Dialog = 1, + Click = 2, + Unsure0 = 3, + RotateCamera = 4, + Attack = 5, + Attack1 = 6, + Warp = 7, + NoAction = 8, + Grab = 9, + Unsure1 = 10, + Unsure2 = 11, + WarpFast = 12, + Unsure3 = 13, } impl From for usize { @@ -38,7 +39,8 @@ impl From for usize { pub struct MouseCursor { sprite: Arc, actions: Arc, - animation_state: AnimationState, + cursor_state: MouseCursorState, + animation_state: SpriteAnimationState, shown: bool, } @@ -46,12 +48,13 @@ impl MouseCursor { pub fn new(sprite_loader: &mut SpriteLoader, action_loader: &mut ActionLoader) -> Self { let sprite = sprite_loader.get("cursors.spr").unwrap(); let actions = action_loader.get("cursors.act").unwrap(); - let animation_state = AnimationState::new(MouseCursorState::Default, ClientTick(0)); + let animation_state = SpriteAnimationState::new(ClientTick(0)); let shown = true; Self { sprite, actions, + cursor_state: MouseCursorState::Default, animation_state, shown, } @@ -69,18 +72,12 @@ impl MouseCursor { self.animation_state.update(client_tick); } - // TODO: this is just a workaround until i find a better solution to make the - // cursor always look correct. - pub fn set_start_time(&mut self, client_tick: ClientTick) { - self.animation_state.start_time = client_tick; - } - pub fn set_state(&mut self, state: MouseCursorState, client_tick: ClientTick) { - if self.animation_state.action != state { + if self.cursor_state != state { + self.cursor_state = state; + self.animation_state.action_base_offset = usize::from(self.cursor_state); self.animation_state.start_time = client_tick; } - - self.animation_state.action = state; } #[cfg_attr(feature = "debug", korangar_debug::profile("render mouse cursor"))] @@ -106,7 +103,7 @@ impl MouseCursor { Color::WHITE, false, ), - Grabbed::Action(sprite, actions, animation_state) => actions.render( + Grabbed::Action(sprite, actions, animation_state) => actions.render_sprite( renderer, &sprite, &animation_state, @@ -118,13 +115,13 @@ impl MouseCursor { } } - // TODO: figure out how this is actually supposed to work - let direction = match self.animation_state.action { + // TODO: Figure out how this is actually supposed to work + let direction = match self.cursor_state { MouseCursorState::Default | MouseCursorState::Click | MouseCursorState::RotateCamera => 0, _ => 7, }; - self.actions.render( + self.actions.render_sprite( renderer, &self.sprite, &self.animation_state, diff --git a/korangar/src/interface/elements/miscellanious/skill.rs b/korangar/src/interface/elements/miscellanious/skill.rs index 358dcee7..ed143517 100644 --- a/korangar/src/interface/elements/miscellanious/skill.rs +++ b/korangar/src/interface/elements/miscellanious/skill.rs @@ -101,7 +101,7 @@ impl Element for SkillBox { renderer.render_background(CornerRadius::uniform(5.0), background_color); if let Some(skill) = &self.skill { - skill.actions.render( + skill.actions.render_sprite( renderer.renderer, &skill.sprite, &skill.animation_state, diff --git a/korangar/src/inventory/skills.rs b/korangar/src/inventory/skills.rs index ac40a552..a299e7dd 100644 --- a/korangar/src/inventory/skills.rs +++ b/korangar/src/inventory/skills.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState}; use ragnarok_packets::{ClientTick, SkillId, SkillInformation, SkillLevel, SkillType}; -use crate::loaders::{ActionLoader, ActionType, Actions, AnimationState, Sprite, SpriteLoader}; +use crate::loaders::{ActionLoader, Sprite, SpriteLoader}; +use crate::world::{Actions, SpriteAnimationState}; #[derive(Clone, Debug)] pub struct Skill { @@ -13,7 +14,7 @@ pub struct Skill { pub skill_name: String, pub sprite: Arc, pub actions: Arc, - pub animation_state: AnimationState, + pub animation_state: SpriteAnimationState, } #[derive(Default)] @@ -22,7 +23,13 @@ pub struct SkillTree { } impl SkillTree { - pub fn fill(&mut self, sprite_loader: &mut SpriteLoader, action_loader: &mut ActionLoader, skill_data: Vec) { + pub fn fill( + &mut self, + sprite_loader: &mut SpriteLoader, + action_loader: &mut ActionLoader, + skill_data: Vec, + client_tick: ClientTick, + ) { let skills = skill_data .into_iter() .map(|skill_data| { @@ -37,8 +44,7 @@ impl SkillTree { skill_name: skill_data.skill_name, sprite, actions, - // FIX: give correct client tick - animation_state: AnimationState::new(ActionType::Idle, ClientTick(0)), + animation_state: SpriteAnimationState::new(client_tick), } }) .collect(); diff --git a/korangar/src/loaders/action/mod.rs b/korangar/src/loaders/action/mod.rs index 729d606a..788e06b3 100644 --- a/korangar/src/loaders/action/mod.rs +++ b/korangar/src/loaders/action/mod.rs @@ -1,217 +1,22 @@ use std::num::{NonZeroU32, NonZeroUsize}; -use std::ops::Mul; use std::sync::Arc; -use cgmath::{Array, Vector2}; -use derive_new::new; -use korangar_audio::{AudioEngine, SoundEffectKey}; +use korangar_audio::AudioEngine; #[cfg(feature = "debug")] use korangar_debug::logging::{print_debug, Colorize, Timer}; -use korangar_interface::elements::{ElementCell, PrototypeElement}; -use korangar_util::container::{Cacheable, SimpleCache}; +use korangar_util::container::SimpleCache; use korangar_util::FileLoader; use ragnarok_bytes::{ByteReader, FromBytes}; -use ragnarok_formats::action::{Action, ActionsData}; +use ragnarok_formats::action::ActionsData; use ragnarok_formats::version::InternalVersion; -use ragnarok_packets::ClientTick; use super::error::LoadError; -use super::Sprite; -use crate::graphics::Color; -use crate::interface::application::InterfaceSettings; -use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::loaders::{GameFileLoader, FALLBACK_ACTIONS_FILE}; -use crate::renderer::SpriteRenderer; +use crate::world::{ActionEvent, Actions}; 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, - Dead = 8, -} - -impl From for usize { - fn from(value: ActionType) -> Self { - value as 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 for ActionEvent { - fn to_element(&self, display: String) -> ElementCell { - 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 { - pub action: T, - pub start_time: ClientTick, - #[new(default)] - pub time: u32, - #[new(default)] - pub duration: Option, - #[new(default)] - pub factor: Option, -} - -impl AnimationState { - pub fn idle(&mut self, client_tick: ClientTick) { - self.action = ActionType::Idle; - self.start_time = client_tick; - self.duration = None; - self.factor = None; - } - - pub fn walk(&mut self, movement_speed: usize, client_tick: ClientTick) { - self.action = ActionType::Walk; - self.start_time = client_tick; - self.duration = None; - self.factor = Some(movement_speed as f32 * 100.0 / 150.0); - } - - pub fn dead(&mut self, client_tick: ClientTick) { - self.action = ActionType::Dead; - self.start_time = client_tick; - self.duration = None; - self.factor = None; - } -} - -impl AnimationState { - pub fn update(&mut self, client_tick: ClientTick) { - let mut time = client_tick.0.saturating_sub(self.start_time.0); - - // TODO: make everything have a duration so that we can update the start_time - // from time to time so that animations won't start to drop frames as - // soon as start_time - client_tick can no longer be stored in an f32 - // accurately. When fixed remove set_start_time in MouseCursor. - if let Some(duration) = self.duration - && time > duration - { - //self.action = self.next_action; - self.start_time = client_tick; - self.duration = None; - - time = 0; - } - - self.time = time; - } -} - -#[derive(Debug, PrototypeElement)] -pub struct Actions { - pub actions: Vec, - pub delays: Vec, - #[hidden_element] - pub events: Vec, - #[cfg(feature = "debug")] - actions_data: ActionsData, -} - -impl Actions { - pub fn render( - &self, - renderer: &impl SpriteRenderer, - sprite: &Sprite, - animation_state: &AnimationState, - position: ScreenPosition, - camera_direction: usize, - color: Color, - application: &InterfaceSettings, - ) where - T: Into + Copy, - { - let direction = camera_direction % 8; - 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 - .map(|factor| delay * (factor / 5.0)) - .unwrap_or_else(|| delay * 50.0); - - let frame = animation_state - .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 timing to an f32. When - // fixed remove set_start_time in MouseCursor. - - let motion = &action.motions[frame as usize % action.motions.len()]; - - 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; - }; - - let offset = sprite_clip.position.map(|component| component as f32); - let dimesions = sprite_clip - .size - .unwrap_or_else(|| { - let image_size = texture.get_size(); - Vector2::new(image_size.width, image_size.height) - }) - .map(|component| component as f32); - let zoom = sprite_clip.zoom.unwrap_or(1.0) * application.get_scaling_factor(); - let zoom2 = sprite_clip.zoom2.unwrap_or_else(|| Vector2::from_value(1.0)); - - let final_size = dimesions.zip(zoom2, f32::mul) * zoom; - let final_position = Vector2::new(position.left, position.top) + offset - final_size / 2.0; - - let final_size = ScreenSize { - width: final_size.x, - height: final_size.y, - }; - - let final_position = ScreenPosition { - left: final_position.x, - top: final_position.y, - }; - - let screen_clip = ScreenClip { - left: 0.0, - top: 0.0, - right: f32::MAX, - bottom: f32::MAX, - }; - - renderer.render_sprite(texture.clone(), final_position, final_size, screen_clip, color, false); - } - } -} - -impl Cacheable for Actions { - fn size(&self) -> usize { - size_of_val(&self.actions) - } -} - pub struct ActionLoader { game_file_loader: Arc, audio_engine: Arc>, diff --git a/korangar/src/loaders/animation/mod.rs b/korangar/src/loaders/animation/mod.rs index 02da31b3..533ce41a 100644 --- a/korangar/src/loaders/animation/mod.rs +++ b/korangar/src/loaders/animation/mod.rs @@ -9,8 +9,8 @@ use korangar_util::container::SimpleCache; use num::Zero; use super::error::LoadError; -use crate::loaders::{ActionEvent, ActionLoader, SpriteLoader}; -use crate::world::{Animation, AnimationData, AnimationFrame, AnimationFramePart, AnimationPair}; +use crate::loaders::{ActionLoader, SpriteLoader}; +use crate::world::{ActionEvent, Animation, AnimationData, AnimationFrame, AnimationFramePart, AnimationPair}; use crate::{Color, EntityType}; const MAX_CACHE_COUNT: u32 = 256; diff --git a/korangar/src/main.rs b/korangar/src/main.rs index a03f460e..6c3ec9cf 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -994,9 +994,6 @@ impl Client { self.particle_holder.clear(); let _ = self.networking_system.map_loaded(); - // TODO: This is just a workaround until I find a better solution to make the - // cursor always look correct. - self.mouse_cursor.set_start_time(client_tick); self.game_timer.set_client_tick(client_tick); } NetworkEvent::CharacterCreated { character_information } => { @@ -1106,10 +1103,6 @@ impl Client { self.effect_holder.clear(); self.point_light_manager.clear(); let _ = self.networking_system.map_loaded(); - - // TODO: This is just a workaround until I find a better solution to make the - // cursor always look correct. - self.mouse_cursor.set_start_time(client_tick); } NetworkEvent::SetPlayerPosition(player_position) => { let player_position = Vector2::new(player_position.x, player_position.y); @@ -1195,7 +1188,7 @@ impl Client { } NetworkEvent::SkillTree(skill_information) => { self.player_skill_tree - .fill(&mut self.sprite_loader, &mut self.action_loader, skill_information); + .fill(&mut self.sprite_loader, &mut self.action_loader, skill_information, client_tick); } NetworkEvent::UpdateEquippedPosition { index, equipped_position } => { self.player_inventory.update_equipped_position(index, equipped_position); diff --git a/korangar/src/world/action/mod.rs b/korangar/src/world/action/mod.rs new file mode 100644 index 00000000..f9e08e57 --- /dev/null +++ b/korangar/src/world/action/mod.rs @@ -0,0 +1,131 @@ +use std::ops::Mul; + +use cgmath::{Array, Vector2}; +use derive_new::new; +use korangar_audio::SoundEffectKey; +use korangar_interface::elements::{ElementCell, PrototypeElement}; +use korangar_util::container::Cacheable; +use ragnarok_formats::action::{Action, ActionsData}; +use ragnarok_packets::ClientTick; + +use crate::graphics::Color; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::loaders::Sprite; +use crate::renderer::SpriteRenderer; + +#[derive(Clone, Debug, new)] +pub struct SpriteAnimationState { + #[new(default)] + pub action_base_offset: usize, + pub start_time: ClientTick, + #[new(default)] + pub time: u32, +} + +impl SpriteAnimationState { + pub fn update(&mut self, client_tick: ClientTick) { + self.time = client_tick.0.wrapping_sub(self.start_time.0); + } +} + +#[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 for ActionEvent { + fn to_element(&self, display: String) -> ElementCell { + match self { + Self::Sound { .. } => PrototypeElement::to_element(&"Sound", display), + Self::Attack => PrototypeElement::to_element(&"Attack", display), + Self::Unknown => PrototypeElement::to_element(&"Unknown", display), + } + } +} + +#[derive(Debug, PrototypeElement)] +pub struct Actions { + pub actions: Vec, + pub delays: Vec, + #[hidden_element] + pub events: Vec, + #[cfg(feature = "debug")] + pub actions_data: ActionsData, +} + +impl Actions { + pub fn render_sprite( + &self, + renderer: &impl SpriteRenderer, + sprite: &Sprite, + animation_state: &SpriteAnimationState, + position: ScreenPosition, + camera_direction: usize, + color: Color, + application: &InterfaceSettings, + ) { + let direction = camera_direction % 8; + let animation_action = animation_state.action_base_offset * 8 + direction; + let action = &self.actions[animation_action % self.actions.len()]; + let delay = self.delays[animation_action % self.delays.len()]; + let factor = delay * 50.0; + + // We must use f64 here, so that the microsecond u32 value of + // `animation_state.time` can always be properly represented. + let frame = (f64::from(animation_state.time) / f64::from(factor)) as usize; + + let motion = &action.motions[frame % action.motions.len()]; + + 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; + }; + + let offset = sprite_clip.position.map(|component| component as f32); + let dimensions = sprite_clip + .size + .unwrap_or_else(|| { + let image_size = texture.get_size(); + Vector2::new(image_size.width, image_size.height) + }) + .map(|component| component as f32); + let zoom = sprite_clip.zoom.unwrap_or(1.0) * application.get_scaling_factor(); + let zoom2 = sprite_clip.zoom2.unwrap_or_else(|| Vector2::from_value(1.0)); + + let final_size = dimensions.zip(zoom2, f32::mul) * zoom; + let final_position = Vector2::new(position.left, position.top) + offset - final_size / 2.0; + + let final_size = ScreenSize { + width: final_size.x, + height: final_size.y, + }; + + let final_position = ScreenPosition { + left: final_position.x, + top: final_position.y, + }; + + let screen_clip = ScreenClip { + left: 0.0, + top: 0.0, + right: f32::MAX, + bottom: f32::MAX, + }; + + renderer.render_sprite(texture.clone(), final_position, final_size, screen_clip, color, false); + } + } +} + +impl Cacheable for Actions { + fn size(&self) -> usize { + size_of_val(&self.actions) + } +} diff --git a/korangar/src/world/animation/mod.rs b/korangar/src/world/animation/mod.rs index 32762ea2..7024b4ac 100644 --- a/korangar/src/world/animation/mod.rs +++ b/korangar/src/world/animation/mod.rs @@ -3,17 +3,121 @@ use std::sync::Arc; use cgmath::{Array, Matrix4, Point3, Transform, Vector2, Vector3, Zero}; use korangar_interface::elements::PrototypeElement; use korangar_util::container::Cacheable; -use ragnarok_packets::{Direction, EntityId}; +use ragnarok_packets::{ClientTick, Direction, EntityId}; #[cfg(feature = "debug")] use crate::graphics::DebugRectangleInstruction; use crate::graphics::{Color, EntityInstruction}; -use crate::loaders::{ActionEvent, ActionType, Actions, AnimationState, Sprite}; -use crate::world::{Camera, EntityType}; +use crate::loaders::Sprite; +use crate::world::{ActionEvent, Actions, Camera, EntityType}; const TILE_SIZE: f32 = 10.0; const SPRITE_SCALE: f32 = 1.4; +#[allow(dead_code)] +#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum AnimationActionType { + Attack1, + Attack2, + Attack3, + Die, + Freeze1, + Freeze2, + Hurt, + #[default] + Idle, + Pickup, + ReadyFight, + Sit, + Skill, + Special, + Walk, +} + +impl AnimationActionType { + pub fn action_base_offset(&self, entity_type: EntityType) -> usize { + match entity_type { + EntityType::Hidden | EntityType::Player => match self { + AnimationActionType::Idle => 0, + AnimationActionType::Walk => 1, + AnimationActionType::Sit => 2, + AnimationActionType::Pickup => 3, + AnimationActionType::ReadyFight => 4, + AnimationActionType::Attack1 => 5, + AnimationActionType::Hurt => 6, + AnimationActionType::Freeze1 => 7, + AnimationActionType::Die => 8, + AnimationActionType::Freeze2 => 9, + AnimationActionType::Attack2 => 10, + AnimationActionType::Attack3 => 11, + AnimationActionType::Skill => 12, + _ => 0, + }, + EntityType::Npc | EntityType::Monster => match self { + AnimationActionType::Idle => 0, + AnimationActionType::Walk => 1, + AnimationActionType::Attack1 => 2, + AnimationActionType::Hurt => 3, + AnimationActionType::Die => 4, + _ => 0, + }, + EntityType::Warp => 0, + } + } +} + +#[derive(Clone, Debug)] +pub struct AnimationState { + pub action_type: AnimationActionType, + pub action_base_offset: usize, + pub start_time: ClientTick, + pub time: u32, + pub duration: Option, + pub factor: Option, +} + +impl AnimationState { + pub fn new(entity_type: EntityType, start_time: ClientTick) -> Self { + let action_type = AnimationActionType::Idle; + Self { + action_type, + action_base_offset: action_type.action_base_offset(entity_type), + start_time, + time: 0, + duration: None, + factor: None, + } + } + + pub fn idle(&mut self, entity_type: EntityType, client_tick: ClientTick) { + self.action_type = AnimationActionType::Idle; + self.action_base_offset = self.action_type.action_base_offset(entity_type); + self.start_time = client_tick; + self.duration = None; + self.factor = None; + } + + pub fn walk(&mut self, entity_type: EntityType, movement_speed: usize, client_tick: ClientTick) { + self.action_type = AnimationActionType::Walk; + self.action_base_offset = self.action_type.action_base_offset(entity_type); + self.start_time = client_tick; + self.duration = None; + self.factor = Some(movement_speed as f32 * 100.0 / 150.0 / 5.0); + } + + pub fn dead(&mut self, entity_type: EntityType, client_tick: ClientTick) { + self.action_type = AnimationActionType::Die; + self.action_base_offset = self.action_type.action_base_offset(entity_type); + self.start_time = client_tick; + self.duration = None; + self.factor = None; + } + + pub fn update(&mut self, client_tick: ClientTick) { + self.time = client_tick.0.wrapping_sub(self.start_time.0); + } +} + #[derive(Clone, PrototypeElement)] pub struct AnimationData { pub animation_pair: Vec, @@ -84,8 +188,8 @@ impl Default for AnimationFramePart { impl AnimationData { pub fn get_frame(&self, animation_state: &AnimationState, camera: &dyn Camera, direction: Direction) -> &AnimationFrame { let camera_direction = camera.camera_direction(); - let direction_usize = (camera_direction + usize::from(direction)) & 7; - let animation_action_index = animation_state.action as usize * 8 + direction_usize; + let direction = (camera_direction + usize::from(direction)) & 7; + let animation_action_index = animation_state.action_type.action_base_offset(self.entity_type) * 8 + direction; let delay_index = animation_action_index % self.delays.len(); let animation_index = animation_action_index % self.animations.len(); @@ -93,22 +197,17 @@ impl AnimationData { let delay = self.delays[delay_index]; let animation = &self.animations[animation_index]; - let factor = animation_state - .factor - .map(|factor| delay * (factor / 5.0)) - .unwrap_or_else(|| delay * 50.0); + let factor = animation_state.factor.map(|factor| delay * factor).unwrap_or_else(|| delay * 50.0); let frame_time = animation_state .duration .map(|duration| animation_state.time * animation.frames.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 time to an f32. When - // fixed remove set_start_time in MouseCursor. let frame_index = frame_time as usize % animation.frames.len(); // Remove Doridori animation from Player - if self.entity_type == EntityType::Player && animation_state.action == ActionType::Idle { + if self.entity_type == EntityType::Player && animation_state.action_type == AnimationActionType::Idle { &animation.frames[0] } else { &animation.frames[frame_index] diff --git a/korangar/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs index c22c5466..961fb227 100644 --- a/korangar/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -22,13 +22,13 @@ use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenPosition, ScreenSize}; use crate::interface::theme::GameTheme; use crate::interface::windows::WindowCache; -use crate::loaders::{ActionEvent, ActionLoader, ActionType, AnimationLoader, AnimationState, GameFileLoader, ScriptLoader, SpriteLoader}; +use crate::loaders::{ActionLoader, AnimationLoader, GameFileLoader, ScriptLoader, SpriteLoader}; use crate::renderer::GameInterfaceRenderer; #[cfg(feature = "debug")] use crate::renderer::MarkerRenderer; #[cfg(feature = "debug")] use crate::world::MarkerIdentifier; -use crate::world::{AnimationData, Camera, Map}; +use crate::world::{ActionEvent, AnimationActionType, AnimationData, AnimationState, Camera, Map}; #[cfg(feature = "debug")] use crate::{Buffer, Color, ModelVertex}; @@ -69,13 +69,26 @@ pub struct Step { arrival_timestamp: u32, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum EntityType { - Warp, Hidden, - Player, - Npc, Monster, + Npc, + Player, + Warp, +} + +impl From for EntityType { + fn from(value: usize) -> Self { + match value { + 45 => EntityType::Warp, + 111 => EntityType::Hidden, // TODO: check that this is correct + 0..=44 | 4000..=5999 => EntityType::Player, + 46..=999 | 10000..=19999 => EntityType::Npc, + 1000..=3999 | 20000..=29999 => EntityType::Monster, + _ => EntityType::Npc, + } + } } #[derive(Copy, Clone, Default)] @@ -354,23 +367,14 @@ impl Common { let sex = entity_data.sex; let active_movement = None; - - let entity_type = match job_id { - 45 => EntityType::Warp, - 111 => EntityType::Hidden, // TODO: check that this is correct - // 111 | 139 => None, - 0..=44 | 4000..=5999 => EntityType::Player, - 46..=999 | 10000..=19999 => EntityType::Npc, - 1000..=3999 | 20000..=29999 => EntityType::Monster, - _ => EntityType::Npc, - }; + let entity_type = job_id.into(); let entity_part_files = get_entity_part_files(script_loader, entity_type, job_id, sex, Some(head)); let animation_data = animation_loader .get(sprite_loader, action_loader, entity_type, &entity_part_files) .unwrap(); let details = ResourceState::Unavailable; - let animation_state = AnimationState::new(ActionType::Idle, client_tick); + let animation_state = AnimationState::new(entity_type, client_tick); let mut common = Self { grid_position, @@ -474,7 +478,7 @@ impl Common { self.grid_position = position; self.position = map.get_world_position(position); self.active_movement = None; - self.animation_state.idle(client_tick); + self.animation_state.idle(self.entity_type, client_tick); } pub fn move_from_to( @@ -531,8 +535,8 @@ impl Common { if steps.len() > 1 { self.active_movement = Movement::new(steps, starting_timestamp.0).into(); - if self.animation_state.action != ActionType::Walk { - self.animation_state.walk(self.movement_speed, starting_timestamp); + if self.animation_state.action_type != AnimationActionType::Walk { + self.animation_state.walk(self.entity_type, self.movement_speed, starting_timestamp); } } } @@ -1058,9 +1062,8 @@ impl Entity { } pub fn set_hair(&mut self, hair_id: usize) { - match self { - Self::Player(player) => player.hair_id = hair_id, - _ => (), + if let Self::Player(player) = self { + player.hair_id = hair_id } } @@ -1104,11 +1107,13 @@ impl Entity { } pub fn set_dead(&mut self, client_tick: ClientTick) { - self.get_common_mut().animation_state.dead(client_tick); + let entity_type = self.get_entity_type(); + self.get_common_mut().animation_state.dead(entity_type, client_tick); } pub fn set_idle(&mut self, client_tick: ClientTick) { - self.get_common_mut().animation_state.idle(client_tick); + let entity_type = self.get_entity_type(); + self.get_common_mut().animation_state.idle(entity_type, client_tick); } pub fn update_health(&mut self, health_points: usize, maximum_health_points: usize) { diff --git a/korangar/src/world/mod.rs b/korangar/src/world/mod.rs index 0f1688ca..30000f80 100644 --- a/korangar/src/world/mod.rs +++ b/korangar/src/world/mod.rs @@ -1,3 +1,4 @@ +mod action; mod animation; mod cameras; mod effect; @@ -9,6 +10,7 @@ mod object; mod particles; mod sound; +pub use self::action::*; pub use self::animation::*; pub use self::cameras::*; pub use self::effect::*;