From c1c09dce1b11202876a46a86bc71540ebb98e64c Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 9 Apr 2020 22:36:35 -0400 Subject: [PATCH] Enable unequipping as well as equipping to specific slots --- assets/voxygen/item_image_manifest.ron | 14 +- client/src/lib.rs | 6 +- common/src/comp/controller.rs | 8 +- common/src/comp/inventory/item/armor.rs | 8 - common/src/comp/inventory/item/mod.rs | 9 + common/src/comp/inventory/mod.rs | 9 +- common/src/comp/inventory/slot.rs | 250 ++++++++++++++++++++ common/src/comp/mod.rs | 2 +- common/src/lib.rs | 1 + server/src/events/inventory_manip.rs | 288 ++++++++++++------------ voxygen/src/hud/bag.rs | 37 ++- voxygen/src/hud/item_imgs.rs | 4 +- voxygen/src/hud/mod.rs | 49 ++-- voxygen/src/hud/slots.rs | 59 ++--- voxygen/src/scene/figure/load.rs | 31 ++- voxygen/src/session.rs | 19 +- voxygen/src/ui/widgets/slot.rs | 4 +- 17 files changed, 506 insertions(+), 292 deletions(-) create mode 100644 common/src/comp/inventory/slot.rs diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index afc2378592..58dde6e4f9 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -54,6 +54,13 @@ "voxel.weapon.shield.wood-0", (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 2.4, ), + // Lanterns + Lantern(Black0): Png( + "element.icons.lantern_black-0", + ), + Lantern(Green0): Png( + "element.icons.lantern_green-0", + ), // Other Utility(Collar): Png( "element.icons.collar", @@ -251,13 +258,6 @@ Armor(Neck(Neck0)): Png( "element.icons.neck-0", ), - // Lanterns - Armor(Lantern(Black0)): Png( - "element.icons.lantern_black-0", - ), - Armor(Lantern(Green0)): Png( - "element.icons.lantern_green-0", - ), // Tabards Armor(Tabard(Admin)): Png( "element.icons.tabard_admin", diff --git a/client/src/lib.rs b/client/src/lib.rs index 1cbe9a72ce..1e4aea04b6 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -244,21 +244,21 @@ impl Client { // Can't fail } - pub fn use_inventory_slot(&mut self, slot: usize) { + pub fn use_slot(&mut self, slot: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Use(slot), ))); } - pub fn swap_inventory_slots(&mut self, a: usize, b: usize) { + pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Swap(a, b), ))); } - pub fn drop_inventory_slot(&mut self, slot: usize) { + pub fn drop_slot(&mut self, slot: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Drop(slot), diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 49c5cf8658..6daf98e1cf 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -1,4 +1,4 @@ -use crate::{sync::Uid, util::Dir}; +use crate::{comp::inventory::slot::Slot, sync::Uid, util::Dir}; use specs::{Component, FlaggedStorage}; use specs_idvs::IDVStorage; use std::time::Duration; @@ -11,9 +11,9 @@ pub const DEFAULT_HOLD_DURATION: Duration = Duration::from_millis(200); pub enum InventoryManip { Pickup(Uid), Collect(Vec3), - Use(usize), - Swap(usize, usize), - Drop(usize), + Use(Slot), + Swap(Slot, Slot), + Drop(Slot), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index b212474273..74885598be 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -188,13 +188,6 @@ pub enum Neck { pub const ALL_NECKS: [Neck; 1] = [Neck::Neck0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] -pub enum Lantern { - Black0 = 1, - Green0 = 2, -} -pub const ALL_LANTERNS: [Lantern; 2] = [Lantern::Black0, Lantern::Green0]; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[repr(u32)] pub enum Head { Leather0 = 1, AssaMask0 = 2, @@ -218,7 +211,6 @@ pub enum Armor { Back(Back), Ring(Ring), Neck(Neck), - Lantern(Lantern), Head(Head), Tabard(Tabard), } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 0fa256a5ef..42dcdd6a01 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -36,12 +36,21 @@ pub enum Ingredient { Grass, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Lantern { + Black0 = 1, + Green0 = 2, +} +pub const ALL_LANTERNS: [Lantern; 2] = [Lantern::Black0, Lantern::Green0]; + fn default_amount() -> u32 { 1 } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ItemKind { /// Something wieldable Tool(tool::Tool), + Lantern(Lantern), Armor { kind: armor::Armor, stats: armor::Stats, diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index c5ee442e96..e7e126ed96 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,4 +1,5 @@ pub mod item; +pub mod slot; use crate::assets; use item::{Consumable, Item, ItemKind}; @@ -36,7 +37,9 @@ impl Inventory { /// new group. Returns the item again if no space was found. pub fn push(&mut self, item: Item) -> Option { let item = match item.kind { - ItemKind::Tool(_) | ItemKind::Armor { .. } => self.add_to_first_empty(item), + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => { + self.add_to_first_empty(item) + }, ItemKind::Utility { kind: item_kind, amount: new_amount, @@ -239,7 +242,9 @@ impl Inventory { if let Some(Some(item)) = self.slots.get_mut(cell) { let mut return_item = item.clone(); match &mut item.kind { - ItemKind::Tool(_) | ItemKind::Armor { .. } => self.remove(cell), + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => { + self.remove(cell) + }, ItemKind::Utility { kind, amount } => { if *amount <= 1 { self.remove(cell) diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs new file mode 100644 index 0000000000..25730c626b --- /dev/null +++ b/common/src/comp/inventory/slot.rs @@ -0,0 +1,250 @@ +use crate::{comp, comp::item}; +use comp::{Inventory, Loadout}; + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum Slot { + Inventory(usize), + Equip(EquipSlot), +} + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum EquipSlot { + Armor(ArmorSlot), + Mainhand, + Offhand, + Lantern, +} + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum ArmorSlot { + Head, + Neck, + Shoulders, + Chest, + Hands, + Ring, + Back, + Belt, + Legs, + Feet, + Tabard, +} + +//const ALL_ARMOR_SLOTS: [ArmorSlot; 11] = [ +// Head, Neck, Shoulders, Chest, Hands, Ring, Back, Belt, Legs, Feet, Tabard, +//]; + +impl Slot { + pub fn can_hold(self, item_kind: &item::ItemKind) -> bool { + match (self, item_kind) { + (Self::Inventory(_), _) => true, + (Self::Equip(slot), item_kind) => slot.can_hold(item_kind), + } + } +} + +impl EquipSlot { + fn can_hold(self, item_kind: &item::ItemKind) -> bool { + use item::ItemKind; + match (self, item_kind) { + (Self::Armor(slot), ItemKind::Armor { kind, .. }) => slot.can_hold(kind), + (Self::Mainhand, ItemKind::Tool(_)) => true, + (Self::Offhand, ItemKind::Tool(_)) => true, + (Self::Lantern, ItemKind::Lantern(_)) => true, + _ => false, + } + } +} + +impl ArmorSlot { + fn can_hold(self, armor: &item::armor::Armor) -> bool { + use item::armor::Armor; + match (self, armor) { + (Self::Head, Armor::Head(_)) => true, + (Self::Neck, Armor::Neck(_)) => true, + (Self::Shoulders, Armor::Shoulder(_)) => true, + (Self::Chest, Armor::Chest(_)) => true, + (Self::Hands, Armor::Hand(_)) => true, + (Self::Ring, Armor::Ring(_)) => true, + (Self::Back, Armor::Back(_)) => true, + (Self::Belt, Armor::Belt(_)) => true, + (Self::Legs, Armor::Pants(_)) => true, + (Self::Feet, Armor::Foot(_)) => true, + (Self::Tabard, Armor::Tabard(_)) => true, + _ => false, + } + } +} + +// TODO: shouldn't need this +fn item_config(item: item::Item) -> comp::ItemConfig { + let mut abilities = if let item::ItemKind::Tool(tool) = &item.kind { + tool.get_abilities() + } else { + Vec::new() + } + .into_iter(); + + comp::ItemConfig { + item, + ability1: abilities.next(), + ability2: abilities.next(), + ability3: abilities.next(), + block_ability: Some(comp::CharacterAbility::BasicBlock), + dodge_ability: Some(comp::CharacterAbility::Roll), + } +} + +fn loadout_replace( + equip_slot: EquipSlot, + item: Option, + loadout: &mut Loadout, +) -> Option { + use std::mem::replace; + match equip_slot { + EquipSlot::Armor(ArmorSlot::Head) => replace(&mut loadout.head, item), + EquipSlot::Armor(ArmorSlot::Neck) => replace(&mut loadout.neck, item), + EquipSlot::Armor(ArmorSlot::Shoulders) => replace(&mut loadout.shoulder, item), + EquipSlot::Armor(ArmorSlot::Chest) => replace(&mut loadout.chest, item), + EquipSlot::Armor(ArmorSlot::Hands) => replace(&mut loadout.hand, item), + EquipSlot::Armor(ArmorSlot::Ring) => replace(&mut loadout.ring, item), + EquipSlot::Armor(ArmorSlot::Back) => replace(&mut loadout.back, item), + EquipSlot::Armor(ArmorSlot::Belt) => replace(&mut loadout.belt, item), + EquipSlot::Armor(ArmorSlot::Legs) => replace(&mut loadout.pants, item), + EquipSlot::Armor(ArmorSlot::Feet) => replace(&mut loadout.foot, item), + EquipSlot::Armor(ArmorSlot::Tabard) => replace(&mut loadout.tabard, item), + EquipSlot::Lantern => replace(&mut loadout.lantern, item), + EquipSlot::Mainhand => { + replace(&mut loadout.active_item, item.map(item_config)).map(|i| i.item) + }, + EquipSlot::Offhand => { + replace(&mut loadout.second_item, item.map(item_config)).map(|i| i.item) + }, + } +} + +#[must_use] +fn loadout_insert( + equip_slot: EquipSlot, + item: item::Item, + loadout: &mut Loadout, +) -> Option { + loadout_replace(equip_slot, Some(item), loadout) +} + +pub fn loadout_remove(equip_slot: EquipSlot, loadout: &mut Loadout) -> Option { + loadout_replace(equip_slot, None, loadout) +} + +fn swap_inventory_loadout( + inventory_slot: usize, + equip_slot: EquipSlot, + inventory: &mut Inventory, + loadout: &mut Loadout, +) { + // Check if loadout slot can hold item + if inventory + .get(inventory_slot) + .map_or(true, |item| equip_slot.can_hold(&item.kind)) + { + // Take item from loadout + let from_equip = loadout_remove(equip_slot, loadout); + // Swap with item in the inventory + let from_inv = if let Some(item) = from_equip { + // If this fails and we get item back as an err it will just be put back in the + // loadout + inventory + .insert(inventory_slot, item) + .unwrap_or_else(|i| Some(i)) + } else { + inventory.remove(inventory_slot) + }; + // Put item from the inventory in loadout + if let Some(item) = from_inv { + loadout_insert(equip_slot, item, loadout).unwrap_none(); // Can never fail + } + } +} + +fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout) { + // Get items from the slots + let item_a = loadout_remove(slot_a, loadout); + let item_b = loadout_remove(slot_b, loadout); + // Check if items can go in the other slots + if item_a.as_ref().map_or(true, |i| slot_b.can_hold(&i.kind)) + && item_b.as_ref().map_or(true, |i| slot_a.can_hold(&i.kind)) + { + // Swap + loadout_replace(slot_b, item_a, loadout).unwrap_none(); + loadout_replace(slot_a, item_b, loadout).unwrap_none(); + } else { + // Otherwise put the items back + loadout_replace(slot_a, item_a, loadout).unwrap_none(); + loadout_replace(slot_b, item_b, loadout).unwrap_none(); + } +} + +// Should this report if a change actually occurred? (might be useful when +// minimizing network use) +pub fn swap( + slot_a: Slot, + slot_b: Slot, + inventory: Option<&mut Inventory>, + loadout: Option<&mut Loadout>, +) { + match (slot_a, slot_b) { + (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => { + inventory.map(|i| i.swap_slots(slot_a, slot_b)); + }, + (Slot::Inventory(inv_slot), Slot::Equip(equip_slot)) + | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => { + if let Some((inventory, loadout)) = loadout.and_then(|l| inventory.map(|i| (i, l))) { + swap_inventory_loadout(inv_slot, equip_slot, inventory, loadout); + } + }, + + (Slot::Equip(slot_a), Slot::Equip(slot_b)) => { + loadout.map(|l| swap_loadout(slot_a, slot_b, l)); + }, + } +} + +pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) { + use item::{armor::Armor, ItemKind}; + + let equip_slot = inventory.get(slot).and_then(|i| match &i.kind { + ItemKind::Tool(_) => Some(EquipSlot::Mainhand), + ItemKind::Armor { kind, .. } => Some(EquipSlot::Armor(match kind { + Armor::Head(_) => ArmorSlot::Head, + Armor::Neck(_) => ArmorSlot::Neck, + Armor::Shoulder(_) => ArmorSlot::Shoulders, + Armor::Chest(_) => ArmorSlot::Chest, + Armor::Hand(_) => ArmorSlot::Hands, + Armor::Ring(_) => ArmorSlot::Ring, + Armor::Back(_) => ArmorSlot::Back, + Armor::Belt(_) => ArmorSlot::Belt, + Armor::Pants(_) => ArmorSlot::Legs, + Armor::Foot(_) => ArmorSlot::Feet, + Armor::Tabard(_) => ArmorSlot::Tabard, + })), + ItemKind::Lantern(_) => Some(EquipSlot::Lantern), + _ => None, + }); + + if let Some(equip_slot) = equip_slot { + // If item is going to mainhand, put mainhand in offhand and place offhand in + // inventory + if let EquipSlot::Mainhand = equip_slot { + swap_loadout(EquipSlot::Mainhand, EquipSlot::Offhand, loadout); + } + + swap_inventory_loadout(slot, equip_slot, inventory, loadout); + } +} + +pub fn unequip(slot: EquipSlot, inventory: &mut Inventory, loadout: &mut Loadout) { + loadout_remove(slot, loadout) // Remove item from loadout + .and_then(|i| inventory.push(i)) // Insert into inventory + .and_then(|i| loadout_insert(slot, i, loadout)) // If that fails put back in loadout + .unwrap(); // Never fails +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 00079da365..09f88a6a08 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -31,7 +31,7 @@ pub use controller::{ pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; pub use inventory::{ - item, item::Item, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR, + item, item::Item, slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR, }; pub use last::Last; pub use location::{Waypoint, WaypointArea}; diff --git a/common/src/lib.rs b/common/src/lib.rs index 70df9a2c1d..df969e5af4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,6 +2,7 @@ #![type_length_limit = "1664759"] #![feature( arbitrary_enum_discriminant, + option_unwrap_none, bool_to_option, label_break_value, trait_alias, diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 7174c15e64..9181ce12f5 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -1,6 +1,10 @@ use crate::{Server, StateExt}; use common::{ - comp::{self, item, Pos, MAX_PICKUP_RANGE_SQR}, + comp::{ + self, item, + slot::{self, Slot}, + Pos, MAX_PICKUP_RANGE_SQR, + }, sync::WorldSyncExt, terrain::block::Block, vol::{ReadVol, Vox}, @@ -83,162 +87,145 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv } }, - comp::InventoryManip::Use(slot_idx) => { - let item_opt = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.take(slot_idx)); + comp::InventoryManip::Use(slot) => { + let mut inventories = state.ecs().write_storage::(); + let inventory = if let Some(inventory) = inventories.get_mut(entity) { + inventory + } else { + error!("Can't manipulate inventory, entity doesn't have one"); + return; + }; - let mut event = comp::InventoryUpdateEvent::Used; + let mut maybe_effect = None; - if let Some(item) = item_opt { - match &item.kind { - item::ItemKind::Tool(tool) => { - if let Some(loadout) = - state.ecs().write_storage::().get_mut(entity) - { - // Insert old item into inventory - if let Some(old_item) = loadout.active_item.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, old_item.item)); - } - - let mut abilities = tool.get_abilities(); - let mut ability_drain = abilities.drain(..); - let active_item = comp::ItemConfig { - item, - ability1: ability_drain.next(), - ability2: ability_drain.next(), - ability3: ability_drain.next(), - block_ability: Some(comp::CharacterAbility::BasicBlock), - dodge_ability: Some(comp::CharacterAbility::Roll), - }; - loadout.active_item = Some(active_item); + let event = match slot { + Slot::Inventory(slot) => { + use item::ItemKind; + // Check if item is equipable + if inventory.get(slot).map_or(false, |i| match &i.kind { + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => true, + _ => false, + }) { + if let Some(loadout) = state.ecs().write_storage().get_mut(entity) { + slot::equip(slot, inventory, loadout); + Some(comp::InventoryUpdateEvent::Used) + } else { + None } - }, - - item::ItemKind::Consumable { kind, effect, .. } => { - event = comp::InventoryUpdateEvent::Consumed(*kind); - state.apply_effect(entity, *effect); - }, - - item::ItemKind::Armor { kind, .. } => { - if let Some(loadout) = - state.ecs().write_storage::().get_mut(entity) - { - use comp::item::armor::Armor::*; - let slot = match kind.clone() { - Shoulder(_) => &mut loadout.shoulder, - Chest(_) => &mut loadout.chest, - Belt(_) => &mut loadout.belt, - Hand(_) => &mut loadout.hand, - Pants(_) => &mut loadout.pants, - Foot(_) => &mut loadout.foot, - Back(_) => &mut loadout.back, - Ring(_) => &mut loadout.ring, - Neck(_) => &mut loadout.neck, - Lantern(_) => &mut loadout.lantern, - Head(_) => &mut loadout.head, - Tabard(_) => &mut loadout.tabard, - }; - - // Insert old item into inventory - if let Some(old_item) = slot.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, old_item)); - } - - *slot = Some(item); - } - }, - - item::ItemKind::Utility { kind, .. } => match kind { - comp::item::Utility::Collar => { - let reinsert = if let Some(pos) = - state.read_storage::().get(entity) - { - if ( - &state.read_storage::(), - &state.read_storage::(), - ) - .join() - .filter(|(alignment, _)| { - alignment == &&comp::Alignment::Owned(entity) - }) - .count() - >= 3 + } else if let Some(item) = inventory.take(slot) { + match &item.kind { + ItemKind::Consumable { kind, effect, .. } => { + maybe_effect = Some(*effect); + Some(comp::InventoryUpdateEvent::Consumed(*kind)) + }, + ItemKind::Utility { + kind: comp::item::Utility::Collar, + .. + } => { + let reinsert = if let Some(pos) = + state.read_storage::().get(entity) { - true - } else if let Some(tameable_entity) = { - let nearest_tameable = ( - &state.ecs().entities(), - &state.ecs().read_storage::(), - &state.ecs().read_storage::(), + if ( + &state.read_storage::(), + &state.read_storage::(), ) .join() - .filter(|(_, wild_pos, _)| { - wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0) + .filter(|(alignment, _)| { + alignment == &&comp::Alignment::Owned(entity) }) - .filter(|(_, _, alignment)| { - alignment == &&comp::Alignment::Wild - }) - .min_by_key(|(_, wild_pos, _)| { - (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 - }) - .map(|(entity, _, _)| entity); - nearest_tameable - } { - let _ = state - .ecs() - .write_storage() - .insert(tameable_entity, comp::Alignment::Owned(entity)); - let _ = state - .ecs() - .write_storage() - .insert(tameable_entity, comp::Agent::default()); - false + .count() + >= 3 + { + true + } else if let Some(tameable_entity) = { + let nearest_tameable = ( + &state.ecs().entities(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + ) + .join() + .filter(|(_, wild_pos, _)| { + wild_pos.0.distance_squared(pos.0) + < 5.0f32.powf(2.0) + }) + .filter(|(_, _, alignment)| { + alignment == &&comp::Alignment::Wild + }) + .min_by_key(|(_, wild_pos, _)| { + (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 + }) + .map(|(entity, _, _)| entity); + nearest_tameable + } { + let _ = state.ecs().write_storage().insert( + tameable_entity, + comp::Alignment::Owned(entity), + ); + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Agent::default()); + false + } else { + true + } } else { true + }; + + if reinsert { + let _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, item)); } - } else { - true - }; - if reinsert { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, item)); - } - }, - }, - _ => { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, item)); - }, - } + Some(comp::InventoryUpdateEvent::Used) + }, + _ => { + // TODO: this doesn't work for stackable items + inventory.insert(slot, item).unwrap(); + None + }, + } + } else { + None + } + }, + Slot::Equip(slot) => { + if let Some(loadout) = state.ecs().write_storage().get_mut(entity) { + slot::unequip(slot, inventory, loadout); + Some(comp::InventoryUpdateEvent::Used) + } else { + error!("Entity doesn't have a loadout, can't unequip..."); + None + } + }, + }; + + drop(inventories); + if let Some(effect) = maybe_effect { + state.apply_effect(entity, effect); + } + if let Some(event) = event { + state.write_component(entity, comp::InventoryUpdate::new(event)); } - - state.write_component(entity, comp::InventoryUpdate::new(event)); }, comp::InventoryManip::Swap(a, b) => { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.swap_slots(a, b)); + let ecs = state.ecs(); + let mut inventories = ecs.write_storage(); + let mut loadouts = ecs.write_storage(); + let inventory = inventories.get_mut(entity); + let loadout = loadouts.get_mut(entity); + + slot::swap(a, b, inventory, loadout); + + // :/ + drop(loadouts); + drop(inventories); + state.write_component( entity, comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped), @@ -246,11 +233,18 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }, comp::InventoryManip::Drop(slot) => { - let item = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); + let item = match slot { + Slot::Inventory(slot) => state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)), + Slot::Equip(slot) => state + .ecs() + .write_storage() + .get_mut(entity) + .and_then(|ldt| slot::loadout_remove(slot, ldt)), + }; if let (Some(item), Some(pos)) = (item, state.ecs().read_storage::().get(entity)) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index d7d54e61e3..09f8209ee8 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,9 +1,8 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slots::{ArmorSlot, InventorySlot, SlotManager}, - Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, - XP_COLOR, + slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager}, + Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ i18n::VoxygenLocalization, @@ -135,7 +134,6 @@ pub struct State { } pub enum Event { - HudEvent(HudEvent), Stats, Close, } @@ -350,7 +348,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Head, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Head), [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.0) .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -363,7 +361,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Neck, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Neck), [45.0; 2]) .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -377,7 +375,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Chest, [85.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Chest), [85.0; 2]) .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0) .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -388,7 +386,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Shoulders, [70.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Shoulders), [70.0; 2]) .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -401,7 +399,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Hands, [70.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Hands), [70.0; 2]) .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -414,7 +412,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Belt, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Belt), [45.0; 2]) .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0) .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -427,7 +425,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Legs, [85.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Legs), [85.0; 2]) .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0) .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -438,7 +436,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Lantern, [45.0; 2]) + .fabricate(EquipSlot::Lantern, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -451,7 +449,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Ring, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Ring), [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -464,7 +462,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Back, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Back), [45.0; 2]) .down_from(state.ids.lantern_slot, 10.0) .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -477,7 +475,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Feet, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Feet), [45.0; 2]) .down_from(state.ids.ring_slot, 10.0) .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -490,7 +488,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Tabard, [70.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Tabard), [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) .with_icon(self.imgs.tabard_bg, Vec2::new(60.0, 60.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -501,7 +499,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Mainhand, [85.0; 2]) + .fabricate(EquipSlot::Mainhand, [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)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -512,7 +510,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Offhand, [85.0; 2]) + .fabricate(EquipSlot::Offhand, [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)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -681,9 +679,6 @@ impl<'a> Widget for Bag<'a> { } } - // Drop selected item - // if ui.widget_input(ui.window).clicks().left().next().is_some() { - // Stats Button if Button::image(self.imgs.button) .w_h(92.0, 22.0) diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 647c91d3ac..3c76f58087 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -4,7 +4,7 @@ use common::{ comp::item::{ armor::Armor, tool::{Tool, ToolKind}, - Consumable, Ingredient, Item, ItemKind, Utility, + Consumable, Ingredient, Item, ItemKind, Lantern, Utility, }, figure::Segment, }; @@ -20,6 +20,7 @@ use vek::*; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ItemKey { Tool(ToolKind), + Lantern(Lantern), Armor(Armor), Utility(Utility), Consumable(Consumable), @@ -30,6 +31,7 @@ impl From<&Item> for ItemKey { fn from(item: &Item) -> Self { match &item.kind { ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(kind.clone()), + ItemKind::Lantern(kind) => ItemKey::Lantern(kind.clone()), ItemKind::Armor { kind, .. } => ItemKey::Armor(kind.clone()), ItemKind::Utility { kind, .. } => ItemKey::Utility(kind.clone()), ItemKind::Consumable { kind, .. } => ItemKey::Consumable(kind.clone()), diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 44eaea390f..93e67ff70f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -232,11 +232,9 @@ pub enum Event { ToggleDebug(bool), UiScale(ScaleChange), CharacterSelection, - UseInventorySlot(usize), - SwapInventorySlots(usize, usize), - SwapInventoryArmor(usize, slots::ArmorSlot), - SwapArmorSlots(slots::ArmorSlot, slots::ArmorSlot), - DropInventorySlot(usize), + UseSlot(comp::slot::Slot), + SwapSlots(comp::slot::Slot, comp::slot::Slot), + DropSlot(comp::slot::Slot), Logout, Quit, ChangeLanguage(LanguageMetadata), @@ -1660,7 +1658,6 @@ impl Hud { ) .set(self.ids.bag, ui_widgets) { - Some(bag::Event::HudEvent(event)) => events.push(event), Some(bag::Event::Stats) => self.show.stats = !self.show.stats, Some(bag::Event::Close) => { self.show.bag(false); @@ -1961,30 +1958,32 @@ impl Hud { // Maintain slot manager for event in self.slot_manager.maintain(ui_widgets) { + use comp::slot::Slot; use slots::SlotKind; + let to_slot = |slot_kind| match slot_kind { + SlotKind::Inventory(i) => Some(Slot::Inventory(i.0)), + SlotKind::Equip(e) => Some(Slot::Equip(e)), + //SlotKind::Hotbar(h) => None, + }; match event { - slot::Event::Dragged(SlotKind::Inventory(from), SlotKind::Inventory(to)) => { - // Swap between inventory slots - events.push(Event::SwapInventorySlots(from.0, to.0)); + slot::Event::Dragged(a, b) => { + // Swap between slots + if let (Some(a), Some(b)) = (to_slot(a), to_slot(b)) { + events.push(Event::SwapSlots(a, b)); + } }, - slot::Event::Dragged(SlotKind::Armor(from), SlotKind::Armor(to)) => { - // Swap between two armor slots - events.push(Event::SwapArmorSlots(from, to)); + slot::Event::Dropped(from) => { + // Drop item + if let Some(from) = to_slot(from) { + events.push(Event::DropSlot(from)); + } }, - slot::Event::Dragged(SlotKind::Inventory(inv), SlotKind::Armor(arm)) - | slot::Event::Dragged(SlotKind::Armor(arm), SlotKind::Inventory(inv)) => { - // Swap between inventory and armor slot - events.push(Event::SwapInventoryArmor(inv.0, arm)); + slot::Event::Used(from) => { + // Item used (selected and then clicked again) + if let Some(from) = to_slot(from) { + events.push(Event::UseSlot(from)); + } }, - slot::Event::Dropped(SlotKind::Inventory(from)) => { - // Drop item from inventory - events.push(Event::DropInventorySlot(from.0)); - }, - slot::Event::Used(SlotKind::Inventory(inv)) => { - // Item in inventory used (selected and then clicked again) - events.push(Event::UseInventorySlot(inv.0)); - }, - _ => {}, } } diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 398abca975..3e22307cbd 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -3,10 +3,12 @@ use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{item::ItemKind, Inventory, Loadout}; use conrod_core::image; +pub use common::comp::slot::{ArmorSlot, EquipSlot}; + #[derive(Clone, Copy, PartialEq)] pub enum SlotKind { Inventory(InventorySlot), - Armor(ArmorSlot), + Equip(EquipSlot), /*Hotbar(HotbarSlot), *Spellbook(SpellbookSlot), TODO */ } @@ -16,24 +18,6 @@ pub type SlotManager = slot::SlotManager; #[derive(Clone, Copy, PartialEq)] pub struct InventorySlot(pub usize); -#[derive(Clone, Copy, PartialEq)] -pub enum ArmorSlot { - Head, - Neck, - Shoulders, - Chest, - Hands, - Ring, - Lantern, - Back, - Belt, - Legs, - Feet, - Mainhand, - Offhand, - Tabard, -} - /*#[derive(Clone, Copy, PartialEq)] pub enum HotbarSlot { One, @@ -59,7 +43,7 @@ impl SlotKey for InventorySlot { source .get(self.0) .and_then(|item| match item.kind { - ItemKind::Tool { .. } | ItemKind::Armor { .. } => None, + ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, ItemKind::Utility { amount, .. } | ItemKind::Consumable { amount, .. } | ItemKind::Ingredient { amount, .. } => Some(amount), @@ -72,26 +56,25 @@ impl SlotKey for InventorySlot { } } -impl SlotKey for ArmorSlot { +impl SlotKey for EquipSlot { type ImageKey = ItemKey; fn image_key(&self, source: &Loadout) -> Option { let item = match self { - ArmorSlot::Shoulders => source.shoulder.as_ref(), - ArmorSlot::Chest => source.chest.as_ref(), - ArmorSlot::Belt => source.belt.as_ref(), - ArmorSlot::Hands => source.hand.as_ref(), - ArmorSlot::Legs => source.pants.as_ref(), - ArmorSlot::Feet => source.foot.as_ref(), - ArmorSlot::Back => source.back.as_ref(), - ArmorSlot::Ring => source.ring.as_ref(), - ArmorSlot::Neck => source.neck.as_ref(), - ArmorSlot::Head => source.head.as_ref(), - ArmorSlot::Lantern => source.lantern.as_ref(), - ArmorSlot::Tabard => source.tabard.as_ref(), - ArmorSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), - ArmorSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), - _ => None, + EquipSlot::Armor(ArmorSlot::Shoulders) => source.shoulder.as_ref(), + EquipSlot::Armor(ArmorSlot::Chest) => source.chest.as_ref(), + EquipSlot::Armor(ArmorSlot::Belt) => source.belt.as_ref(), + EquipSlot::Armor(ArmorSlot::Hands) => source.hand.as_ref(), + EquipSlot::Armor(ArmorSlot::Legs) => source.pants.as_ref(), + EquipSlot::Armor(ArmorSlot::Feet) => source.foot.as_ref(), + EquipSlot::Armor(ArmorSlot::Back) => source.back.as_ref(), + EquipSlot::Armor(ArmorSlot::Ring) => source.ring.as_ref(), + EquipSlot::Armor(ArmorSlot::Neck) => source.neck.as_ref(), + EquipSlot::Armor(ArmorSlot::Head) => source.head.as_ref(), + EquipSlot::Armor(ArmorSlot::Tabard) => source.tabard.as_ref(), + EquipSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), + EquipSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), + EquipSlot::Lantern => source.lantern.as_ref(), }; item.map(Into::into) @@ -132,8 +115,8 @@ impl From for SlotKind { fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } } -impl From for SlotKind { - fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) } +impl From for SlotKind { + fn from(equip: EquipSlot) -> Self { Self::Equip(equip) } } //impl From for SlotKind { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 7bfac88f5e..6c3d24a37a 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -12,9 +12,9 @@ use common::{ dragon, fish_medium, fish_small, humanoid::{Body, BodyType, EyeColor, Eyebrows, Race, Skin}, item::{ - armor::{Armor, Back, Belt, Chest, Foot, Hand, Head, Lantern, Pants, Shoulder, Tabard}, + armor::{Armor, Back, Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Tabard}, tool::{Tool, ToolKind}, - ItemKind, + ItemKind, Lantern, }, object, quadruped_medium::{BodyType as QMBodyType, Species as QMSpecies}, @@ -701,21 +701,18 @@ impl HumArmorLanternSpec { } pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Lantern(lantern), - .. - }) = loadout.lantern.as_ref().map(|i| &i.kind) - { - match self.0.map.get(&lantern) { - Some(spec) => spec, - None => { - error!("No lantern specification exists for {:?}", lantern); - return load_mesh("not_found", Vec3::new(-4.0, -3.5, 2.0)); - }, - } - } else { - &self.0.default - }; + let spec = + if let Some(ItemKind::Lantern(lantern)) = loadout.lantern.as_ref().map(|i| &i.kind) { + match self.0.map.get(&lantern) { + Some(spec) => spec, + None => { + error!("No lantern specification exists for {:?}", lantern); + return load_mesh("not_found", Vec3::new(-4.0, -3.5, 2.0)); + }, + } + } else { + &self.0.default + }; let lantern_segment = color_segment( graceful_load_mat_segment(&spec.vox_spec.0), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 380a108603..80b121c897 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -643,22 +643,9 @@ impl PlayState for SessionState { global_state.settings.graphics.max_fps = fps; global_state.settings.save_to_file_warn(); }, - HudEvent::UseInventorySlot(x) => self.client.borrow_mut().use_inventory_slot(x), - HudEvent::SwapInventorySlots(a, b) => { - self.client.borrow_mut().swap_inventory_slots(a, b) - }, - HudEvent::SwapInventoryArmor(inv_slot, armor_slot) => { - // Swapping between inventory and armor slot - // TODO: don't do this - self.client.borrow_mut().use_inventory_slot(inv_slot) - }, - HudEvent::SwapArmorSlots(from, to) => { - // Only works with rings currently - // TODO: implement - }, - HudEvent::DropInventorySlot(x) => { - self.client.borrow_mut().drop_inventory_slot(x) - }, + HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x), + HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b), + HudEvent::DropSlot(x) => self.client.borrow_mut().drop_slot(x), HudEvent::ChangeFOV(new_fov) => { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 59576ce5b3..0da5ceae01 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -153,9 +153,9 @@ where } } - // If dragging and mouse if released check if there is a slot widget under the + // If dragging and mouse is released check if there is a slot widget under the // mouse - if let ManagerState::Dragging(id, slot, content_img) = &self.state { + if let ManagerState::Dragging(_, slot, content_img) = &self.state { let content_img = *content_img; let input = &ui.global_input().current; if let mouse::ButtonPosition::Up = input.mouse.buttons.left() {