Merge branch 'sam/extra-equip-slots' into 'master'

Added Extra Loadout Slots

See merge request veloren/veloren!2295
This commit is contained in:
Samuel Keiffer 2021-05-21 20:28:53 +00:00
commit a0599ac046
31 changed files with 467 additions and 190 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -24,6 +24,10 @@
"hud.bag.feet": "Feet",
"hud.bag.mainhand": "Mainhand",
"hud.bag.offhand": "Offhand",
"hud.bag.inactive_mainhand": "Inactive Mainhand",
"hud.bag.inactive_offhand": "Inactive Offhand",
"hud.bag.swap_equipped_weapons_title": "Swap equipped weapons",
"hud.bag.swap_equipped_weapons_desc": "Press {key}",
"hud.bag.bag": "Bag",
"hud.bag.health": "Health",
"hud.bag.stamina": "Stamina",

View File

@ -805,8 +805,8 @@ fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &T
#[cfg(not(target_arch = "wasm32"))]
pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
(
equipped_item_and_tool(inv, EquipSlot::Mainhand).map(|(_, tool)| tool.kind),
equipped_item_and_tool(inv, EquipSlot::Offhand).map(|(_, tool)| tool.kind),
equipped_item_and_tool(inv, EquipSlot::ActiveMainhand).map(|(_, tool)| tool.kind),
equipped_item_and_tool(inv, EquipSlot::ActiveOffhand).map(|(_, tool)| tool.kind),
)
}
@ -845,14 +845,14 @@ fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
fn get_weapon_rating(inventory: &Inventory, msm: &MaterialStatManifest) -> f32 {
let mainhand_rating =
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::Mainhand) {
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveMainhand) {
weapon_rating(item, msm)
} else {
0.0
};
let offhand_rating =
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::Offhand) {
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveOffhand) {
weapon_rating(item, msm)
} else {
0.0

View File

@ -1,6 +1,6 @@
use crate::comp::{
inventory::{
item::ItemKind,
item::{Hands, ItemKind, Tool},
slot::{ArmorSlot, EquipSlot},
InvSlot,
},
@ -75,8 +75,10 @@ impl Loadout {
(EquipSlot::Armor(ArmorSlot::Bag2), "bag2".to_string()),
(EquipSlot::Armor(ArmorSlot::Bag3), "bag3".to_string()),
(EquipSlot::Armor(ArmorSlot::Bag4), "bag4".to_string()),
(EquipSlot::Mainhand, "active_item".to_string()),
(EquipSlot::Offhand, "second_item".to_string()),
(EquipSlot::ActiveMainhand, "active_mainhand".to_string()),
(EquipSlot::ActiveOffhand, "active_offhand".to_string()),
(EquipSlot::InactiveMainhand, "inactive_mainhand".to_string()),
(EquipSlot::InactiveOffhand, "inactive_offhand".to_string()),
]
.into_iter()
.map(|(equip_slot, persistence_key)| LoadoutSlot::new(equip_slot, persistence_key))
@ -166,23 +168,17 @@ impl Loadout {
}
let item_a = self.swap(equip_slot_a, None);
let item_b = self.swap(equip_slot_b, None);
let item_b = self.swap(equip_slot_b, item_a);
assert_eq!(self.swap(equip_slot_a, item_b), None);
// Check if items can go in the other slots
if item_a
.as_ref()
.map_or(true, |i| equip_slot_b.can_hold(&i.kind()))
&& item_b
.as_ref()
.map_or(true, |i| equip_slot_a.can_hold(&i.kind()))
// Check if items are valid in their new positions
if !self.slot_can_hold(equip_slot_a, self.equipped(equip_slot_b).map(|x| x.kind()))
|| !self.slot_can_hold(equip_slot_b, self.equipped(equip_slot_a).map(|x| x.kind()))
{
// Swap
self.swap(equip_slot_b, item_a).unwrap_none();
self.swap(equip_slot_a, item_b).unwrap_none();
} else {
// Otherwise put the items back
self.swap(equip_slot_a, item_a).unwrap_none();
self.swap(equip_slot_b, item_b).unwrap_none();
// If not, revert the swap
let item_a = self.swap(equip_slot_a, None);
let item_b = self.swap(equip_slot_b, item_a);
assert_eq!(self.swap(equip_slot_a, item_b), None);
}
}
@ -195,7 +191,7 @@ impl Loadout {
let mut suitable_slots = self
.slots
.iter()
.filter(|s| s.equip_slot.can_hold(item_kind));
.filter(|s| self.slot_can_hold(s.equip_slot, Some(item_kind)));
let first = suitable_slots.next();
@ -215,7 +211,7 @@ impl Loadout {
) -> impl Iterator<Item = &Item> {
self.slots
.iter()
.filter(move |s| s.equip_slot.can_hold(&item_kind))
.filter(move |s| self.slot_can_hold(s.equip_slot, Some(&item_kind)))
.filter_map(|s| s.slot.as_ref())
}
@ -264,7 +260,7 @@ impl Loadout {
pub(super) fn inv_slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
self.slots.iter_mut()
.filter_map(|x| x.slot.as_mut().map(|item| item.slots_mut())) // Discard loadout items that have no slots of their own
.flat_map(|loadout_slots| loadout_slots.iter_mut()) //Collapse iter of Vec<InvSlot> to iter of InvSlot
.flat_map(|loadout_slots| loadout_slots.iter_mut()) //Collapse iter of Vec<InvSlot> to iter of InvSlot
}
/// Gets the range of loadout-provided inventory slot indexes that are
@ -294,12 +290,17 @@ impl Loadout {
/// If no slot is available the item is returned.
#[must_use = "Returned item will be lost if not used"]
pub(super) fn try_equip(&mut self, item: Item) -> Result<(), Item> {
if let Some(loadout_slot) = self
let loadout_slot = self
.slots
.iter()
.find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, Some(item.kind())))
.map(|s| s.equip_slot);
if let Some(slot) = self
.slots
.iter_mut()
.find(|s| s.slot.is_none() && s.equip_slot.can_hold(item.kind()))
.find(|s| Some(s.equip_slot) == loadout_slot)
{
loadout_slot.slot = Some(item);
slot.slot = Some(item);
Ok(())
} else {
Err(item)
@ -309,6 +310,90 @@ impl Loadout {
pub(super) fn items(&self) -> impl Iterator<Item = &Item> {
self.slots.iter().filter_map(|x| x.slot.as_ref())
}
/// Checks that a slot can hold a given item
pub(super) fn slot_can_hold(
&self,
equip_slot: EquipSlot,
item_kind: Option<&ItemKind>,
) -> bool {
// Disallow equipping incompatible weapon pairs (i.e a two-handed weapon and a
// one-handed weapon)
if !(match equip_slot {
EquipSlot::ActiveMainhand => Loadout::is_valid_weapon_pair(
item_kind,
self.equipped(EquipSlot::ActiveOffhand).map(|x| &x.kind),
),
EquipSlot::ActiveOffhand => Loadout::is_valid_weapon_pair(
self.equipped(EquipSlot::ActiveMainhand).map(|x| &x.kind),
item_kind,
),
EquipSlot::InactiveMainhand => Loadout::is_valid_weapon_pair(
item_kind,
self.equipped(EquipSlot::InactiveOffhand).map(|x| &x.kind),
),
EquipSlot::InactiveOffhand => Loadout::is_valid_weapon_pair(
self.equipped(EquipSlot::InactiveMainhand).map(|x| &x.kind),
item_kind,
),
_ => true,
}) {
return false;
}
item_kind.map_or(true, |item_kind| equip_slot.can_hold(item_kind))
}
#[rustfmt::skip]
fn is_valid_weapon_pair(main_hand: Option<&ItemKind>, off_hand: Option<&ItemKind>) -> bool {
matches!((main_hand, off_hand),
(Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), None) |
(Some(ItemKind::Tool(Tool { hands: Hands::Two, .. })), None) |
(Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), Some(ItemKind::Tool(Tool { hands: Hands::One, .. }))) |
(None, None))
}
pub(super) fn swap_equipped_weapons(&mut self) {
// Checks if a given slot can hold an item right now, defaults to true if
// nothing is equipped in slot
let valid_slot = |equip_slot| {
self.equipped(equip_slot)
.map_or(true, |i| self.slot_can_hold(equip_slot, Some(i.kind())))
};
// If every weapon is currently in a valid slot, after this change they will
// still be in a valid slot. This is because active mainhand and
// inactive mainhand, and active offhand and inactive offhand have the same
// requirements on what can be equipped.
if valid_slot(EquipSlot::ActiveMainhand)
&& valid_slot(EquipSlot::ActiveOffhand)
&& valid_slot(EquipSlot::InactiveMainhand)
&& valid_slot(EquipSlot::InactiveOffhand)
{
// Get weapons from each slot
let active_mainhand = self.swap(EquipSlot::ActiveMainhand, None);
let active_offhand = self.swap(EquipSlot::ActiveOffhand, None);
let inactive_mainhand = self.swap(EquipSlot::InactiveMainhand, None);
let inactive_offhand = self.swap(EquipSlot::InactiveOffhand, None);
// Equip weapons into new slots
assert!(
self.swap(EquipSlot::ActiveMainhand, inactive_mainhand)
.is_none()
);
assert!(
self.swap(EquipSlot::ActiveOffhand, inactive_offhand)
.is_none()
);
assert!(
self.swap(EquipSlot::InactiveMainhand, active_mainhand)
.is_none()
);
assert!(
self.swap(EquipSlot::InactiveOffhand, active_offhand)
.is_none()
);
}
}
}
#[cfg(test)]

View File

@ -30,7 +30,7 @@ use tracing::warn;
/// // Build a loadout with character starter defaults and a specific sword with default sword abilities
/// let loadout = LoadoutBuilder::new()
/// .defaults()
/// .active_item(Some(Item::new_from_asset_expect("common.items.weapons.sword.steel-8")))
/// .active_mainhand(Some(Item::new_from_asset_expect("common.items.weapons.sword.steel-8")))
/// .build();
/// ```
#[derive(Clone)]
@ -376,7 +376,7 @@ impl LoadoutBuilder {
match config {
Adlet => match active_tool_kind {
Some(ToolKind::Bow) => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.adlet.head.adlet_bow",
)))
@ -397,7 +397,7 @@ impl LoadoutBuilder {
)))
.build(),
Some(ToolKind::Spear) | Some(ToolKind::Staff) => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.adlet.head.adlet_spear",
)))
@ -417,11 +417,11 @@ impl LoadoutBuilder {
"common.items.npc_armor.biped_small.adlet.tail.adlet",
)))
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
_ => LoadoutBuilder::new().active_mainhand(active_item).build(),
},
Gnarling => match active_tool_kind {
Some(ToolKind::Bow) => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.gnarling.head.gnarling",
)))
@ -442,7 +442,7 @@ impl LoadoutBuilder {
)))
.build(),
Some(ToolKind::Staff) => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.gnarling.head.gnarling",
)))
@ -463,7 +463,7 @@ impl LoadoutBuilder {
)))
.build(),
Some(ToolKind::Spear) => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.gnarling.head.gnarling",
)))
@ -480,10 +480,10 @@ impl LoadoutBuilder {
"common.items.npc_armor.biped_small.gnarling.pants.gnarling",
)))
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
_ => LoadoutBuilder::new().active_mainhand(active_item).build(),
},
Sahagin => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.sahagin.head.sahagin",
)))
@ -504,7 +504,7 @@ impl LoadoutBuilder {
)))
.build(),
Haniwa => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.haniwa.head.haniwa",
)))
@ -522,7 +522,7 @@ impl LoadoutBuilder {
)))
.build(),
Myrmidon => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.myrmidon.head.myrmidon",
)))
@ -543,7 +543,7 @@ impl LoadoutBuilder {
)))
.build(),
Husk => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.head(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_small.husk.head.husk",
)))
@ -561,7 +561,7 @@ impl LoadoutBuilder {
)))
.build(),
Guard => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.leather_plate.shoulder",
)))
@ -703,7 +703,7 @@ impl LoadoutBuilder {
}
}
LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.twigsflowers.shoulder",
)))
@ -742,7 +742,7 @@ impl LoadoutBuilder {
.build()
},
Outcast => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.cloth_purple.shoulder",
)))
@ -767,7 +767,7 @@ impl LoadoutBuilder {
})
.build(),
Highwayman => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.swift.shoulder",
)))
@ -795,7 +795,7 @@ impl LoadoutBuilder {
)))
.build(),
Bandit => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.assassin.shoulder",
)))
@ -823,7 +823,7 @@ impl LoadoutBuilder {
)))
.build(),
CultistNovice => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.steel.shoulder",
)))
@ -854,7 +854,7 @@ impl LoadoutBuilder {
)))
.build(),
CultistAcolyte => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.cultist.shoulder",
)))
@ -885,7 +885,7 @@ impl LoadoutBuilder {
)))
.build(),
Warlord => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.warlord.shoulder",
)))
@ -916,7 +916,7 @@ impl LoadoutBuilder {
)))
.build(),
Warlock => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.shoulder(Some(Item::new_from_asset_expect(
"common.items.armor.warlock.shoulder",
)))
@ -947,7 +947,7 @@ impl LoadoutBuilder {
)))
.build(),
Villager => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.chest(Some(Item::new_from_asset_expect(
match rand::thread_rng().gen_range(0..10) {
0 => "common.items.armor.misc.chest.worker_green_0",
@ -981,36 +981,46 @@ impl LoadoutBuilder {
match body {
Body::BipedLarge(b) => match b.species {
biped_large::Species::Mindflayer => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.biped_large.mindflayer",
)))
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
_ => LoadoutBuilder::new().active_mainhand(active_item).build(),
},
Body::Golem(g) => match g.species {
golem::Species::ClayGolem => LoadoutBuilder::new()
.active_item(active_item)
.active_mainhand(active_item)
.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.golem.claygolem",
)))
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
_ => LoadoutBuilder::new().active_mainhand(active_item).build(),
},
_ => LoadoutBuilder::new().active_item(active_item).build(),
_ => LoadoutBuilder::new().active_mainhand(active_item).build(),
}
};
Self(loadout)
}
pub fn active_item(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::Mainhand, item);
pub fn active_mainhand(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::ActiveMainhand, item);
self
}
pub fn second_item(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::Offhand, item);
pub fn active_offhand(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::ActiveOffhand, item);
self
}
pub fn inactive_mainhand(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::InactiveMainhand, item);
self
}
pub fn inactive_offhand(mut self, item: Option<Item>) -> Self {
self.0.swap(EquipSlot::InactiveOffhand, item);
self
}

