From 8f0cca074df5b37aa0def53f0ff605e7ce10d46e Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 12 Feb 2021 19:43:33 -0500 Subject: [PATCH] Dual wielding now uses skillbar abilities from multiple weapons. --- .../abilities/weapon_ability_manifest.ron | 1 + .../common/items/weapons/axe/bronze_axe-1.ron | 2 +- common/net/src/msg/client.rs | 2 + common/src/comp/controller.rs | 9 +- common/src/states/utils.rs | 56 ++++++++++- common/src/states/wielding.rs | 1 + voxygen/src/hud/hotbar.rs | 66 ++++++++++++- voxygen/src/hud/mod.rs | 8 +- voxygen/src/hud/skillbar.rs | 96 +++++++++++++------ voxygen/src/hud/slots.rs | 71 +++++++++++--- voxygen/src/session.rs | 1 + 11 files changed, 267 insertions(+), 46 deletions(-) diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index 66c39105f2..5a847f4bd3 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -20,6 +20,7 @@ secondary: "common.abilities.hammer.charged", skills: [ (Some(Hammer(UnlockLeap)), "common.abilities.hammer.leap"), + (Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"), // test, remove later ], ), Bow: ( diff --git a/assets/common/items/weapons/axe/bronze_axe-1.ron b/assets/common/items/weapons/axe/bronze_axe-1.ron index a367894890..f29e1fa748 100644 --- a/assets/common/items/weapons/axe/bronze_axe-1.ron +++ b/assets/common/items/weapons/axe/bronze_axe-1.ron @@ -3,7 +3,7 @@ ItemDef( description: "While the metal alloy is relatively simple, this unique circular axe has a unique appearance.", kind: Tool(( kind: Axe, - hands: TwoHand, + hands: OneHand, stats: ( equip_time_millis: 400, power: 1.0, diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index 91a8c305c5..6228372536 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -46,6 +46,7 @@ pub struct ClientRegister { /// Messages sent from the client to the server #[derive(Debug, Clone, Serialize, Deserialize)] +#[allow(clippy::large_enum_variant)] pub enum ClientGeneral { //Only in Character Screen RequestCharacterList, @@ -58,6 +59,7 @@ pub enum ClientGeneral { Character(CharacterId), Spectate, //Only in game + // Large enum variant allowed for clippy because of this ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), ControlAction(comp::ControlAction), diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 3fbfce27cd..733d47ef2e 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -222,6 +222,7 @@ pub struct ControllerInputs { pub primary: Input, pub secondary: Input, pub ability3: Input, + pub ability4: Input, pub jump: Input, pub roll: Input, pub glide: Input, @@ -249,6 +250,7 @@ impl ControllerInputs { self.primary.tick(dt); self.secondary.tick(dt); self.ability3.tick(dt); + self.ability4.tick(dt); self.jump.tick(dt); self.roll.tick(dt); self.glide.tick(dt); @@ -261,6 +263,7 @@ impl ControllerInputs { self.primary.tick_freshness(); self.secondary.tick_freshness(); self.ability3.tick_freshness(); + self.ability4.tick_freshness(); self.jump.tick_freshness(); self.roll.tick_freshness(); self.glide.tick_freshness(); @@ -274,6 +277,7 @@ impl ControllerInputs { self.primary.update_with_new(new.primary); self.secondary.update_with_new(new.secondary); self.ability3.update_with_new(new.ability3); + self.ability4.update_with_new(new.ability4); self.jump.update_with_new(new.jump); self.roll.update_with_new(new.roll); self.glide.update_with_new(new.glide); @@ -287,7 +291,10 @@ impl ControllerInputs { } pub fn holding_ability_key(&self) -> bool { - self.primary.is_pressed() || self.secondary.is_pressed() || self.ability3.is_pressed() + self.primary.is_pressed() + || self.secondary.is_pressed() + || self.ability3.is_pressed() + || self.ability4.is_pressed() } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index b6ba3ce5ea..3f97064219 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -278,12 +278,13 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { handle_orientation(data, update, 1.0); } -/// First checks whether `primary`, `secondary` or `ability3` input is pressed, -/// then attempts to go into Equipping state, otherwise Idle +/// First checks whether `primary`, `secondary`, `ability3`, or `ability4` input +/// is pressed, then attempts to go into Equipping state, otherwise Idle pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) { if data.inputs.primary.is_pressed() || data.inputs.secondary.is_pressed() || data.inputs.ability3.is_pressed() + || data.inputs.ability4.is_pressed() { attempt_wield(data, update); } @@ -481,6 +482,54 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { } } +pub fn handle_ability4_input(data: &JoinData, update: &mut StateUpdate) { + if data.inputs.ability4.is_pressed() { + let active_tool_hands = match data + .inventory + .equipped(EquipSlot::Mainhand) + .map(|i| i.kind()) + { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let second_tool_hands = match data + .inventory + .equipped(EquipSlot::Offhand) + .map(|i| i.kind()) + { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) { + (Some(Hands::TwoHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, Some(Hands::OneHand)) => (Some(EquipSlot::Offhand), 0), + (Some(Hands::OneHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, _) => (None, 0), + }; + + if let Some(equip_slot) = equip_slot { + if let Some(ability) = data + .inventory + .equipped(equip_slot) + .and_then(|i| i.item_config_expect().abilities.skills.get(skill_index)) + .and_then(|(s, a)| { + s.map_or(true, |s| data.stats.skill_set.has_skill(s)) + .then_some(a) + }) + .map(|a| { + let tool = unwrap_tool_data(data).map(|t| t.kind); + a.clone().adjusted_by_skills(&data.stats.skill_set, tool) + }) + .filter(|ability| ability.requirements_paid(data, update)) + { + update.character = (&ability, AbilityKey::Skill1).into(); + } + } + } +} + /// Checks that player can perform a dodge, then /// attempts to perform their dodge ability pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { @@ -530,6 +579,7 @@ pub fn handle_interrupt(data: &JoinData, update: &mut StateUpdate, attacks_inter handle_ability1_input(data, update); handle_ability2_input(data, update); handle_ability3_input(data, update); + handle_ability4_input(data, update); } handle_dodge_input(data, update); } @@ -539,6 +589,7 @@ pub fn ability_key_is_pressed(data: &JoinData, ability_key: AbilityKey) -> bool AbilityKey::Mouse1 => data.inputs.primary.is_pressed(), AbilityKey::Mouse2 => data.inputs.secondary.is_pressed(), AbilityKey::Skill1 => data.inputs.ability3.is_pressed(), + AbilityKey::Skill2 => data.inputs.ability4.is_pressed(), AbilityKey::Dodge => data.inputs.roll.is_pressed(), } } @@ -570,6 +621,7 @@ pub enum AbilityKey { Mouse1, Mouse2, Skill1, + Skill2, Dodge, } diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index 30a8c69d4b..dc21ae44d7 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -19,6 +19,7 @@ impl CharacterBehavior for Data { handle_ability1_input(&data, &mut update); handle_ability2_input(&data, &mut update); handle_ability3_input(&data, &mut update); + handle_ability4_input(&data, &mut update); handle_dodge_input(&data, &mut update); update diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index cd3909baa2..ef26b6ded9 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -1,5 +1,9 @@ use crate::hud::slots::EquipSlot; -use common::comp::{slot::InvSlotId, Inventory}; +use common::comp::{ + item::{tool::Hands, ItemKind}, + slot::InvSlotId, + Inventory, +}; use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, PartialEq)] @@ -20,6 +24,7 @@ pub enum Slot { pub enum SlotContents { Inventory(InvSlotId), Ability3, + Ability4, } #[derive(Clone, Debug)] @@ -103,4 +108,63 @@ impl State { .for_each(|s| *s = None) } } + + pub fn maintain_ability4(&mut self, client: &client::Client) { + use specs::WorldExt; + let inventories = client.state().ecs().read_storage::(); + let inventory = inventories.get(client.entity()); + let stats = client.state().ecs().read_storage::(); + let stat = stats.get(client.entity()); + let should_be_present = if let (Some(inventory), Some(stat)) = (inventory, stat) { + let active_tool_hands = match inventory.equipped(EquipSlot::Mainhand).map(|i| i.kind()) + { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let second_tool_hands = match inventory.equipped(EquipSlot::Offhand).map(|i| i.kind()) { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) { + (Some(Hands::TwoHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, Some(Hands::OneHand)) => (Some(EquipSlot::Offhand), 0), + (Some(Hands::OneHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, _) => (None, 0), + }; + + if let Some(equip_slot) = equip_slot { + inventory.equipped(equip_slot).map_or(false, |i| { + i.item_config_expect() + .abilities + .skills + .get(skill_index) + .as_ref() + .map_or(false, |(s, _)| { + s.map_or(true, |s| stat.skill_set.has_skill(s)) + }) + }) + } else { + false + } + } else { + false + }; + + if should_be_present { + if !self + .slots + .iter() + .any(|s| matches!(s, Some(SlotContents::Ability4))) + { + self.slots[1] = Some(SlotContents::Ability4); + } + } else { + self.slots + .iter_mut() + .filter(|s| matches!(s, Some(SlotContents::Ability4))) + .for_each(|s| *s = None) + } + } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index c52cdef988..ecd123413e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -401,6 +401,7 @@ pub enum Event { ChangeHotbarState(Box), TradeAction(TradeAction), Ability3(bool), + Ability4(bool), Logout, Quit, ChangeLanguage(Box), @@ -2684,8 +2685,9 @@ impl Hud { bypass_dialog: false, }); }, - hotbar::SlotContents::Ability3 => {}, /* Event::Ability3(true), - * sticks */ + hotbar::SlotContents::Ability3 | hotbar::SlotContents::Ability4 => { + }, /* Event::Ability3(true), + * sticks */ } }); } @@ -2693,6 +2695,7 @@ impl Hud { } } self.hotbar.maintain_ability3(client); + self.hotbar.maintain_ability4(client); events } @@ -2751,6 +2754,7 @@ impl Hud { } }, hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)), + hotbar::SlotContents::Ability4 => events.push(Event::Ability4(state)), }); } } diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index a91a63d944..ddf8c2a930 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -452,36 +452,48 @@ impl<'a> Widget for Skillbar<'a> { .equipped(EquipSlot::Mainhand) .map(|i| i.kind()) .and_then(|kind| match kind { - ItemKind::Tool(Tool { kind, .. }) => match kind { - ToolKind::Hammer => Some(( - "Smash of Doom", - "\nAn AOE attack with knockback. \nLeaps to position of \ - cursor.", - )), - ToolKind::Axe => { - Some(("Spin Leap", "\nA slashing running spin leap.")) - }, - ToolKind::Staff => Some(( - "Firebomb", - "\nWhirls a big fireball into the air. \nExplodes the ground \ - and does\na big amount of damage", - )), - ToolKind::Sword => Some(( - "Whirlwind", - "\nMove forward while spinning with \n your sword.", - )), - ToolKind::Bow => Some(( - "Burst", - "\nLaunches a burst of arrows at the top \nof a running leap.", - )), - ToolKind::Debug => Some(( - "Possessing Arrow", - "\nShoots a poisonous arrow.\nLets you control your target.", - )), - _ => None, - }, + ItemKind::Tool(Tool { kind, .. }) => ability_description(kind), _ => None, }), + hotbar::SlotContents::Ability4 => { + let active_tool_hands = match content_source + .1 + .equipped(EquipSlot::Mainhand) + .map(|i| i.kind()) + { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let second_tool_hands = match content_source + .1 + .equipped(EquipSlot::Offhand) + .map(|i| i.kind()) + { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let equip_slot = match (active_tool_hands, second_tool_hands) { + (Some(Hands::TwoHand), _) => Some(EquipSlot::Mainhand), + (_, Some(Hands::OneHand)) => Some(EquipSlot::Offhand), + (Some(Hands::OneHand), _) => Some(EquipSlot::Mainhand), + (_, _) => None, + }; + + if let Some(equip_slot) = equip_slot { + content_source + .1 + .equipped(equip_slot) + .map(|i| i.kind()) + .and_then(|kind| match kind { + ItemKind::Tool(Tool { kind, .. }) => ability_description(kind), + _ => None, + }) + } else { + None + } + }, }) }; // Slot 1-5 @@ -890,3 +902,31 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.m2_ico, ui); } } + +fn ability_description(tool: &ToolKind) -> Option<(&str, &str)> { + match tool { + ToolKind::Hammer => Some(( + "Smash of Doom", + "\nAn AOE attack with knockback. \nLeaps to position of cursor.", + )), + ToolKind::Axe => Some(("Spin Leap", "\nA slashing running spin leap.")), + ToolKind::Staff => Some(( + "Firebomb", + "\nWhirls a big fireball into the air. \nExplodes the ground and does\na big amount \ + of damage", + )), + ToolKind::Sword => Some(( + "Whirlwind", + "\nMove forward while spinning with \n your sword.", + )), + ToolKind::Bow => Some(( + "Burst", + "\nLaunches a burst of arrows at the top \nof a running leap.", + )), + ToolKind::Debug => Some(( + "Possessing Arrow", + "\nShoots a poisonous arrow.\nLets you control your target.", + )), + _ => None, + } +} diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index decfa23223..c5a61300e6 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -6,7 +6,7 @@ use super::{ use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{ item::{ - tool::{AbilityMap, ToolKind}, + tool::{AbilityMap, Hands, ToolKind}, ItemKind, }, slot::InvSlotId, @@ -123,16 +123,7 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { }; tool.and_then(|tool| { - match tool.kind { - ToolKind::Staff => Some(HotbarImage::FireAoe), - ToolKind::Hammer => Some(HotbarImage::HammerLeap), - ToolKind::Axe => Some(HotbarImage::AxeLeapSlash), - ToolKind::Bow => Some(HotbarImage::BowJumpBurst), - ToolKind::Debug => Some(HotbarImage::SnakeArrow), - ToolKind::Sword => Some(HotbarImage::SwordWhirlwind), - _ => None, - } - .map(|i| { + hotbar_image(tool.kind).map(|i| { ( i, if let Some(skill) = tool.get_abilities(ability_map).skills.get(0) { @@ -148,6 +139,51 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { }) }) }, + hotbar::SlotContents::Ability4 => { + let active_tool_hands = + match inventory.equipped(EquipSlot::Mainhand).map(|i| i.kind()) { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let second_tool_hands = + match inventory.equipped(EquipSlot::Offhand).map(|i| i.kind()) { + Some(ItemKind::Tool(tool)) => Some(tool.hands), + _ => None, + }; + + let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) { + (Some(Hands::TwoHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, Some(Hands::OneHand)) => (Some(EquipSlot::Offhand), 0), + (Some(Hands::OneHand), _) => (Some(EquipSlot::Mainhand), 1), + (_, _) => (None, 0), + }; + + let tool = match equip_slot.and_then(|es| inventory.equipped(es).map(|i| i.kind())) + { + Some(ItemKind::Tool(tool)) => Some(tool), + _ => None, + }; + + tool.and_then(|tool| { + hotbar_image(tool.kind).map(|i| { + ( + i, + if let Some(skill) = + tool.get_abilities(ability_map).skills.get(skill_index) + { + if energy.current() >= skill.1.get_energy_cost() { + Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)) + } else { + Some(Color::Rgba(0.3, 0.3, 0.3, 0.8)) + } + } else { + Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)) + }, + ) + }) + }) + }, }) } @@ -157,6 +193,7 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { .and_then(|content| match content { hotbar::SlotContents::Inventory(idx) => inventory.get(idx), hotbar::SlotContents::Ability3 => None, + hotbar::SlotContents::Ability4 => None, }) .map(|item| item.amount()) .filter(|amount| *amount > 1) @@ -194,3 +231,15 @@ impl From for SlotKind { } impl SumSlot for SlotKind {} + +fn hotbar_image(tool: ToolKind) -> Option { + match tool { + ToolKind::Staff => Some(HotbarImage::FireAoe), + ToolKind::Hammer => Some(HotbarImage::HammerLeap), + ToolKind::Axe => Some(HotbarImage::AxeLeapSlash), + ToolKind::Bow => Some(HotbarImage::BowJumpBurst), + ToolKind::Debug => Some(HotbarImage::SnakeArrow), + ToolKind::Sword => Some(HotbarImage::SwordWhirlwind), + _ => None, + } +} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7eb334b345..4d079262c2 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1153,6 +1153,7 @@ impl PlayState for SessionState { client.perform_trade_action(action); }, HudEvent::Ability3(state) => self.inputs.ability3.set_state(state), + HudEvent::Ability4(state) => self.inputs.ability4.set_state(state), HudEvent::ChangeFOV(new_fov) => { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn();