diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index dc6b793961..5c3b4ef801 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1,10 +1,12 @@ +use crate::comp::CharacterState; use specs::{Component, DenseVecStorage, FlaggedStorage, HashMapStorage}; +use std::time::Duration; #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] -pub enum AbilityState { +pub enum CharacterAbility { BasicAttack { - /// Amount of energy required to use ability - cost: i32, + buildup_duration: Duration, + recover_duration: Duration, }, BasicBlock, Roll, @@ -14,30 +16,43 @@ pub enum AbilityState { cost: i32, }, } -impl Default for AbilityState { - fn default() -> Self { Self::BasicAttack { cost: -100 } } -} -impl Component for AbilityState { +impl Component for CharacterAbility { type Storage = DenseVecStorage; } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] pub struct AbilityPool { - pub primary: Option, - pub secondary: Option, - pub block: Option, - pub dodge: Option, + pub primary: Option, + pub secondary: Option, + pub block: Option, + pub dodge: Option, } -impl Default for AbilityPool { - fn default() -> Self { - Self { - primary: Some(AbilityState::default()), - // primary: Some(AbilityState::TimedCombo), - secondary: Some(AbilityState::BasicBlock), - block: None, - dodge: Some(AbilityState::Roll), +impl From for CharacterState { + fn from(ability: CharacterAbility) -> Self { + match ability { + CharacterAbility::BasicAttack { + buildup_duration, + recover_duration, + } => CharacterState::BasicAttack { + exhausted: false, + buildup_duration, + recover_duration, + }, + CharacterAbility::BasicBlock { .. } => CharacterState::BasicBlock {}, + CharacterAbility::Roll { .. } => CharacterState::Roll { + remaining_duration: Duration::from_millis(600), + }, + CharacterAbility::ChargeAttack { .. } => CharacterState::ChargeAttack { + remaining_duration: Duration::from_millis(600), + }, + CharacterAbility::TimedCombo { .. } => CharacterState::TimedCombo { + stage: 1, + stage_time_active: Duration::default(), + stage_exhausted: false, + can_transition: false, + }, } } } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index f7b4e4dbd6..4ba4af1e57 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -19,8 +19,8 @@ pub struct StateUpdate { #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] pub enum CharacterState { - Idle {}, - Climb {}, + Idle, + Climb, Sit {}, Equipping { /// The weapon being equipped @@ -32,16 +32,18 @@ pub enum CharacterState { /// The weapon being wielded tool: ToolData, }, - Glide {}, + Glide, /// A basic attacking state BasicAttack { - /// How long the state has until exiting - remaining_duration: Duration, + /// How long till the state deals damage + buildup_duration: Duration, + /// How long till the state remains after dealing damage + recover_duration: Duration, /// Whether the attack can deal more damage exhausted: bool, }, /// A basic blocking state - BasicBlock {}, + BasicBlock, ChargeAttack { /// How long the state has until exiting remaining_duration: Duration, @@ -53,8 +55,6 @@ pub enum CharacterState { /// A three-stage attack where play must click at appropriate times /// to continue attack chain. TimedCombo { - /// The tool this state will read to handle damage, etc. - tool: ToolData, /// `int` denoting what stage (of 3) the attack is in. stage: i8, /// How long current stage has been active diff --git a/common/src/comp/inventory/item.rs b/common/src/comp/inventory/item.rs index 2636d0a245..e24a107507 100644 --- a/common/src/comp/inventory/item.rs +++ b/common/src/comp/inventory/item.rs @@ -1,6 +1,6 @@ use crate::{ assets::{self, Asset}, - comp::AbilityState, + comp::CharacterAbility, effect::Effect, terrain::{Block, BlockKind}, }; @@ -25,47 +25,55 @@ pub enum ToolKind { Dagger, Staff, Shield, - Debug(Debug), -} - -impl Default for ToolKind { - fn default() -> Self { Self::Axe } + Debug(DebugKind), } impl ToolData { pub fn equip_time(&self) -> Duration { Duration::from_millis(self.equip_time_millis) } - pub fn attack_buildup_duration(&self) -> Duration { - Duration::from_millis(self.attack_buildup_millis) - } - - pub fn attack_recover_duration(&self) -> Duration { - Duration::from_millis(self.attack_recover_millis) - } - - pub fn attack_duration(&self) -> Duration { - self.attack_buildup_duration() + self.attack_recover_duration() - } - - pub fn get_primary_abilities(&self) -> Vec { - use AbilityState::*; - use SwordKind::*; + pub fn get_abilities(&self) -> Vec { + use CharacterAbility::*; + use DebugKind::*; use ToolKind::*; - let default_return = vec![AbilityState::default()]; - match self.kind { - Sword(kind) => match kind { - Rapier => vec![TimedCombo { cost: -150 }], - Scimitar => default_return, + Sword(_) => vec![TimedCombo { cost: -150 }], + Axe => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Hammer => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Bow => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Dagger => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Staff => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Shield => vec![BasicAttack { + buildup_duration: Duration::from_millis(1000), + recover_duration: Duration::from_millis(500), + }], + Debug(kind) => match kind { + Boost => vec![], + Possess => vec![], }, - _ => default_return, + + _ => vec![], } } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Debug { +pub enum DebugKind { Boost, Possess, } @@ -109,7 +117,7 @@ pub enum Ingredient { Grass, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ToolData { pub kind: ToolKind, equip_time_millis: u64, diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 9f15789a9d..13ff8449f4 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,7 +1,7 @@ pub mod item; // Reexports -pub use item::{Consumable, Debug, Item, ItemKind, SwordKind, ToolData, ToolKind}; +pub use item::{Consumable, DebugKind, Item, ItemKind, SwordKind, ToolData, ToolKind}; use crate::assets; use specs::{Component, FlaggedStorage, HashMapStorage}; diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 940c8f4130..8a6fe50631 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -16,7 +16,7 @@ mod stats; mod visual; // Reexports -pub use ability::{AbilityPool, AbilityState}; +pub use ability::{AbilityPool, CharacterAbility}; pub use admin::Admin; pub use agent::{Agent, Alignment}; pub use body::{ diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 91d78d35d7..f0135dd3c4 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -21,7 +21,7 @@ sum_type! { Mass(comp::Mass), Gravity(comp::Gravity), Sticky(comp::Sticky), - AbilityState(comp::AbilityState), + CharacterAbility(comp::CharacterAbility), AbilityPool(comp::AbilityPool), Attacking(comp::Attacking), CharacterState(comp::CharacterState), @@ -45,7 +45,7 @@ sum_type! { Mass(PhantomData), Gravity(PhantomData), Sticky(PhantomData), - AbilityState(PhantomData), + CharacterAbility(PhantomData), AbilityPool(PhantomData), Attacking(PhantomData), CharacterState(PhantomData), @@ -69,7 +69,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::AbilityState(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::CharacterAbility(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::AbilityPool(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Attacking(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), @@ -91,7 +91,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::AbilityState(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::CharacterAbility(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::AbilityPool(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Attacking(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), @@ -115,8 +115,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::Mass(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Gravity(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Sticky(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::AbilityState(_) => { - sync::handle_remove::(entity, world) + EcsCompPhantom::CharacterAbility(_) => { + sync::handle_remove::(entity, world) }, EcsCompPhantom::AbilityPool(_) => { sync::handle_remove::(entity, world) diff --git a/common/src/state.rs b/common/src/state.rs index 0e07b75659..5fdbc211f6 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -107,7 +107,7 @@ impl State { ecs.register_sync_marker(); // Register server -> all clients synced components. ecs.register::(); - ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/states/basic_attack.rs b/common/src/states/basic_attack.rs index 31343305c6..782a93f00d 100644 --- a/common/src/states/basic_attack.rs +++ b/common/src/states/basic_attack.rs @@ -18,51 +18,65 @@ pub fn behavior(data: &JoinData) -> StateUpdate { if let CharacterState::BasicAttack { exhausted, - remaining_duration, + buildup_duration, + recover_duration, } = data.character { let tool_kind = data.stats.equipment.main.as_ref().map(|i| i.kind); - if let Some(Tool(tool)) = tool_kind { - handle_move(data, &mut update); + handle_move(data, &mut update); - let mut new_exhausted = *exhausted; - - if !*exhausted && *remaining_duration < tool.attack_recover_duration() { + if buildup_duration != &Duration::default() { + // Start to swing + update.character = CharacterState::BasicAttack { + buildup_duration: buildup_duration + .checked_sub(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + recover_duration: *recover_duration, + exhausted: false, + }; + } else if !*exhausted { + // Swing hits + if let Some(Tool(tool)) = tool_kind { data.updater.insert(data.entity, Attacking { weapon: Some(tool), applied: false, hit_count: 0, }); - new_exhausted = true; } - let new_remaining_duration = remaining_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(); - - if let Some(attack) = data.attacking { - if attack.applied && attack.hit_count > 0 { - data.updater.remove::(data.entity); - update.energy.change_by(100, EnergySource::HitEnemy); - } - } - - // Tick down update.character = CharacterState::BasicAttack { - remaining_duration: new_remaining_duration, - exhausted: new_exhausted, + buildup_duration: *buildup_duration, + recover_duration: *recover_duration, + exhausted: true, }; - - // Check if attack duration has expired - if new_remaining_duration == Duration::default() { + } else if recover_duration != &Duration::default() { + // Recover from swing + update.character = CharacterState::BasicAttack { + buildup_duration: *buildup_duration, + recover_duration: recover_duration + .checked_sub(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + exhausted: true, + } + } else { + // Done + if let Some(Tool(tool)) = tool_kind { update.character = CharacterState::Wielding { tool }; data.updater.remove::(data.entity); + } else { + update.character = CharacterState::Idle; } - - update - } else { - update } + + // More handling + if let Some(attack) = data.attacking { + if attack.applied && attack.hit_count > 0 { + data.updater.remove::(data.entity); + update.energy.change_by(100, EnergySource::HitEnemy); + } + } + + update } else { update.character = CharacterState::Idle {}; update diff --git a/common/src/states/timed_combo.rs b/common/src/states/timed_combo.rs index aa176ead02..18380a2fc0 100644 --- a/common/src/states/timed_combo.rs +++ b/common/src/states/timed_combo.rs @@ -17,77 +17,81 @@ pub fn behavior(data: &JoinData) -> StateUpdate { }; if let CharacterState::TimedCombo { - tool, stage, stage_time_active, stage_exhausted, can_transition, } = data.character { - let mut new_can_transition = *can_transition; - let mut new_stage_exhausted = *stage_exhausted; - let new_stage_time_active = stage_time_active - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or(Duration::default()); + // Sorry adam, I don't want to fix this rn, check out basic_attack to + // see how to do it + // + /* + let mut new_can_transition = *can_transition; + let mut new_stage_exhausted = *stage_exhausted; + let new_stage_time_active = stage_time_active + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or(Duration::default()); - match stage { - 1 => { - if new_stage_time_active > tool.attack_buildup_duration() { - if !*stage_exhausted { - // Try to deal damage - data.updater.insert(data.entity, Attacking { - weapon: Some(*tool), - applied: false, - hit_count: 0, - }); - new_stage_exhausted = true; - } else { - // Make sure to remove Attacking component - data.updater.remove::(data.entity); + match stage { + 1 => { + if new_stage_time_active > tool.attack_buildup_duration() { + if !*stage_exhausted { + // Try to deal damage + data.updater.insert(data.entity, Attacking { + weapon: Some(*tool), + applied: false, + hit_count: 0, + }); + new_stage_exhausted = true; + } else { + // Make sure to remove Attacking component + data.updater.remove::(data.entity); + } + + // Check if player has timed click right + if data.inputs.primary.is_just_pressed() { + println!("Can transition"); + new_can_transition = true; + } } - // Check if player has timed click right - if data.inputs.primary.is_just_pressed() { - println!("Can transition"); - new_can_transition = true; - } - } - - if new_stage_time_active > tool.attack_duration() { - if new_can_transition { - update.character = CharacterState::TimedCombo { - tool: *tool, - stage: 2, - stage_time_active: Duration::default(), - stage_exhausted: false, - can_transition: false, + if new_stage_time_active > tool.attack_duration() { + if new_can_transition { + update.character = CharacterState::TimedCombo { + tool: *tool, + stage: 2, + stage_time_active: Duration::default(), + stage_exhausted: false, + can_transition: false, + } + } else { + println!("Failed"); + attempt_wield(data, &mut update); } } else { - println!("Failed"); - attempt_wield(data, &mut update); + update.character = CharacterState::TimedCombo { + tool: *tool, + stage: 1, + stage_time_active: new_stage_time_active, + stage_exhausted: new_stage_exhausted, + can_transition: new_can_transition, + } } - } else { - update.character = CharacterState::TimedCombo { - tool: *tool, - stage: 1, - stage_time_active: new_stage_time_active, - stage_exhausted: new_stage_exhausted, - can_transition: new_can_transition, - } - } - }, - 2 => { - println!("2"); - attempt_wield(data, &mut update); - }, - 3 => { - println!("3"); - attempt_wield(data, &mut update); - }, - _ => { - // Should never get here. - }, - } + }, + 2 => { + println!("2"); + attempt_wield(data, &mut update); + }, + 3 => { + println!("3"); + attempt_wield(data, &mut update); + }, + _ => { + // Should never get here. + }, + } + */ } update diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7c02539108..3cf8c014b2 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{AbilityState, CharacterState, EnergySource, ItemKind::Tool, StateUpdate, ToolData}, + comp::{CharacterAbility, CharacterState, EnergySource, ItemKind::Tool, StateUpdate, ToolData}, event::LocalEvent, sys::{character_behavior::JoinData, phys::GRAVITY}, }; @@ -191,8 +191,8 @@ pub fn handle_primary_input(data: &JoinData, update: &mut StateUpdate) { /// Attempts to go into `ability_pool.primary` if is `Some()` on `AbilityPool` pub fn attempt_primary_ability(data: &JoinData, update: &mut StateUpdate) { - if let Some(ability_state) = data.ability_pool.primary { - update.character = ability_to_character_state(data, update, ability_state); + if let Some(ability) = data.ability_pool.primary { + update.character = ability.into(); } } @@ -201,15 +201,15 @@ pub fn attempt_primary_ability(data: &JoinData, update: &mut StateUpdate) { pub fn handle_secondary_input(data: &JoinData, update: &mut StateUpdate) { if data.inputs.secondary.is_pressed() { if let CharacterState::Wielding { .. } = update.character { - attempt_seconday_ability(data, update); + attempt_secondary_ability(data, update); } } } /// Attempts to go into `ability_pool.secondary` if is `Some()` on `AbilityPool` -pub fn attempt_seconday_ability(data: &JoinData, update: &mut StateUpdate) { - if let Some(ability_state) = data.ability_pool.secondary { - update.character = ability_to_character_state(data, update, ability_state); +pub fn attempt_secondary_ability(data: &JoinData, update: &mut StateUpdate) { + if let Some(ability) = data.ability_pool.secondary { + update.character = ability.into(); } } @@ -232,63 +232,7 @@ pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { } pub fn attempt_dodge_ability(data: &JoinData, update: &mut StateUpdate) { - if let Some(ability_state) = data.ability_pool.dodge { - update.character = ability_to_character_state(data, update, ability_state); - } -} - -// TODO: Might need a fn `CharacterState::new(data, update)` if -// initialization gets too lengthy. - -/// Maps from `AbilityState`s to `CharacterStates`s. Also handles intializing -/// the new `CharacterState` -pub fn ability_to_character_state( - data: &JoinData, - update: &mut StateUpdate, - ability_state: AbilityState, -) -> CharacterState { - match ability_state { - AbilityState::BasicAttack { cost, .. } => { - if let Some(tool) = unwrap_tool_data(data) { - if update.energy.try_change_by(cost, EnergySource::HitEnemy).is_ok() { - return CharacterState::BasicAttack { - exhausted: false, - remaining_duration: tool.attack_duration(), - }; - } - } - *data.character - }, - AbilityState::BasicBlock { .. } => CharacterState::BasicBlock {}, - AbilityState::Roll { .. } => CharacterState::Roll { - remaining_duration: Duration::from_millis(600), - }, - AbilityState::ChargeAttack { .. } => CharacterState::ChargeAttack { - remaining_duration: Duration::from_millis(600), - }, - AbilityState::TimedCombo { .. } => { - if let Some(tool) = unwrap_tool_data(data) { - CharacterState::TimedCombo { - tool, - stage: 1, - stage_time_active: Duration::default(), - stage_exhausted: false, - can_transition: false, - } - } else { - *data.character - } - }, - - // Do not use default match - // _ => *data.character - } -} - -pub fn unwrap_tool_data(data: &JoinData) -> Option { - if let Some(Tool(tool)) = data.stats.equipment.main.as_ref().map(|i| i.kind) { - Some(tool) - } else { - None + if let Some(ability) = data.ability_pool.dodge { + update.character = ability.into(); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 637b11d5ec..ebe6dd8d84 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -272,7 +272,7 @@ impl Server { let spawn_point = state.ecs().read_resource::().0; state.write_component(entity, body); - state.write_component(entity, comp::Stats::new(name, body, main)); + state.write_component(entity, comp::Stats::new(name, body, main.clone())); state.write_component(entity, comp::Energy::new(1000)); state.write_component(entity, comp::Controller::default()); state.write_component(entity, comp::Pos(spawn_point)); @@ -286,7 +286,27 @@ impl Server { entity, comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()), ); - state.write_component(entity, comp::AbilityPool::default()); + + state.write_component( + entity, + if let Some(comp::Item { + kind: comp::ItemKind::Tool(tool), + .. + }) = main + { + let mut abilities = tool.get_abilities(); + let mut ability_drain = abilities.drain(..); + comp::AbilityPool { + primary: ability_drain.next(), + secondary: ability_drain.next(), + block: Some(comp::CharacterAbility::BasicBlock), + dodge: Some(comp::CharacterAbility::Roll), + } + } else { + comp::AbilityPool::default() + }, + ); + // Make sure physics are accepted. state.write_component(entity, comp::ForceUpdate); diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 1ed37221d5..8df29bac07 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -10,7 +10,7 @@ use crate::{ use common::{ assets::load_expect, comp::{ - item::{Debug, ToolData, ToolKind}, + item::{DebugKind, ToolData, ToolKind}, CharacterState, ControllerInputs, Energy, ItemKind, Stats, }, }; @@ -597,7 +597,7 @@ impl<'a> Widget for Skillbar<'a> { ToolKind::Axe => self.imgs.twohaxe_m1, ToolKind::Bow => self.imgs.bow_m1, ToolKind::Staff => self.imgs.staff_m1, - ToolKind::Debug(Debug::Boost) => self.imgs.flyingrod_m1, + ToolKind::Debug(DebugKind::Boost) => self.imgs.flyingrod_m1, _ => self.imgs.twohaxe_m1, }, _ => self.imgs.twohaxe_m1, @@ -690,7 +690,7 @@ impl<'a> Widget for Skillbar<'a> { ToolKind::Axe => self.imgs.twohaxe_m2, ToolKind::Bow => self.imgs.bow_m2, ToolKind::Staff => self.imgs.staff_m2, - ToolKind::Debug(Debug::Boost) => self.imgs.flyingrod_m2, + ToolKind::Debug(DebugKind::Boost) => self.imgs.flyingrod_m2, _ => self.imgs.twohaxe_m2, }, _ => self.imgs.twohaxe_m2,