View File

@ -552,15 +552,7 @@ impl Inventory {
pub fn equip(&mut self, inv_slot: InvSlotId) -> Vec<Item> {
self.get(inv_slot)
.and_then(|item| self.loadout.get_slot_to_equip_into(item.kind()))
.map(|equip_slot| {
// Special case when equipping into main hand - swap with offhand first
if equip_slot == EquipSlot::Mainhand {
self.loadout
.swap_slots(EquipSlot::Mainhand, EquipSlot::Offhand);
}
self.swap_inventory_loadout(inv_slot, equip_slot)
})
.map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot))
.unwrap_or_else(Vec::new)
}
@ -585,7 +577,7 @@ impl Inventory {
+ i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
- i32::try_from(self.populated_slots())
.expect("Inventory item with more than i32::MAX used slots")
+ inv_slot_for_equipped // If there is no item already in the equip slot we gain 1 slot
+ inv_slot_for_equipped // If there is no item already in the equip slot we gain 1 slot
}
/// Handles picking up an item, unloading any items inside the item being
@ -648,7 +640,7 @@ impl Inventory {
.expect("Equipped item with more than i32::MAX slots")
- i32::try_from(self.populated_slots())
.expect("Inventory item with more than i32::MAX used slots")
- inv_slot_for_unequipped // If there is an item being unequipped we lose 1 slot
- inv_slot_for_unequipped // If there is an item being unequipped we lose 1 slot
}
/// Swaps items from two slots, regardless of if either is inventory or
@ -692,7 +684,7 @@ impl Inventory {
- i32::try_from(self.populated_slots())
.expect("inventory with more than i32::MAX used slots")
- inv_slot_for_equipped // +1 inventory slot required if an item was unequipped
+ inv_slot_for_inv_item // -1 inventory slot required if an item was equipped
+ inv_slot_for_inv_item // -1 inventory slot required if an item was equipped
}
/// Swap item in an inventory slot with one in a loadout slot.
@ -730,6 +722,36 @@ impl Inventory {
})
.unwrap_or_default();
// If 2 1h weapons are equipped, and mainhand weapon removed, move offhand into
// mainhand
match equip_slot {
EquipSlot::ActiveMainhand => {
if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
&& self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
{
let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None);
assert!(
self.loadout
.swap(EquipSlot::ActiveMainhand, offhand)
.is_none()
);
}
},
EquipSlot::InactiveMainhand => {
if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
&& self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
{
let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None);
assert!(
self.loadout
.swap(EquipSlot::InactiveMainhand, offhand)
.is_none()
);
}
},
_ => {},
}
// Attempt to put any items unloaded from the unequipped item into empty
// inventory slots and return any that don't fit to the caller where they
// will be dropped on the ground
@ -743,12 +765,12 @@ impl Inventory {
/// account whether there will be free space in the inventory for the
/// loadout item once any slots that were provided by it have been
/// removed.
#[allow(clippy::blocks_in_if_conditions)]
pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
// Check if loadout slot can hold item
if !self
.get(inv_slot_id)
.map_or(true, |item| equip_slot.can_hold(&item.kind()))
{
if !self.get(inv_slot_id).map_or(true, |item| {
self.loadout.slot_can_hold(equip_slot, Some(&item.kind()))
}) {
trace!("can_swap = false, equip slot can't hold item");
return false;
}
@ -777,6 +799,8 @@ impl Inventory {
pub fn equipped_items_of_kind(&self, item_kind: ItemKind) -> impl Iterator<Item = &Item> {
self.loadout.equipped_items_of_kind(item_kind)
}
pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() }
}
impl Component for Inventory {

View File

@ -3,7 +3,7 @@ use std::{cmp::Ordering, convert::TryFrom};
use crate::comp::{
inventory::{
item::{armor, armor::ArmorKind, ItemKind},
item::{armor, armor::ArmorKind, tool, ItemKind},
loadout::LoadoutSlotId,
},
item,
@ -81,8 +81,10 @@ impl From<InvSlotId> for SlotId {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub enum EquipSlot {
Armor(ArmorSlot),
Mainhand,
Offhand,
ActiveMainhand,
ActiveOffhand,
InactiveMainhand,
InactiveOffhand,
Lantern,
Glider,
}
@ -120,8 +122,10 @@ impl EquipSlot {
pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
match (self, item_kind) {
(Self::Armor(slot), ItemKind::Armor(armor::Armor { kind, .. })) => slot.can_hold(kind),
(Self::Mainhand, ItemKind::Tool(_)) => true,
(Self::Offhand, ItemKind::Tool(_)) => true,
(Self::ActiveMainhand, ItemKind::Tool(_)) => true,
(Self::ActiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
(Self::InactiveMainhand, ItemKind::Tool(_)) => true,
(Self::InactiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
(Self::Lantern, ItemKind::Lantern(_)) => true,
(Self::Glider, ItemKind::Glider(_)) => true,
_ => false,

View File

@ -231,23 +231,29 @@ fn unequip_items_both_hands() {
let sword = Item::new_from_asset_expect("common.items.weapons.sword.steel-8");
inv.replace_loadout_item(EquipSlot::Mainhand, Some(sword.duplicate(ability_map, msm)));
inv.replace_loadout_item(EquipSlot::Offhand, Some(sword.duplicate(ability_map, msm)));
inv.replace_loadout_item(
EquipSlot::ActiveMainhand,
Some(sword.duplicate(ability_map, msm)),
);
inv.replace_loadout_item(
EquipSlot::InactiveMainhand,
Some(sword.duplicate(ability_map, msm)),
);
// Fill all inventory slots except one
fill_inv_slots(&mut inv, 17);
let result = inv.unequip(EquipSlot::Mainhand);
let result = inv.unequip(EquipSlot::ActiveMainhand);
// We have space in the inventory, so this should have unequipped
assert_eq!(None, inv.equipped(EquipSlot::Mainhand));
assert_eq!(None, inv.equipped(EquipSlot::ActiveMainhand));
assert_eq!(18, inv.populated_slots());
assert_eq!(true, result.is_ok());
let result = inv.unequip(EquipSlot::Offhand).unwrap_err();
let result = inv.unequip(EquipSlot::InactiveMainhand).unwrap_err();
assert_eq!(SlotError::InventoryFull, result);
// There is no more space in the inventory, so this should still be equipped
assert_eq!(&sword, inv.equipped(EquipSlot::Offhand).unwrap());
assert_eq!(&sword, inv.equipped(EquipSlot::InactiveMainhand).unwrap());
// Verify inventory
assert_eq!(inv.slots[17], Some(sword));

View File

@ -14,6 +14,7 @@
option_expect_none,
option_unwrap_none,
option_zip,
or_patterns,
trait_alias,
type_alias_impl_trait
)]

View File

@ -440,14 +440,34 @@ pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
/// If a tool is equipped, goes into Equipping state, otherwise goes to Idle
pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) {
if let Some((item, ItemKind::Tool(tool))) = data
.inventory
.equipped(EquipSlot::Mainhand)
.map(|i| (i, i.kind()))
{
// Closure to get equip time provided an equip slot if a tool is equipped in
// equip slot
let equip_time = |equip_slot| {
data.inventory
.equipped(equip_slot)
.and_then(|item| match item.kind() {
ItemKind::Tool(tool) => Some((item, tool)),
_ => None,
})
.map(|(item, tool)| tool.equip_time(data.msm, item.components()))
};
// Calculates time required to equip weapons, if weapon in mainhand and offhand,
// uses maximum duration
let mainhand_equip_time = equip_time(EquipSlot::ActiveMainhand);
let offhand_equip_time = equip_time(EquipSlot::ActiveOffhand);
let equip_time = match (mainhand_equip_time, offhand_equip_time) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
};
// Moves entity into equipping state if there is some equip time, else moves
// intantly into wield
if let Some(equip_time) = equip_time {
update.character = CharacterState::Equipping(equipping::Data {
static_data: equipping::StaticData {
buildup_duration: tool.equip_time(data.msm, item.components()),
buildup_duration: equip_time,
},
timer: Duration::default(),
});
@ -504,7 +524,15 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) -> bool {
/// Checks that player can Swap Weapons and updates `Loadout` if so
pub fn attempt_swap_equipped_weapons(data: &JoinData, update: &mut StateUpdate) {
if data.inventory.equipped(EquipSlot::Offhand).is_some() {
if data
.inventory
.equipped(EquipSlot::InactiveMainhand)
.is_some()
|| data
.inventory
.equipped(EquipSlot::InactiveOffhand)
.is_some()
{
update.swap_equipped_weapons = true;
}
}
@ -556,14 +584,14 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
let no_main_hand = hands.0.is_none();
// skill_index used to select ability for the AbilityKey::Skill2 input
let (equip_slot, skill_index) = if no_main_hand {
(Some(EquipSlot::Offhand), 1)
(Some(EquipSlot::ActiveOffhand), 1)
} else if always_main_hand {
(Some(EquipSlot::Mainhand), 0)
(Some(EquipSlot::ActiveMainhand), 0)
} else {
match hands {
(Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
(_, Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
(_, Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
(_, _) => (None, 0),
}
};
@ -596,7 +624,11 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
{
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(data, matches!(equip_slot, EquipSlot::Offhand), input),
AbilityInfo::from_input(
data,
matches!(equip_slot, EquipSlot::ActiveOffhand),
input,
),
));
}
}
@ -640,7 +672,8 @@ pub fn handle_block_input(data: &JoinData, update: &mut StateUpdate) {
|equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some(tool) if tool.can_block());
let hands = get_hands(data);
if input_is_pressed(data, InputKind::Block)
&& (can_block(EquipSlot::Mainhand) || (hands.0.is_none() && can_block(EquipSlot::Offhand)))
&& (can_block(EquipSlot::ActiveMainhand)
|| (hands.0.is_none() && can_block(EquipSlot::ActiveOffhand)))
{
let ability = CharacterAbility::default_block();
if ability.requirements_paid(data, update) {
@ -696,15 +729,18 @@ pub fn get_hands(data: &JoinData) -> (Option<Hands>, Option<Hands>) {
None
}
};
(hand(EquipSlot::Mainhand), hand(EquipSlot::Offhand))
(
hand(EquipSlot::ActiveMainhand),
hand(EquipSlot::ActiveOffhand),
)
}
pub fn get_crit_data(data: &JoinData, ai: AbilityInfo) -> (f32, f32) {
const DEFAULT_CRIT_DATA: (f32, f32) = (0.5, 1.3);
use HandInfo::*;
let slot = match ai.hand {
Some(TwoHanded) | Some(MainHand) => EquipSlot::Mainhand,
Some(OffHand) => EquipSlot::Offhand,
Some(TwoHanded) | Some(MainHand) => EquipSlot::ActiveMainhand,
Some(OffHand) => EquipSlot::ActiveOffhand,
None => return DEFAULT_CRIT_DATA,
};
if let Some(item) = data.inventory.equipped(slot) {
@ -792,9 +828,9 @@ pub struct AbilityInfo {
impl AbilityInfo {
pub fn from_input(data: &JoinData, from_offhand: bool, input: InputKind) -> Self {
let tool_data = if from_offhand {
unwrap_tool_data(data, EquipSlot::Offhand)
unwrap_tool_data(data, EquipSlot::ActiveOffhand)
} else {
unwrap_tool_data(data, EquipSlot::Mainhand)
unwrap_tool_data(data, EquipSlot::ActiveMainhand)
};
let (tool, hand) = (
tool_data.map(|t| t.kind),

View File

@ -60,9 +60,12 @@ impl CharacterBehavior for Data {
fn manipulate_loadout(&self, data: &JoinData, inv_action: InventoryAction) -> StateUpdate {
let mut update = StateUpdate::from(data);
match inv_action {
InventoryAction::Drop(EquipSlot::Mainhand)
| InventoryAction::Swap(EquipSlot::Mainhand, _)
| InventoryAction::Swap(_, Slot::Equip(EquipSlot::Mainhand)) => {
InventoryAction::Drop(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand)
| InventoryAction::Swap(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand, _)
| InventoryAction::Swap(
_,
Slot::Equip(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand),
) => {
update.character = CharacterState::Idle;
},
_ => (),

View File

@ -5,14 +5,9 @@ use specs::{
use common::{
comp::{
self,
inventory::{
item::MaterialStatManifest,
slot::{EquipSlot, Slot},
},
Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, Inventory, Mass,
Melee, Mounting, Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, Stats,
Vel,
self, inventory::item::MaterialStatManifest, Beam, Body, CharacterState, Combo, Controller,
Density, Energy, Health, Inventory, Mass, Melee, Mounting, Ori, PhysicsState, Poise,
PoiseState, Pos, SkillSet, StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome,
@ -48,16 +43,7 @@ fn incorporate_update(join: &mut JoinStruct, mut state_update: StateUpdate) {
if state_update.swap_equipped_weapons {
let mut inventory = join.inventory.get_mut_unchecked();
let inventory = &mut *inventory;
assert!(
inventory
.swap(
Slot::Equip(EquipSlot::Mainhand),
Slot::Equip(EquipSlot::Offhand),
)
.first()
.is_none(),
"Swapping main and offhand never results in leftover items",
);
inventory.swap_equipped_weapons();
}
}

View File

@ -38,7 +38,7 @@ pub fn create_character(
let loadout = LoadoutBuilder::new()
.defaults()
.active_item(Some(Item::new_from_asset_expect(&tool_id)))
.active_mainhand(Some(Item::new_from_asset_expect(&tool_id)))
.build();
let mut inventory = Inventory::new_with_loadout(loadout);

View File

@ -254,15 +254,15 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possesse_uid: Uid
assert!(
inventory
.swap(
Slot::Equip(EquipSlot::Mainhand),
Slot::Equip(EquipSlot::Offhand),
Slot::Equip(EquipSlot::ActiveMainhand),
Slot::Equip(EquipSlot::InactiveMainhand),
)
.first()
.is_none(),
"Swapping main and offhand never results in leftover items",
"Swapping active and inactive mainhands never results in leftover items",
);
inventory.replace_loadout_item(EquipSlot::Mainhand, Some(debug_item));
inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item));
}
// Remove will of the entity

View File

@ -0,0 +1,8 @@
-- Sets active_item to active_mainhand and second_item to inactive_mainhand.
--
-- second_item becomes inactive_mainhand because active_offhand is enforced to be 1h
-- and second_item was not necessarily guaranteed to be 1h.
UPDATE item
SET position = 'active_mainhand' WHERE position = 'active_item';
UPDATE item
SET position = 'inactive_mainhand' WHERE position = 'second_item';

View File

@ -1553,7 +1553,7 @@ impl<'a> AgentData<'a> {
let tactic = self
.inventory
.equipped(EquipSlot::Mainhand)
.equipped(EquipSlot::ActiveMainhand)
.as_ref()
.map(|item| {
if let Some(ability_spec) = item.ability_spec() {

View File

@ -145,7 +145,7 @@ impl CombatEventMapper {
previous_state: &PreviousEntityState,
inventory: &Inventory,
) -> SfxEvent {
if let Some(item) = inventory.equipped(EquipSlot::Mainhand) {
if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
if let ItemKind::Tool(data) = item.kind() {
if character_state.is_attack() {
return SfxEvent::Attack(

View File

@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
#[test]
fn maps_wield_while_equipping() {
let loadout = LoadoutBuilder::new()
.active_item(Some(Item::new_from_asset_expect(
.active_mainhand(Some(Item::new_from_asset_expect(
"common.items.weapons.axe.starter_axe",
)))
.build();
@ -40,7 +40,7 @@ fn maps_wield_while_equipping() {
#[test]
fn maps_unwield() {
let loadout = LoadoutBuilder::new()
.active_item(Some(Item::new_from_asset_expect(
.active_mainhand(Some(Item::new_from_asset_expect(
"common.items.weapons.bow.starter",
)))
.build();
@ -62,7 +62,7 @@ fn maps_unwield() {
#[test]
fn maps_basic_melee() {
let loadout = LoadoutBuilder::new()
.active_item(Some(Item::new_from_asset_expect(
.active_mainhand(Some(Item::new_from_asset_expect(
"common.items.weapons.axe.starter_axe",
)))
.build();
@ -104,7 +104,7 @@ fn maps_basic_melee() {
#[test]
fn matches_ability_stage() {
let loadout = LoadoutBuilder::new()
.active_item(Some(Item::new_from_asset_expect(
.active_mainhand(Some(Item::new_from_asset_expect(
"common.items.weapons.sword.starter",
)))
.build();
@ -163,7 +163,7 @@ fn matches_ability_stage() {
#[test]
fn ignores_different_ability_stage() {
let loadout = LoadoutBuilder::new()
.active_item(Some(Item::new_from_asset_expect(
.active_mainhand(Some(Item::new_from_asset_expect(
"common.items.weapons.axe.starter_axe",
)))
.build();

View File

@ -13,6 +13,8 @@ use crate::{
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
Tooltipable,
},
window::GameInput,
GlobalState,
};
use client::Client;
use common::{
@ -448,8 +450,11 @@ widget_ids! {
back_slot,
tabard_slot,
glider_slot,
mainhand_slot,
offhand_slot,
active_mainhand_slot,
active_offhand_slot,
inactive_mainhand_slot,
inactive_offhand_slot,
swap_equipped_weapons_btn,
bag1_slot,
bag2_slot,
bag3_slot,
@ -463,6 +468,7 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct Bag<'a> {
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
@ -487,6 +493,7 @@ impl<'a> Bag<'a> {
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn new(
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
@ -506,6 +513,7 @@ impl<'a> Bag<'a> {
) -> Self {
Self {
client,
global_state,
imgs,
item_imgs,
fonts,
@ -543,6 +551,7 @@ pub enum Event {
BagExpand,
Close,
SortInventory,
SwapEquippedWeapons,
}
impl<'a> Widget for Bag<'a> {
@ -567,6 +576,7 @@ impl<'a> Widget for Bag<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args;
let i18n = &self.localized_strings;
let key_layout = &self.global_state.window.key_layout;
let mut event = None;
let bag_tooltip = Tooltip::new({
@ -690,7 +700,7 @@ impl<'a> Widget for Bag<'a> {
if inventory.slots().count() > 45 || self.show.bag_inv {
let expand_btn_top = if self.show.bag_inv { 53.0 } else { 460.0 };
if expand_btn
.top_left_with_margins_on(state.bg_ids.bg_frame, expand_btn_top, 211.5)
.top_right_with_margins_on(state.bg_ids.bg_frame, expand_btn_top, 37.0)
.with_tooltip(self.tooltip_manager, &txt, "", &bag_tooltip, TEXT_COLOR)
.set(state.ids.bag_expand_btn, ui)
.was_clicked()
@ -1152,19 +1162,19 @@ impl<'a> Widget for Bag<'a> {
)
.set(state.ids.tabard_slot, ui)
}
// Mainhand/Left-Slot
// Active Mainhand/Left-Slot
let mainhand_item = inventory
.equipped(EquipSlot::Mainhand)
.equipped(EquipSlot::ActiveMainhand)
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Mainhand, [85.0; 2])
.fabricate(EquipSlot::ActiveMainhand, [85.0; 2])
.bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0)
.with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
.filled_slot(filled_slot);
if let Some(item) = mainhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
.set(state.ids.mainhand_slot, ui)
.set(state.ids.active_mainhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
@ -1173,20 +1183,21 @@ impl<'a> Widget for Bag<'a> {
&tooltip,
color::WHITE,
)
.set(state.ids.mainhand_slot, ui)
.set(state.ids.active_mainhand_slot, ui)
}
// Offhand/Right-Slot
// Active Offhand/Right-Slot
let offhand_item = inventory
.equipped(EquipSlot::Offhand)
.equipped(EquipSlot::ActiveOffhand)
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::Offhand, [85.0; 2])
.fabricate(EquipSlot::ActiveOffhand, [85.0; 2])
.bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0)
.with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
.filled_slot(filled_slot);
if let Some(item) = offhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
.set(state.ids.offhand_slot, ui)
.set(state.ids.active_offhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
@ -1195,7 +1206,83 @@ impl<'a> Widget for Bag<'a> {
&tooltip,
color::WHITE,
)
.set(state.ids.offhand_slot, ui)
.set(state.ids.active_offhand_slot, ui)
}
// Inactive Mainhand/Left-Slot
let mainhand_item = inventory
.equipped(EquipSlot::InactiveMainhand)
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::InactiveMainhand, [40.0; 2])
.bottom_right_with_margins_on(state.ids.active_mainhand_slot, 3.0, -47.0)
.with_icon(self.imgs.mainhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
.filled_slot(filled_slot);
if let Some(item) = mainhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
.set(state.ids.inactive_mainhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
i18n.get("hud.bag.inactive_mainhand"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.inactive_mainhand_slot, ui)
}
// Inctive Offhand/Right-Slot
let offhand_item = inventory
.equipped(EquipSlot::InactiveOffhand)
.map(|item| item.to_owned());
let slot = slot_maker
.fabricate(EquipSlot::InactiveOffhand, [40.0; 2])
.bottom_left_with_margins_on(state.ids.active_offhand_slot, 3.0, -47.0)
.with_icon(self.imgs.offhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
.filled_slot(filled_slot);
if let Some(item) = offhand_item {
slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
.set(state.ids.inactive_offhand_slot, ui)
} else {
slot.with_tooltip(
self.tooltip_manager,
i18n.get("hud.bag.inactive_offhand"),
"",
&tooltip,
color::WHITE,
)
.set(state.ids.inactive_offhand_slot, ui)
}
if Button::image(self.imgs.swap_equipped_weapons_btn)
.hover_image(self.imgs.swap_equipped_weapons_btn_hover)
.press_image(self.imgs.swap_equipped_weapons_btn_press)
.w_h(32.0, 40.0)
.bottom_left_with_margins_on(state.bg_ids.bg_frame, 0.0, 23.3)
.align_middle_y_of(state.ids.active_mainhand_slot)
.with_tooltip(
self.tooltip_manager,
i18n.get("hud.bag.swap_equipped_weapons_title"),
if let Some(key) = self
.global_state
.settings
.controls
.get_binding(GameInput::SwapLoadout)
{
i18n.get("hud.bag.swap_equipped_weapons_desc")
.replace("{key}", key.display_string(key_layout).as_str())
} else {
"".to_string()
}
.as_str(),
&tooltip,
color::WHITE,
)
.set(state.ids.swap_equipped_weapons_btn, ui)
.was_clicked()
{
event = Some(Event::SwapEquippedWeapons);
}
}

View File

@ -88,9 +88,12 @@ impl State {
_ => None,
};
let equip_slot = match (hands(EquipSlot::Mainhand), hands(EquipSlot::Offhand)) {
(Some(_), _) => Some(EquipSlot::Mainhand),
(_, Some(_)) => Some(EquipSlot::Offhand),
let equip_slot = match (
hands(EquipSlot::ActiveMainhand),
hands(EquipSlot::ActiveOffhand),
) {
(Some(_), _) => Some(EquipSlot::ActiveMainhand),
(_, Some(_)) => Some(EquipSlot::ActiveOffhand),
_ => None,
};
@ -140,14 +143,14 @@ impl State {
_ => None,
};
let active_tool_hands = hands(EquipSlot::Mainhand);
let second_tool_hands = hands(EquipSlot::Offhand);
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
(Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
(Some(_), Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
(None, Some(_)) => (Some(EquipSlot::Offhand), 1),
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
(Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
(None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
(_, _) => (None, 0),
};

View File

@ -421,6 +421,9 @@ image_ids! {
inv_sort_btn: "voxygen.element.ui.bag.buttons.inv_sort",
inv_sort_btn_hover: "voxygen.element.ui.bag.buttons.inv_sort_hover",
inv_sort_btn_press: "voxygen.element.ui.bag.buttons.inv_sort_press",
swap_equipped_weapons_btn: "voxygen.element.ui.bag.buttons.swap_equipped_weapons",
swap_equipped_weapons_btn_hover: "voxygen.element.ui.bag.buttons.swap_equipped_weapons_hover",
swap_equipped_weapons_btn_press: "voxygen.element.ui.bag.buttons.swap_equipped_weapons_press",
coin_ico: "voxygen.element.items.coin",
cheese_ico: "voxygen.element.items.item_cheese",
inv_bg_armor: "voxygen.element.ui.bag.inv_bg_0",

View File

@ -373,6 +373,7 @@ pub enum Event {
slot: comp::slot::Slot,
bypass_dialog: bool,
},
SwapEquippedWeapons,
SwapSlots {
slot_a: comp::slot::Slot,
slot_b: comp::slot::Slot,
@ -2448,6 +2449,7 @@ impl Hud {
) {
match Bag::new(
client,
global_state,
&self.imgs,
&self.item_imgs,
&self.fonts,
@ -2479,6 +2481,9 @@ impl Hud {
};
},
Some(bag::Event::SortInventory) => self.events.push(Event::SortInventory),
Some(bag::Event::SwapEquippedWeapons) => {
self.events.push(Event::SwapEquippedWeapons)
},
None => {},
}
}

View File

@ -536,7 +536,7 @@ impl<'a> Widget for Skillbar<'a> {
.map(|item| (item.name(), item.description())),
hotbar::SlotContents::Ability3 => content_source
.1
.equipped(EquipSlot::Mainhand)
.equipped(EquipSlot::ActiveMainhand)
.map(|i| i.kind())
.and_then(|kind| match kind {
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
@ -552,14 +552,14 @@ impl<'a> Widget for Skillbar<'a> {
_ => None,
};
let active_tool_hands = hands(EquipSlot::Mainhand);
let second_tool_hands = hands(EquipSlot::Offhand);
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
let equip_slot = match (active_tool_hands, second_tool_hands) {
(Some(Hands::Two), _) => Some(EquipSlot::Mainhand),
(Some(_), Some(Hands::One)) => Some(EquipSlot::Offhand),
(Some(Hands::One), _) => Some(EquipSlot::Mainhand),
(None, Some(_)) => Some(EquipSlot::Offhand),
(Some(Hands::Two), _) => Some(EquipSlot::ActiveMainhand),
(Some(_), Some(Hands::One)) => Some(EquipSlot::ActiveOffhand),
(Some(Hands::One), _) => Some(EquipSlot::ActiveMainhand),
(None, Some(_)) => Some(EquipSlot::ActiveOffhand),
(_, _) => None,
};
@ -657,8 +657,8 @@ impl<'a> Widget for Skillbar<'a> {
.right_from(state.ids.slot5, slot_offset)
.set(state.ids.m1_slot_bg, ui);
let active_tool = get_item_and_tool(self.inventory, EquipSlot::Mainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::Offhand);
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
let tool = match (
active_tool.map(|(_, x)| x.hands),
@ -700,8 +700,8 @@ impl<'a> Widget for Skillbar<'a> {
}
}
let active_tool = get_item_and_tool(self.inventory, EquipSlot::Mainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::Offhand);
let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
let tool = match (
active_tool.map(|(_, x)| x.hands),

View File

@ -143,12 +143,12 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
_ => None,
};
let active_tool_hands = hands(EquipSlot::Mainhand);
let second_tool_hands = hands(EquipSlot::Offhand);
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
let equip_slot = match (active_tool_hands, second_tool_hands) {
(Some(_), _) => Some(EquipSlot::Mainhand),
(None, Some(_)) => Some(EquipSlot::Offhand),
(Some(_), _) => Some(EquipSlot::ActiveMainhand),
(None, Some(_)) => Some(EquipSlot::ActiveOffhand),
(_, _) => None,
};
@ -189,14 +189,14 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
_ => None,
};
let active_tool_hands = hands(EquipSlot::Mainhand);
let second_tool_hands = hands(EquipSlot::Offhand);
let active_tool_hands = hands(EquipSlot::ActiveMainhand);
let second_tool_hands = hands(EquipSlot::ActiveOffhand);
let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
(Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
(Some(_), Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
(None, Some(_)) => (Some(EquipSlot::Offhand), 1),
(Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
(Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
(Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
(None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
(_, _) => (None, 0),
};

View File

@ -182,7 +182,7 @@ impl Mode {
let loadout = LoadoutBuilder::new()
.defaults()
.active_item(Some(Item::new_from_asset_expect(tool)))
.active_mainhand(Some(Item::new_from_asset_expect(tool)))
.build();
let inventory = Box::new(Inventory::new_with_loadout(loadout));
@ -1328,7 +1328,7 @@ impl Controls {
{
*tool = value;
inventory.replace_loadout_item(
EquipSlot::Mainhand,
EquipSlot::ActiveMainhand,
Some(Item::new_from_asset_expect(*tool)),
);
}

View File

@ -221,10 +221,10 @@ impl CharacterCacheKey {
};
Some(CharacterToolKey {
active: inventory
.equipped(EquipSlot::Mainhand)
.equipped(EquipSlot::ActiveMainhand)
.map(tool_key_from_item),
second: inventory
.equipped(EquipSlot::Offhand)
.equipped(EquipSlot::ActiveOffhand)
.map(tool_key_from_item),
})
} else {

View File

@ -740,9 +740,9 @@ impl FigureMgr {
};
let (active_tool_kind, active_tool_hand, active_tool_spec) =
tool_info(EquipSlot::Mainhand);
tool_info(EquipSlot::ActiveMainhand);
let (second_tool_kind, second_tool_hand, second_tool_spec) =
tool_info(EquipSlot::Offhand);
tool_info(EquipSlot::ActiveOffhand);
let hands = (active_tool_hand, second_tool_hand);

View File

@ -307,7 +307,7 @@ impl Scene {
.clean(&mut self.col_lights, scene_data.tick);
let active_item_kind = inventory
.and_then(|inv| inv.equipped(EquipSlot::Mainhand))
.and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
.map(|i| i.kind());
let (active_tool_kind, active_tool_hand) =
@ -318,7 +318,7 @@ impl Scene {
};
let second_item_kind = inventory
.and_then(|inv| inv.equipped(EquipSlot::Offhand))
.and_then(|inv| inv.equipped(EquipSlot::ActiveOffhand))
.map(|i| i.kind());
let (second_tool_kind, second_tool_hand) =

View File

@ -329,7 +329,7 @@ impl PlayState for SessionState {
.borrow()
.inventories()
.get(player_entity)
.and_then(|inv| inv.equipped(EquipSlot::Mainhand))
.and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
.and_then(|item| item.tool())
.map_or(false, |tool| tool.kind == ToolKind::Pick)
&& self.client.borrow().is_wielding() == Some(true);
@ -1086,6 +1086,9 @@ impl PlayState for SessionState {
self.client.borrow_mut().use_slot(slot);
}
},
HudEvent::SwapEquippedWeapons => {
self.client.borrow_mut().swap_loadout();
},
HudEvent::SwapSlots {
slot_a,
slot_b,