diff --git a/CHANGELOG.md b/CHANGELOG.md index d39623f0c3..f792f93994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Loading-Screen tips - Feeding animation for some animals - Power stat to weapons which affects weapon damage +- Add IDs to abilities. ### Changed diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 79292b797e..357482ebce 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -43,6 +43,63 @@ impl From<&CharacterState> for CharacterAbilityType { } } +/// This enum is used for matching ability to image in GUI. +/// This is beacuse `CharacterAbility` can have more "meanings" +/// (like axe swing and sword swing, both are BasicMelee) +/// and different GUI can be done for each. +/// +/// TODO: Dehardcode this in case of modding. +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub enum AbilityId { + // Sword + SwordCut, + SwordThrust, + // Axe + AxeSwing, + AxeSpin, + // Hammer + HammerSmash, + HammerLeap, + // Bow + BowShot, + BowCharged, + // Staff + StaffSwing, + StaffShot, + StaffFireball, + StaffHeal, + // Dagger + DaggerStab, + DaggerDash, + // Shield + ShieldBash, + ShieldBlock, + // Farming + FarmingAttack, + // Debug + DebugFlyDirection, + DebugFlyUp, + DebugPossesArrow, + // Special + // TODO: Review usage + Roll, + Block, + Empty, +} + +/// Unique ability config. +/// It composes from an ID (currently used to inform Client's GUI) +/// and data, which is the actual ability behaviour/config. +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Ability { + pub id: AbilityId, + pub data: CharacterAbility, +} + +impl Ability { + pub fn new(id: AbilityId, data: CharacterAbility) -> Ability { Ability { id, data } } +} + #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub enum CharacterAbility { BasicMelee { @@ -158,11 +215,11 @@ impl CharacterAbility { #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct ItemConfig { pub item: Item, - pub ability1: Option, - pub ability2: Option, - pub ability3: Option, - pub block_ability: Option, - pub dodge_ability: Option, + pub ability1: Option, + pub ability2: Option, + pub ability3: Option, + pub block_ability: Option, + pub dodge_ability: Option, } #[derive(Arraygen, Clone, PartialEq, Default, Debug, Serialize, Deserialize)] @@ -360,6 +417,10 @@ impl From<&CharacterAbility> for CharacterState { } } +impl From<&Ability> for CharacterState { + fn from(ability: &Ability) -> Self { CharacterState::from(&ability.data) } +} + impl Component for Loadout { type Storage = FlaggedStorage>; } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 742709901e..79a9908c65 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,7 +2,8 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, + body::object, projectile, Ability, AbilityId, Body, CharacterAbility, Gravity, LightEmitter, + Projectile, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -106,62 +107,62 @@ impl Tool { Duration::from_millis(self.stats.equip_time_millis as u64) } - pub fn get_abilities(&self) -> Vec { + pub fn get_abilities(&self) -> Vec { use CharacterAbility::*; use ToolKind::*; match &self.kind { Sword(_) => vec![ - TripleStrike { + Ability::new(AbilityId::SwordCut, TripleStrike { base_damage: (60.0 * self.base_power()) as u32, needs_timing: false, - }, - DashMelee { + }), + Ability::new(AbilityId::SwordThrust, DashMelee { energy_cost: 700, buildup_duration: Duration::from_millis(500), recover_duration: Duration::from_millis(500), base_damage: (120.0 * self.base_power()) as u32, - }, + }), ], Axe(_) => vec![ - TripleStrike { + Ability::new(AbilityId::AxeSwing, TripleStrike { base_damage: (80.0 * self.base_power()) as u32, needs_timing: true, - }, - SpinMelee { + }), + Ability::new(AbilityId::AxeSpin, SpinMelee { energy_cost: 100, buildup_duration: Duration::from_millis(125), recover_duration: Duration::from_millis(125), base_damage: (60.0 * self.base_power()) as u32, - }, + }), ], Hammer(_) => vec![ - BasicMelee { + Ability::new(AbilityId::HammerSmash, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(700), recover_duration: Duration::from_millis(300), base_healthchange: (-120.0 * self.base_power()) as i32, range: 3.5, max_angle: 60.0, - }, - LeapMelee { + }), + Ability::new(AbilityId::HammerLeap, LeapMelee { energy_cost: 800, movement_duration: Duration::from_millis(500), buildup_duration: Duration::from_millis(1000), recover_duration: Duration::from_millis(100), base_damage: (240.0 * self.base_power()) as u32, - }, + }), ], - Farming(_) => vec![BasicMelee { + Farming(_) => vec![Ability::new(AbilityId::FarmingAttack, BasicMelee { energy_cost: 1, buildup_duration: Duration::from_millis(700), recover_duration: Duration::from_millis(150), base_healthchange: (-50.0 * self.base_power()) as i32, range: 3.0, max_angle: 60.0, - }], + })], Bow(_) => vec![ - BasicRanged { + Ability::new(AbilityId::BowShot, BasicRanged { energy_cost: 0, holdable: true, prepare_duration: Duration::from_millis(100), @@ -180,8 +181,8 @@ impl Tool { projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, projectile_gravity: Some(Gravity(0.2)), - }, - ChargedRanged { + }), + Ability::new(AbilityId::BowCharged, ChargedRanged { energy_cost: 0, energy_drain: 300, initial_damage: (40.0 * self.base_power()) as u32, @@ -193,55 +194,55 @@ impl Tool { recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - }, + }), ], Dagger(_) => vec![ - BasicMelee { + Ability::new(AbilityId::DaggerStab, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(400), base_healthchange: (-50.0 * self.base_power()) as i32, range: 3.5, max_angle: 60.0, - }, - DashMelee { + }), + Ability::new(AbilityId::DaggerDash, DashMelee { energy_cost: 700, buildup_duration: Duration::from_millis(500), recover_duration: Duration::from_millis(500), base_damage: (100.0 * self.base_power()) as u32, - }, + }), ], Staff(kind) => { if kind == "Sceptre" { vec![ - BasicMelee { + Ability::new(AbilityId::StaffSwing, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(0), recover_duration: Duration::from_millis(300), base_healthchange: (-10.0 * self.base_power()) as i32, range: 10.0, max_angle: 45.0, - }, - BasicMelee { + }), + Ability::new(AbilityId::StaffHeal, BasicMelee { energy_cost: 350, buildup_duration: Duration::from_millis(0), recover_duration: Duration::from_millis(1000), base_healthchange: (150.0 * self.base_power()) as i32, range: 10.0, max_angle: 45.0, - }, + }), ] } else { vec![ - BasicMelee { + Ability::new(AbilityId::StaffSwing, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(300), base_healthchange: (-40.0 * self.base_power()) as i32, range: 10.0, max_angle: 45.0, - }, - BasicRanged { + }), + Ability::new(AbilityId::StaffShot, BasicRanged { energy_cost: 0, holdable: false, prepare_duration: Duration::from_millis(250), @@ -263,8 +264,8 @@ impl Tool { }), projectile_gravity: None, - }, - BasicRanged { + }), + Ability::new(AbilityId::StaffFireball, BasicRanged { energy_cost: 400, holdable: true, prepare_duration: Duration::from_millis(800), @@ -292,33 +293,33 @@ impl Tool { }), projectile_gravity: None, - }, + }), ] } }, Shield(_) => vec![ - BasicMelee { + Ability::new(AbilityId::ShieldBash, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(400), base_healthchange: (-40.0 * self.base_power()) as i32, range: 3.0, max_angle: 120.0, - }, - BasicBlock, + }), + Ability::new(AbilityId::ShieldBlock, BasicBlock), ], Debug(kind) => { if kind == "Boost" { vec![ - CharacterAbility::Boost { + Ability::new(AbilityId::DebugFlyDirection, CharacterAbility::Boost { duration: Duration::from_millis(50), only_up: false, - }, - CharacterAbility::Boost { + }), + Ability::new(AbilityId::DebugFlyUp, CharacterAbility::Boost { duration: Duration::from_millis(50), only_up: true, - }, - BasicRanged { + }), + Ability::new(AbilityId::DebugPossesArrow, BasicRanged { energy_cost: 0, holdable: false, prepare_duration: Duration::from_millis(0), @@ -338,20 +339,20 @@ impl Tool { ..Default::default() }), projectile_gravity: None, - }, + }), ] } else { vec![] } }, - Empty => vec![BasicMelee { + Empty => vec![Ability::new(AbilityId::Empty, BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(0), recover_duration: Duration::from_millis(1000), base_healthchange: -20, range: 5.0, max_angle: 60.0, - }], + })], } } diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs index db09354b25..4adf159768 100644 --- a/common/src/comp/inventory/slot.rs +++ b/common/src/comp/inventory/slot.rs @@ -2,7 +2,7 @@ use crate::{ comp, comp::{item, item::armor}, }; -use comp::{Inventory, Loadout}; +use comp::{Ability, AbilityId, Inventory, Loadout}; use serde::{Deserialize, Serialize}; use tracing::warn; @@ -100,8 +100,11 @@ fn item_config(item: item::Item) -> comp::ItemConfig { ability1: abilities.next(), ability2: abilities.next(), ability3: abilities.next(), - block_ability: Some(comp::CharacterAbility::BasicBlock), - dodge_ability: Some(comp::CharacterAbility::Roll), + block_ability: Some(Ability::new( + AbilityId::Block, + comp::CharacterAbility::BasicBlock, + )), + dodge_ability: Some(Ability::new(AbilityId::Roll, comp::CharacterAbility::Roll)), } } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index b99b575450..7b251b69d3 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -20,7 +20,9 @@ mod stats; mod visual; // Reexports -pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout}; +pub use ability::{ + Ability, AbilityId, CharacterAbility, CharacterAbilityType, ItemConfig, Loadout, +}; pub use admin::{Admin, AdminList}; pub use agent::{Agent, Alignment}; pub use body::{ diff --git a/common/src/loadout_builder.rs b/common/src/loadout_builder.rs index 0fc3983039..309fb746c6 100644 --- a/common/src/loadout_builder.rs +++ b/common/src/loadout_builder.rs @@ -2,7 +2,7 @@ use crate::{ assets, comp::{ item::{Item, ItemKind}, - Body, CharacterAbility, ItemConfig, Loadout, + Ability, AbilityId, Body, CharacterAbility, ItemConfig, Loadout, }, }; use std::time::Duration; @@ -67,14 +67,17 @@ impl LoadoutBuilder { Self(Loadout { active_item: Some(ItemConfig { item: assets::load_expect_cloned("common.items.weapons.empty.empty"), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 10, - buildup_duration: Duration::from_millis(600), - recover_duration: Duration::from_millis(100), - base_healthchange: -(body.base_dmg() as i32), - range: body.base_range(), - max_angle: 80.0, - }), + ability1: Some(Ability::new( + AbilityId::Empty, + CharacterAbility::BasicMelee { + energy_cost: 10, + buildup_duration: Duration::from_millis(600), + recover_duration: Duration::from_millis(100), + base_healthchange: -(body.base_dmg() as i32), + range: body.base_range(), + max_angle: 80.0, + }, + )), ability2: None, ability3: None, block_ability: None, @@ -113,8 +116,11 @@ impl LoadoutBuilder { ability1: ability_drain.next(), ability2: ability_drain.next(), ability3: ability_drain.next(), - block_ability: Some(CharacterAbility::BasicBlock), - dodge_ability: Some(CharacterAbility::Roll), + block_ability: Some(Ability::new( + AbilityId::Block, + CharacterAbility::BasicBlock, + )), + dodge_ability: Some(Ability::new(AbilityId::Roll, CharacterAbility::Roll)), }); } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index ee20892e6c..bc1e1b7d3e 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -203,7 +203,7 @@ pub fn handle_ability1_input(data: &JoinData, update: &mut StateUpdate) { .active_item .as_ref() .and_then(|i| i.ability1.as_ref()) - .filter(|ability| ability.requirements_paid(data, update)) + .filter(|ability| ability.data.requirements_paid(data, update)) { update.character = ability.into(); } @@ -233,7 +233,7 @@ pub fn handle_ability2_input(data: &JoinData, update: &mut StateUpdate) { .active_item .as_ref() .and_then(|i| i.ability2.as_ref()) - .filter(|ability| ability.requirements_paid(data, update)) + .filter(|ability| ability.data.requirements_paid(data, update)) { update.character = ability.into(); } @@ -244,7 +244,7 @@ pub fn handle_ability2_input(data: &JoinData, update: &mut StateUpdate) { .second_item .as_ref() .and_then(|i| i.ability2.as_ref()) - .filter(|ability| ability.requirements_paid(data, update)) + .filter(|ability| ability.data.requirements_paid(data, update)) { update.character = ability.into(); } @@ -262,7 +262,7 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { .active_item .as_ref() .and_then(|i| i.ability3.as_ref()) - .filter(|ability| ability.requirements_paid(data, update)) + .filter(|ability| ability.data.requirements_paid(data, update)) { update.character = ability.into(); } @@ -278,7 +278,7 @@ pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { .active_item .as_ref() .and_then(|i| i.dodge_ability.as_ref()) - .filter(|ability| ability.requirements_paid(data, update)) + .filter(|ability| ability.data.requirements_paid(data, update)) { if data.character.is_wield() { update.character = ability.into(); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 9d2775e194..ccf2853a32 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -127,20 +127,26 @@ impl<'a> System<'a> for Sys { ability2: ability_drain.next(), ability3: ability_drain.next(), block_ability: None, - dodge_ability: Some(comp::CharacterAbility::Roll), + dodge_ability: Some(comp::Ability::new( + comp::AbilityId::Roll, + comp::CharacterAbility::Roll, + )), }) } else { Some(ItemConfig { // We need the empty item so npcs can attack item: assets::load_expect_cloned("common.items.weapons.empty.empty"), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(400), - base_healthchange: -60, - range: 5.0, - max_angle: 80.0, - }), + ability1: Some(comp::Ability::new( + comp::AbilityId::Empty, + CharacterAbility::BasicMelee { + energy_cost: 0, + buildup_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(400), + base_healthchange: -60, + range: 5.0, + max_angle: 80.0, + }, + )), ability2: None, ability3: None, block_ability: None, @@ -254,14 +260,17 @@ impl<'a> System<'a> for Sys { item: assets::load_expect_cloned( "common.items.weapons.sword.zweihander_sword_0", ), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(800), - recover_duration: Duration::from_millis(200), - base_healthchange: -100, - range: 3.5, - max_angle: 60.0, - }), + ability1: Some(comp::Ability::new( + comp::AbilityId::Empty, + CharacterAbility::BasicMelee { + energy_cost: 0, + buildup_duration: Duration::from_millis(800), + recover_duration: Duration::from_millis(200), + base_healthchange: -100, + range: 3.5, + max_angle: 60.0, + }, + )), ability2: None, ability3: None, block_ability: None, diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index e5ce67075c..bba3bdcaf3 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -20,7 +20,7 @@ use common::comp::{ tool::{Tool, ToolKind}, Hands, ItemKind, }, - CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, + AbilityId, CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, }; use conrod_core::{ color, @@ -181,6 +181,44 @@ impl<'a> Skillbar<'a> { show, } } + + /// Pairs ability with image. + /// + /// TODO: Dehardcode this into a .ron file + fn get_ability_image(&self, ability: AbilityId) -> conrod_core::image::Id { + use AbilityId::*; + match ability { + // Sword + SwordCut => self.imgs.twohsword_m1, + SwordThrust => self.imgs.twohsword_m2, + // Axe + AxeSwing => self.imgs.twohaxe_m1, + AxeSpin => self.imgs.axespin, + // Hammer + HammerSmash => self.imgs.twohhammer_m1, + HammerLeap => self.imgs.hammerleap, + // Bow + BowShot => self.imgs.bow_m1, + BowCharged => self.imgs.bow_m2, + // Staff + StaffSwing => self.imgs.staff_m1, + StaffShot => self.imgs.staff_m2, + StaffFireball => self.imgs.fire_spell_1, + StaffHeal => self.imgs.heal_0, + // Dagger + DaggerStab => self.imgs.onehdagger_m1, + DaggerDash => self.imgs.onehdagger_m2, + // Shield + ShieldBash => self.imgs.onehshield_m1, + ShieldBlock => self.imgs.onehshield_m2, + // Debug + DebugFlyDirection => self.imgs.flyingrod_m1, + DebugFlyUp => self.imgs.flyingrod_m2, + DebugPossesArrow => self.imgs.snake_arrow_0, + + _ => self.imgs.nothing, + } + } } pub struct State { @@ -608,20 +646,13 @@ impl<'a> Widget for Skillbar<'a> { .middle_of(state.ids.m1_slot) .set(state.ids.m1_slot_bg, ui); Button::image( - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Sword(_) => self.imgs.twohsword_m1, - ToolKind::Dagger(_) => self.imgs.onehdagger_m1, - ToolKind::Shield(_) => self.imgs.onehshield_m1, - ToolKind::Hammer(_) => self.imgs.twohhammer_m1, - ToolKind::Axe(_) => self.imgs.twohaxe_m1, - ToolKind::Bow(_) => self.imgs.bow_m1, - ToolKind::Staff(_) => self.imgs.staff_m1, - ToolKind::Debug(kind) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m1, - _ => self.imgs.nothing, - }, - _ => self.imgs.nothing, + match self.loadout.active_item.as_ref().map(|i| &i.ability1) { + Some(ability) => { + if let Some(ability) = ability { + self.get_ability_image(ability.id) + } else { + self.imgs.nothing + } }, _ => self.imgs.nothing, }, @@ -669,63 +700,38 @@ impl<'a> Widget for Skillbar<'a> { _ => None, }; - let tool_kind = match ( + let active_tool_secondary_ability = + match self.loadout.active_item.as_ref().map(|i| &i.ability2) { + Some(Some(ability)) => Some(ability.id), + _ => None, + }; + + let second_tool_secondary_ability = + match self.loadout.second_item.as_ref().map(|i| &i.ability2) { + Some(Some(ability)) => Some(ability.id), + _ => None, + }; + + let used_secondary_ability = match ( active_tool_kind.map(|tk| tk.hands()), second_tool_kind.map(|tk| tk.hands()), ) { - (Some(Hands::TwoHand), _) => active_tool_kind, - (_, Some(Hands::OneHand)) => second_tool_kind, + (Some(Hands::TwoHand), _) => active_tool_secondary_ability, + (Some(Hands::OneHand), Some(Hands::OneHand)) => second_tool_secondary_ability, (_, _) => None, }; Image::new(self.imgs.skillbar_slot_big_bg) .w_h(38.0 * scale, 38.0 * scale) - .color(match tool_kind { - Some(ToolKind::Bow(_)) => Some(BG_COLOR_2), - Some(ToolKind::Staff(_)) => Some(BG_COLOR_2), - _ => Some(BG_COLOR_2), - }) + .color(Some(BG_COLOR_2)) .middle_of(state.ids.m2_slot) .set(state.ids.m2_slot_bg, ui); - Button::image(match tool_kind { - Some(ToolKind::Sword(_)) => self.imgs.twohsword_m2, - Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2, - Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2, - Some(ToolKind::Hammer(_)) => self.imgs.hammerleap, - Some(ToolKind::Axe(_)) => self.imgs.axespin, - Some(ToolKind::Bow(_)) => self.imgs.bow_m2, - Some(ToolKind::Staff(kind)) => match kind.as_ref() { - "Sceptre" => self.imgs.heal_0, - _ => self.imgs.staff_m2, - }, - Some(ToolKind::Debug(kind)) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m2, - _ => self.imgs.nothing, - }, + Button::image(match used_secondary_ability { + Some(ability) => self.get_ability_image(ability), _ => self.imgs.nothing, }) .w_h(32.0 * scale, 32.0 * scale) .middle_of(state.ids.m2_slot_bg) - .image_color(match tool_kind { - Some(ToolKind::Sword(_)) => { - if self.energy.current() as f64 >= 200.0 { - Color::Rgba(1.0, 1.0, 1.0, 1.0) - } else { - Color::Rgba(0.3, 0.3, 0.3, 0.8) - } - }, - Some(ToolKind::Staff(kind)) => match kind.as_ref() { - "Sceptre" => { - if self.energy.current() as f64 >= 400.0 { - Color::Rgba(1.0, 1.0, 1.0, 1.0) - } else { - Color::Rgba(0.3, 0.3, 0.3, 0.8) - } - }, - _ => Color::Rgba(1.0, 1.0, 1.0, 1.0), - }, - _ => Color::Rgba(1.0, 1.0, 1.0, 1.0), - }) .set(state.ids.m2_content, ui); // Slots let content_source = (self.hotbar, self.inventory, self.loadout, self.energy); // TODO: avoid this