Dual wielding now uses skillbar abilities from multiple weapons.

This commit is contained in:
Sam 2021-02-12 19:43:33 -05:00
parent 6f6a37faf2
commit 8f0cca074d
11 changed files with 267 additions and 46 deletions

View File

@ -20,6 +20,7 @@
secondary: "common.abilities.hammer.charged", secondary: "common.abilities.hammer.charged",
skills: [ skills: [
(Some(Hammer(UnlockLeap)), "common.abilities.hammer.leap"), (Some(Hammer(UnlockLeap)), "common.abilities.hammer.leap"),
(Some(Staff(UnlockShockwave)), "common.abilities.staff.fireshockwave"), // test, remove later
], ],
), ),
Bow: ( Bow: (

View File

@ -3,7 +3,7 @@ ItemDef(
description: "While the metal alloy is relatively simple, this unique circular axe has a unique appearance.", description: "While the metal alloy is relatively simple, this unique circular axe has a unique appearance.",
kind: Tool(( kind: Tool((
kind: Axe, kind: Axe,
hands: TwoHand, hands: OneHand,
stats: ( stats: (
equip_time_millis: 400, equip_time_millis: 400,
power: 1.0, power: 1.0,

View File

@ -46,6 +46,7 @@ pub struct ClientRegister {
/// Messages sent from the client to the server /// Messages sent from the client to the server
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)]
pub enum ClientGeneral { pub enum ClientGeneral {
//Only in Character Screen //Only in Character Screen
RequestCharacterList, RequestCharacterList,
@ -58,6 +59,7 @@ pub enum ClientGeneral {
Character(CharacterId), Character(CharacterId),
Spectate, Spectate,
//Only in game //Only in game
// Large enum variant allowed for clippy because of this
ControllerInputs(comp::ControllerInputs), ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent), ControlEvent(comp::ControlEvent),
ControlAction(comp::ControlAction), ControlAction(comp::ControlAction),

View File

@ -222,6 +222,7 @@ pub struct ControllerInputs {
pub primary: Input, pub primary: Input,
pub secondary: Input, pub secondary: Input,
pub ability3: Input, pub ability3: Input,
pub ability4: Input,
pub jump: Input, pub jump: Input,
pub roll: Input, pub roll: Input,
pub glide: Input, pub glide: Input,
@ -249,6 +250,7 @@ impl ControllerInputs {
self.primary.tick(dt); self.primary.tick(dt);
self.secondary.tick(dt); self.secondary.tick(dt);
self.ability3.tick(dt); self.ability3.tick(dt);
self.ability4.tick(dt);
self.jump.tick(dt); self.jump.tick(dt);
self.roll.tick(dt); self.roll.tick(dt);
self.glide.tick(dt); self.glide.tick(dt);
@ -261,6 +263,7 @@ impl ControllerInputs {
self.primary.tick_freshness(); self.primary.tick_freshness();
self.secondary.tick_freshness(); self.secondary.tick_freshness();
self.ability3.tick_freshness(); self.ability3.tick_freshness();
self.ability4.tick_freshness();
self.jump.tick_freshness(); self.jump.tick_freshness();
self.roll.tick_freshness(); self.roll.tick_freshness();
self.glide.tick_freshness(); self.glide.tick_freshness();
@ -274,6 +277,7 @@ impl ControllerInputs {
self.primary.update_with_new(new.primary); self.primary.update_with_new(new.primary);
self.secondary.update_with_new(new.secondary); self.secondary.update_with_new(new.secondary);
self.ability3.update_with_new(new.ability3); self.ability3.update_with_new(new.ability3);
self.ability4.update_with_new(new.ability4);
self.jump.update_with_new(new.jump); self.jump.update_with_new(new.jump);
self.roll.update_with_new(new.roll); self.roll.update_with_new(new.roll);
self.glide.update_with_new(new.glide); self.glide.update_with_new(new.glide);
@ -287,7 +291,10 @@ impl ControllerInputs {
} }
pub fn holding_ability_key(&self) -> bool { 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()
} }
} }

View File

@ -278,12 +278,13 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
handle_orientation(data, update, 1.0); handle_orientation(data, update, 1.0);
} }
/// First checks whether `primary`, `secondary` or `ability3` input is pressed, /// First checks whether `primary`, `secondary`, `ability3`, or `ability4` input
/// then attempts to go into Equipping state, otherwise Idle /// is pressed, then attempts to go into Equipping state, otherwise Idle
pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) { pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.primary.is_pressed() if data.inputs.primary.is_pressed()
|| data.inputs.secondary.is_pressed() || data.inputs.secondary.is_pressed()
|| data.inputs.ability3.is_pressed() || data.inputs.ability3.is_pressed()
|| data.inputs.ability4.is_pressed()
{ {
attempt_wield(data, update); 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 /// Checks that player can perform a dodge, then
/// attempts to perform their dodge ability /// attempts to perform their dodge ability
pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { 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_ability1_input(data, update);
handle_ability2_input(data, update); handle_ability2_input(data, update);
handle_ability3_input(data, update); handle_ability3_input(data, update);
handle_ability4_input(data, update);
} }
handle_dodge_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::Mouse1 => data.inputs.primary.is_pressed(),
AbilityKey::Mouse2 => data.inputs.secondary.is_pressed(), AbilityKey::Mouse2 => data.inputs.secondary.is_pressed(),
AbilityKey::Skill1 => data.inputs.ability3.is_pressed(), AbilityKey::Skill1 => data.inputs.ability3.is_pressed(),
AbilityKey::Skill2 => data.inputs.ability4.is_pressed(),
AbilityKey::Dodge => data.inputs.roll.is_pressed(), AbilityKey::Dodge => data.inputs.roll.is_pressed(),
} }
} }
@ -570,6 +621,7 @@ pub enum AbilityKey {
Mouse1, Mouse1,
Mouse2, Mouse2,
Skill1, Skill1,
Skill2,
Dodge, Dodge,
} }

View File

@ -19,6 +19,7 @@ impl CharacterBehavior for Data {
handle_ability1_input(&data, &mut update); handle_ability1_input(&data, &mut update);
handle_ability2_input(&data, &mut update); handle_ability2_input(&data, &mut update);
handle_ability3_input(&data, &mut update); handle_ability3_input(&data, &mut update);
handle_ability4_input(&data, &mut update);
handle_dodge_input(&data, &mut update); handle_dodge_input(&data, &mut update);
update update

View File

@ -1,5 +1,9 @@
use crate::hud::slots::EquipSlot; 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}; use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
@ -20,6 +24,7 @@ pub enum Slot {
pub enum SlotContents { pub enum SlotContents {
Inventory(InvSlotId), Inventory(InvSlotId),
Ability3, Ability3,
Ability4,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -103,4 +108,63 @@ impl State {
.for_each(|s| *s = None) .for_each(|s| *s = None)
} }
} }
pub fn maintain_ability4(&mut self, client: &client::Client) {
use specs::WorldExt;
let inventories = client.state().ecs().read_storage::<Inventory>();
let inventory = inventories.get(client.entity());
let stats = client.state().ecs().read_storage::<common::comp::Stats>();
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)
}
}
} }

View File

@ -401,6 +401,7 @@ pub enum Event {
ChangeHotbarState(Box<HotbarState>), ChangeHotbarState(Box<HotbarState>),
TradeAction(TradeAction), TradeAction(TradeAction),
Ability3(bool), Ability3(bool),
Ability4(bool),
Logout, Logout,
Quit, Quit,
ChangeLanguage(Box<LanguageMetadata>), ChangeLanguage(Box<LanguageMetadata>),
@ -2684,8 +2685,9 @@ impl Hud {
bypass_dialog: false, bypass_dialog: false,
}); });
}, },
hotbar::SlotContents::Ability3 => {}, /* Event::Ability3(true), hotbar::SlotContents::Ability3 | hotbar::SlotContents::Ability4 => {
* sticks */ }, /* Event::Ability3(true),
* sticks */
} }
}); });
} }
@ -2693,6 +2695,7 @@ impl Hud {
} }
} }
self.hotbar.maintain_ability3(client); self.hotbar.maintain_ability3(client);
self.hotbar.maintain_ability4(client);
events events
} }
@ -2751,6 +2754,7 @@ impl Hud {
} }
}, },
hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)), hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)),
hotbar::SlotContents::Ability4 => events.push(Event::Ability4(state)),
}); });
} }
} }

View File

@ -452,36 +452,48 @@ impl<'a> Widget for Skillbar<'a> {
.equipped(EquipSlot::Mainhand) .equipped(EquipSlot::Mainhand)
.map(|i| i.kind()) .map(|i| i.kind())
.and_then(|kind| match kind { .and_then(|kind| match kind {
ItemKind::Tool(Tool { kind, .. }) => match kind { ItemKind::Tool(Tool { kind, .. }) => ability_description(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,
},
_ => None, _ => 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 // Slot 1-5
@ -890,3 +902,31 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.m2_ico, ui); .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,
}
}

View File

@ -6,7 +6,7 @@ use super::{
use crate::ui::slot::{self, SlotKey, SumSlot}; use crate::ui::slot::{self, SlotKey, SumSlot};
use common::comp::{ use common::comp::{
item::{ item::{
tool::{AbilityMap, ToolKind}, tool::{AbilityMap, Hands, ToolKind},
ItemKind, ItemKind,
}, },
slot::InvSlotId, slot::InvSlotId,
@ -123,16 +123,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
}; };
tool.and_then(|tool| { tool.and_then(|tool| {
match tool.kind { hotbar_image(tool.kind).map(|i| {
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| {
( (
i, i,
if let Some(skill) = tool.get_abilities(ability_map).skills.get(0) { if let Some(skill) = tool.get_abilities(ability_map).skills.get(0) {
@ -148,6 +139,51 @@ impl<'a> SlotKey<HotbarSource<'a>, 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<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
.and_then(|content| match content { .and_then(|content| match content {
hotbar::SlotContents::Inventory(idx) => inventory.get(idx), hotbar::SlotContents::Inventory(idx) => inventory.get(idx),
hotbar::SlotContents::Ability3 => None, hotbar::SlotContents::Ability3 => None,
hotbar::SlotContents::Ability4 => None,
}) })
.map(|item| item.amount()) .map(|item| item.amount())
.filter(|amount| *amount > 1) .filter(|amount| *amount > 1)
@ -194,3 +231,15 @@ impl From<TradeSlot> for SlotKind {
} }
impl SumSlot for SlotKind {} impl SumSlot for SlotKind {}
fn hotbar_image(tool: ToolKind) -> Option<HotbarImage> {
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,
}
}

View File

@ -1153,6 +1153,7 @@ impl PlayState for SessionState {
client.perform_trade_action(action); client.perform_trade_action(action);
}, },
HudEvent::Ability3(state) => self.inputs.ability3.set_state(state), HudEvent::Ability3(state) => self.inputs.ability3.set_state(state),
HudEvent::Ability4(state) => self.inputs.ability4.set_state(state),
HudEvent::ChangeFOV(new_fov) => { HudEvent::ChangeFOV(new_fov) => {
global_state.settings.graphics.fov = new_fov; global_state.settings.graphics.fov = new_fov;
global_state.settings.save_to_file_warn(); global_state.settings.save_to_file_warn();