diff --git a/Cargo.lock b/Cargo.lock index df37680f44..7275021bc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2685,6 +2685,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.0", "rayon", "serde", ] @@ -6651,7 +6660,7 @@ dependencies = [ "authc", "byteorder", "clap 3.2.22", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "image", "num 0.4.0", "quinn", @@ -6686,7 +6695,7 @@ dependencies = [ "fluent", "fluent-bundle", "fluent-syntax", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "intl-memoizer", "ron 0.8.0", "serde", @@ -6713,7 +6722,7 @@ dependencies = [ "dot_vox", "enum-map", "fxhash", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "indexmap", "itertools", "kiddo 0.1.7", @@ -6808,7 +6817,7 @@ version = "0.10.0" dependencies = [ "bincode", "flate2", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "image", "num-traits", "serde", @@ -6824,7 +6833,7 @@ name = "veloren-common-state" version = "0.10.0" dependencies = [ "bincode", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "num_cpus", "rayon", "scopeguard", @@ -6847,7 +6856,7 @@ dependencies = [ name = "veloren-common-systems" version = "0.10.0" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.13.2", "indexmap", "itertools", "ordered-float 3.1.0", @@ -6878,7 +6887,7 @@ dependencies = [ "crossbeam-channel", "futures-core", "futures-util", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "lazy_static", "lz-fear", "prometheus", @@ -6906,7 +6915,7 @@ dependencies = [ "bitflags", "bytes", "criterion", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "prometheus", "rand 0.8.5", "tokio", @@ -6949,7 +6958,7 @@ dependencies = [ "atomic_refcell", "enum-map", "fxhash", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "itertools", "rand 0.8.5", "rand_chacha 0.3.1", @@ -6979,7 +6988,7 @@ dependencies = [ "enum-map", "enumset", "futures-util", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "humantime", "itertools", "lazy_static", @@ -7095,7 +7104,7 @@ dependencies = [ "gilrs", "glyph_brush", "guillotiere", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "iced_native", "iced_winit", "image", @@ -7195,7 +7204,7 @@ dependencies = [ "fallible-iterator", "flate2", "fxhash", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "image", "inline_tweak", "itertools", diff --git a/client/Cargo.toml b/client/Cargo.toml index 7a7cbb992c..2c06f7c623 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -30,7 +30,7 @@ tracing = { version = "0.1", default-features = false } rayon = "1.5" specs = { version = "0.18", features = ["serde", "storage-event-control", "derive"] } vek = { version = "0.15.8", features = ["serde"] } -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253f8f2f92760ef44d2679c9a" } #TODO: put bot in a different crate diff --git a/client/i18n/Cargo.toml b/client/i18n/Cargo.toml index 593fcd7adb..1c074c0cb4 100644 --- a/client/i18n/Cargo.toml +++ b/client/i18n/Cargo.toml @@ -17,7 +17,7 @@ intl-memoizer = { git = "https://github.com/juliancoffee/fluent-rs.git", branch fluent = { git = "https://github.com/juliancoffee/fluent-rs.git", branch = "patched"} fluent-bundle = { git = "https://github.com/juliancoffee/fluent-rs.git", branch = "patched"} # Utility -hashbrown = { version = "0.12", features = ["serde", "nightly"] } +hashbrown = { version = "0.13", features = ["serde", "nightly"] } deunicode = "1.0" tracing = "0.1" # Bin diff --git a/common/Cargo.toml b/common/Cargo.toml index 8aff837d6b..e3ddc7d43b 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -74,7 +74,7 @@ petgraph = { version = "0.6", optional = true } kiddo = { version = "0.1", optional = true } # Data structures -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } slotmap = { version = "1.0", features = ["serde"] } indexmap = { version = "1.3.0", features = ["rayon"] } slab = "0.4.2" diff --git a/common/net/Cargo.toml b/common/net/Cargo.toml index 4b23bf1eaa..0d9f9a28e4 100644 --- a/common/net/Cargo.toml +++ b/common/net/Cargo.toml @@ -22,7 +22,7 @@ vek = { version = "0.15.8", features = ["serde"] } tracing = { version = "0.1", default-features = false } # Data structures -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } # ECS specs = { version = "0.18", features = ["serde", "storage-event-control"] } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 4432d17d9f..e3299a46ac 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -20,6 +20,7 @@ use core::{ num::{NonZeroU32, NonZeroU64}, }; use crossbeam_utils::atomic::AtomicCell; +use hashbrown::Equivalent; use serde::{de, Deserialize, Serialize, Serializer}; use specs::{Component, DenseVecStorage, DerefFlaggedStorage}; use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc}; @@ -1213,7 +1214,7 @@ impl Item { } } - pub fn durability(&self) -> Option { + pub fn durability_lost(&self) -> Option { self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY)) } @@ -1294,7 +1295,7 @@ pub trait ItemDesc { fn is_modular(&self) -> bool; fn components(&self) -> &[Item]; fn has_durability(&self) -> bool; - fn durability(&self) -> Option; + fn durability_lost(&self) -> Option; fn stats_durability_multiplier(&self) -> DurabilityMultiplier; fn tool_info(&self) -> Option { @@ -1327,7 +1328,7 @@ impl ItemDesc for Item { fn has_durability(&self) -> bool { self.has_durability() } - fn durability(&self) -> Option { self.durability() } + fn durability_lost(&self) -> Option { self.durability_lost() } fn stats_durability_multiplier(&self) -> DurabilityMultiplier { self.stats_durability_multiplier() @@ -1359,7 +1360,7 @@ impl ItemDesc for ItemDef { self.kind().has_durability() && self.quality != Quality::Debug } - fn durability(&self) -> Option { None } + fn durability_lost(&self) -> Option { None } fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) } } @@ -1399,7 +1400,7 @@ impl<'a, T: ItemDesc + ?Sized> ItemDesc for &'a T { fn has_durability(&self) -> bool { (*self).has_durability() } - fn durability(&self) -> Option { (*self).durability() } + fn durability_lost(&self) -> Option { (*self).durability_lost() } fn stats_durability_multiplier(&self) -> DurabilityMultiplier { (*self).stats_durability_multiplier() @@ -1451,6 +1452,10 @@ impl PartialEq for ItemDefinitionId<'_> { fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self } } +impl Equivalent for ItemDefinitionId<'_> { + fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key } +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/src/comp/inventory/loadout.rs b/common/src/comp/inventory/loadout.rs index 3c2f01576a..df7a1d0c7b 100644 --- a/common/src/comp/inventory/loadout.rs +++ b/common/src/comp/inventory/loadout.rs @@ -1,18 +1,29 @@ -use crate::comp::{ - inventory::{ - item::{self, tool::Tool, Hands, ItemKind}, - slot::{ArmorSlot, EquipSlot}, - InvSlot, +use crate::{ + comp::{ + inventory::{ + item::{self, tool::Tool, Hands, ItemDefinitionIdOwned, ItemKind}, + slot::{ArmorSlot, EquipSlot}, + InvSlot, + }, + Item, }, - Item, + resources::Time, }; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::ops::Range; use tracing::warn; +pub(super) const UNEQUIP_TRACKING_DURATION: f64 = 60.0; + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Loadout { slots: Vec, + // Includes time that item was unequipped at + #[serde(skip)] + // Tracks time unequipped at and number that have been unequipped (for things like dual + // wielding, rings, or other future cases) + pub(super) recently_unequipped_items: HashMap, } /// NOTE: Please don't derive a PartialEq Instance for this; that's broken! @@ -83,16 +94,40 @@ impl Loadout { .into_iter() .map(|(equip_slot, persistence_key)| LoadoutSlot::new(equip_slot, persistence_key)) .collect(), + recently_unequipped_items: HashMap::new(), } } /// Replaces the item in the Loadout slot that corresponds to the given /// EquipSlot and returns the previous item if any - pub(super) fn swap(&mut self, equip_slot: EquipSlot, item: Option) -> Option { - self.slots + pub(super) fn swap( + &mut self, + equip_slot: EquipSlot, + item: Option, + time: Time, + ) -> Option { + if let Some(item_def_id) = item.as_ref().map(|item| item.item_definition_id()) { + if let Some((_unequip_time, count)) = + self.recently_unequipped_items.get_mut(&item_def_id) + { + *count = count.saturating_sub(1); + } + } + self.cull_recently_unequipped_items(time); + let unequipped_item = self + .slots .iter_mut() .find(|x| x.equip_slot == equip_slot) - .and_then(|x| core::mem::replace(&mut x.slot, item)) + .and_then(|x| core::mem::replace(&mut x.slot, item)); + if let Some(unequipped_item) = unequipped_item.as_ref() { + // TODO: Avoid this allocation when there isn't an insert + let entry = self + .recently_unequipped_items + .entry(unequipped_item.item_definition_id().to_owned()) + .or_insert((time, 0)); + *entry = (time, entry.1.saturating_add(1)); + } + unequipped_item } /// Returns a reference to the item (if any) equipped in the given EquipSlot @@ -154,7 +189,12 @@ impl Loadout { } /// Swaps the contents of two loadout slots - pub(super) fn swap_slots(&mut self, equip_slot_a: EquipSlot, equip_slot_b: EquipSlot) { + pub(super) fn swap_slots( + &mut self, + equip_slot_a: EquipSlot, + equip_slot_b: EquipSlot, + time: Time, + ) { if self.slot(equip_slot_b).is_none() || self.slot(equip_slot_b).is_none() { // Currently all loadouts contain slots for all EquipSlots so this can never // happen, but if loadouts with alternate slot combinations are @@ -163,9 +203,9 @@ impl Loadout { return; } - 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); + let item_a = self.swap(equip_slot_a, None, time); + let item_b = self.swap(equip_slot_b, item_a, time); + assert_eq!(self.swap(equip_slot_a, item_b, time), None); // Check if items are valid in their new positions if !self.slot_can_hold( @@ -176,9 +216,9 @@ impl Loadout { self.equipped(equip_slot_b).map(|x| x.kind()).as_deref(), ) { // 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); + let item_a = self.swap(equip_slot_a, None, time); + let item_b = self.swap(equip_slot_b, item_a, time); + assert_eq!(self.swap(equip_slot_a, item_b, time), None); } } @@ -223,6 +263,22 @@ impl Loadout { .and_then(|item| item.slot(loadout_slot_id.slot_idx)) } + pub(super) fn inv_slot_with_mutable_recently_unequipped_items( + &mut self, + loadout_slot_id: LoadoutSlotId, + ) -> ( + Option<&InvSlot>, + &mut HashMap, + ) { + ( + self.slots + .get(loadout_slot_id.loadout_idx) + .and_then(|loadout_slot| loadout_slot.slot.as_ref()) + .and_then(|item| item.slot(loadout_slot_id.slot_idx)), + &mut self.recently_unequipped_items, + ) + } + /// Returns the `InvSlot` for a given `LoadoutSlotId` pub(super) fn inv_slot_mut(&mut self, loadout_slot_id: LoadoutSlotId) -> Option<&mut InvSlot> { self.slots @@ -367,7 +423,7 @@ impl Loadout { (None, None)) } - pub(super) fn swap_equipped_weapons(&mut self) { + pub(super) fn swap_equipped_weapons(&mut self, time: Time) { // 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| { @@ -385,25 +441,25 @@ impl Loadout { && 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); + let active_mainhand = self.swap(EquipSlot::ActiveMainhand, None, time); + let active_offhand = self.swap(EquipSlot::ActiveOffhand, None, time); + let inactive_mainhand = self.swap(EquipSlot::InactiveMainhand, None, time); + let inactive_offhand = self.swap(EquipSlot::InactiveOffhand, None, time); // Equip weapons into new slots assert!( - self.swap(EquipSlot::ActiveMainhand, inactive_mainhand) + self.swap(EquipSlot::ActiveMainhand, inactive_mainhand, time) .is_none() ); assert!( - self.swap(EquipSlot::ActiveOffhand, inactive_offhand) + self.swap(EquipSlot::ActiveOffhand, inactive_offhand, time) .is_none() ); assert!( - self.swap(EquipSlot::InactiveMainhand, active_mainhand) + self.swap(EquipSlot::InactiveMainhand, active_mainhand, time) .is_none() ); assert!( - self.swap(EquipSlot::InactiveOffhand, active_offhand) + self.swap(EquipSlot::InactiveOffhand, active_offhand, time) .is_none() ); } @@ -452,21 +508,38 @@ impl Loadout { item.reset_durability(ability_map, msm); } } + + pub(super) fn cull_recently_unequipped_items(&mut self, time: Time) { + for (unequip_time, _count) in self.recently_unequipped_items.values_mut() { + // If somehow time went backwards or faulty unequip time supplied, set unequip + // time to minimum of current time and unequip time + if time.0 < unequip_time.0 { + *unequip_time = time; + } + } + self.recently_unequipped_items + .retain(|_def, (unequip_time, count)| { + (time.0 - unequip_time.0 < UNEQUIP_TRACKING_DURATION) && *count > 0 + }); + } } #[cfg(test)] mod tests { - use crate::comp::{ - inventory::{ - item::{ - armor::{Armor, ArmorKind, Protection}, - ItemKind, + use crate::{ + comp::{ + inventory::{ + item::{ + armor::{Armor, ArmorKind, Protection}, + ItemKind, + }, + loadout::Loadout, + slot::{ArmorSlot, EquipSlot}, + test_helpers::get_test_bag, }, - loadout::Loadout, - slot::{ArmorSlot, EquipSlot}, - test_helpers::get_test_bag, + Item, }, - Item, + resources::Time, }; #[test] @@ -475,7 +548,7 @@ mod tests { let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1); let bag = get_test_bag(18); - loadout.swap(bag1_slot, Some(bag)); + loadout.swap(bag1_slot, Some(bag), Time(0.0)); let result = loadout.slot_range_for_equip_slot(bag1_slot).unwrap(); @@ -496,7 +569,7 @@ mod tests { let feet_slot = EquipSlot::Armor(ArmorSlot::Feet); let boots = Item::new_from_asset_expect("common.items.testing.test_boots"); - loadout.swap(feet_slot, Some(boots)); + loadout.swap(feet_slot, Some(boots), Time(0.0)); let result = loadout.slot_range_for_equip_slot(feet_slot); assert_eq!(None, result); @@ -506,7 +579,11 @@ mod tests { fn test_get_slot_to_equip_into_second_bag_slot_free() { let mut loadout = Loadout::new_empty(); - loadout.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(1))); + loadout.swap( + EquipSlot::Armor(ArmorSlot::Bag1), + Some(get_test_bag(1)), + Time(0.0), + ); let result = loadout .get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor( @@ -523,10 +600,26 @@ mod tests { fn test_get_slot_to_equip_into_no_bag_slots_free() { let mut loadout = Loadout::new_empty(); - loadout.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(1))); - loadout.swap(EquipSlot::Armor(ArmorSlot::Bag2), Some(get_test_bag(1))); - loadout.swap(EquipSlot::Armor(ArmorSlot::Bag3), Some(get_test_bag(1))); - loadout.swap(EquipSlot::Armor(ArmorSlot::Bag4), Some(get_test_bag(1))); + loadout.swap( + EquipSlot::Armor(ArmorSlot::Bag1), + Some(get_test_bag(1)), + Time(0.0), + ); + loadout.swap( + EquipSlot::Armor(ArmorSlot::Bag2), + Some(get_test_bag(1)), + Time(0.0), + ); + loadout.swap( + EquipSlot::Armor(ArmorSlot::Bag3), + Some(get_test_bag(1)), + Time(0.0), + ); + loadout.swap( + EquipSlot::Armor(ArmorSlot::Bag4), + Some(get_test_bag(1)), + Time(0.0), + ); let result = loadout .get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor( diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 4fce8c76fc..42af30bc91 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -9,6 +9,7 @@ use crate::{ item::{self, Item}, object, quadruped_low, quadruped_medium, theropod, Body, }, + resources::Time, trade::SiteInformation, }; use rand::{self, distributions::WeightedError, seq::SliceRandom, Rng}; @@ -1148,7 +1149,11 @@ impl LoadoutBuilder { .map_or(true, |item| equip_slot.can_hold(&item.kind())) ); - self.0.swap(equip_slot, item); + // Used when creating a loadout, so time not needed as it is used to check when + // stuff gets unequipped. A new loadout has never unequipped an item. + let time = Time(0.0); + + self.0.swap(equip_slot, item, time); self } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 51d1db3ead..1bbc2a494b 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,4 +1,5 @@ use core::ops::Not; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use std::{cmp::Ordering, convert::TryFrom, mem, ops::Range}; @@ -9,7 +10,10 @@ use crate::{ comp::{ body::Body, inventory::{ - item::{tool::AbilityMap, ItemDef, ItemKind, MaterialStatManifest, TagExampleInfo}, + item::{ + tool::AbilityMap, ItemDef, ItemDefinitionIdOwned, ItemKind, MaterialStatManifest, + TagExampleInfo, + }, loadout::Loadout, slot::{EquipSlot, Slot, SlotError}, }, @@ -17,6 +21,7 @@ use crate::{ slot::{InvSlotId, SlotId}, Item, }, + resources::Time, uid::Uid, LoadoutBuilder, }; @@ -574,6 +579,24 @@ impl Inventory { } } + fn slot_with_mutable_recently_unequipped_items( + &mut self, + inv_slot_id: InvSlotId, + ) -> ( + Option<&InvSlot>, + &mut HashMap, + ) { + match SlotId::from(inv_slot_id) { + SlotId::Inventory(slot_idx) => ( + self.slots.get(slot_idx), + &mut self.loadout.recently_unequipped_items, + ), + SlotId::Loadout(loadout_slot_id) => self + .loadout + .inv_slot_with_mutable_recently_unequipped_items(loadout_slot_id), + } + } + pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> { match SlotId::from(inv_slot_id) { SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx), @@ -615,18 +638,19 @@ impl Inventory { &mut self, equip_slot: EquipSlot, replacement_item: Option, + time: Time, ) -> Option { - self.loadout.swap(equip_slot, replacement_item) + self.loadout.swap(equip_slot, replacement_item, time) } /// Equip an item from a slot in inventory. The currently equipped item will /// go into inventory. If the item is going to mainhand, put mainhand in /// offhand and place offhand into inventory. #[must_use = "Returned items will be lost if not used"] - pub fn equip(&mut self, inv_slot: InvSlotId) -> Vec { + pub fn equip(&mut self, inv_slot: InvSlotId, time: Time) -> Vec { self.get(inv_slot) .and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind())) - .map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot)) + .map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot, time)) .unwrap_or_else(Vec::new) } @@ -680,7 +704,11 @@ impl Inventory { /// equipped if inventory has no slots available. #[must_use = "Returned items will be lost if not used"] #[allow(clippy::needless_collect)] // This is a false positive, the collect is needed - pub fn unequip(&mut self, equip_slot: EquipSlot) -> Result>, SlotError> { + pub fn unequip( + &mut self, + equip_slot: EquipSlot, + time: Time, + ) -> Result>, SlotError> { // Ensure there is enough space in the inventory to place the unequipped item if self.free_slots_minus_equipped_item(equip_slot) == 0 { return Err(SlotError::InventoryFull); @@ -688,7 +716,7 @@ impl Inventory { Ok(self .loadout - .swap(equip_slot, None) + .swap(equip_slot, None, time) .and_then(|mut unequipped_item| { let unloaded_items: Vec = unequipped_item.drain().collect(); self.push(unequipped_item) @@ -721,7 +749,7 @@ impl Inventory { /// Swaps items from two slots, regardless of if either is inventory or /// loadout. #[must_use = "Returned items will be lost if not used"] - pub fn swap(&mut self, slot_a: Slot, slot_b: Slot) -> Vec { + pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec { match (slot_a, slot_b) { (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => { self.swap_slots(slot_a, slot_b); @@ -729,10 +757,10 @@ impl Inventory { }, (Slot::Inventory(inv_slot), Slot::Equip(equip_slot)) | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => { - self.swap_inventory_loadout(inv_slot, equip_slot) + self.swap_inventory_loadout(inv_slot, equip_slot, time) }, (Slot::Equip(slot_a), Slot::Equip(slot_b)) => { - self.loadout.swap_slots(slot_a, slot_b); + self.loadout.swap_slots(slot_a, slot_b, time); Vec::new() }, } @@ -768,6 +796,7 @@ impl Inventory { &mut self, inv_slot_id: InvSlotId, equip_slot: EquipSlot, + time: Time, ) -> Vec { if !self.can_swap(inv_slot_id, equip_slot) { return Vec::new(); @@ -777,7 +806,7 @@ impl Inventory { let from_inv = self.remove(inv_slot_id); // Swap the equipped item for the item from the inventory - let from_equip = self.loadout.swap(equip_slot, from_inv); + let from_equip = self.loadout.swap(equip_slot, from_inv, time); let unloaded_items = from_equip .map(|mut from_equip| { @@ -803,10 +832,10 @@ impl Inventory { if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none() && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some() { - let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None); + let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time); assert!( self.loadout - .swap(EquipSlot::ActiveMainhand, offhand) + .swap(EquipSlot::ActiveMainhand, offhand, time) .is_none() ); } @@ -815,10 +844,10 @@ impl Inventory { if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none() && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some() { - let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None); + let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time); assert!( self.loadout - .swap(EquipSlot::InactiveMainhand, offhand) + .swap(EquipSlot::InactiveMainhand, offhand, time) .is_none() ); } @@ -867,7 +896,7 @@ impl Inventory { self.loadout.equipped_items_replaceable_by(item_kind) } - pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() } + pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) } /// Update internal computed state of all top level items in this loadout. /// Used only when loading in persistence code. @@ -883,13 +912,56 @@ impl Inventory { }); } - /// Increments durability of all valid items equipped in loaodut by 1 + /// Increments durability of all valid items equipped in loaodut and + /// recently unequipped from loadout by 1 pub fn damage_items( &mut self, ability_map: &item::tool::AbilityMap, msm: &item::MaterialStatManifest, + time: Time, ) { - self.loadout.damage_items(ability_map, msm) + self.loadout.damage_items(ability_map, msm); + self.loadout.cull_recently_unequipped_items(time); + + let slots = self + .slots_with_id() + .filter(|(_slot, item)| { + item.as_ref().map_or(false, |item| { + item.durability_lost() + .map_or(false, |dur| dur < Item::MAX_DURABILITY) + && self + .loadout + .recently_unequipped_items + .contains_key(&item.item_definition_id()) + }) + }) + .map(|(slot, _item)| slot) + .collect::>(); + slots.into_iter().for_each(|slot| { + let slot = if let (Some(Some(item)), recently_unequipped_items) = + self.slot_with_mutable_recently_unequipped_items(slot) + { + if let Some((_unequip_time, count)) = + recently_unequipped_items.get_mut(&item.item_definition_id()) + { + if *count > 0 { + *count -= 1; + Some(slot) + } else { + None + } + } else { + None + } + } else { + None + }; + if let Some(slot) = slot { + if let Some(Some(item)) = self.slot_mut(slot) { + item.increment_damage(ability_map, msm); + } + } + }); } /// Resets durability of item in specified slot diff --git a/common/src/comp/inventory/test.rs b/common/src/comp/inventory/test.rs index c7d7e65b1e..076ccc56af 100644 --- a/common/src/comp/inventory/test.rs +++ b/common/src/comp/inventory/test.rs @@ -129,7 +129,7 @@ fn free_slots_minus_equipped_item_items_only_present_in_equipped_bag_slots() { let bag = get_test_bag(18); let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1); inv.loadout - .swap(bag1_slot, Some(bag.duplicate(ability_map, msm))); + .swap(bag1_slot, Some(bag.duplicate(ability_map, msm)), Time(0.0)); assert!(inv.insert_at(InvSlotId::new(15, 0), bag).unwrap().is_none()); @@ -149,10 +149,11 @@ fn free_slots_minus_equipped_item() { let bag = get_test_bag(18); let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1); inv.loadout - .swap(bag1_slot, Some(bag.duplicate(ability_map, msm))); + .swap(bag1_slot, Some(bag.duplicate(ability_map, msm)), Time(0.0)); inv.loadout.swap( EquipSlot::Armor(ArmorSlot::Bag2), Some(bag.duplicate(ability_map, msm)), + Time(0.0), ); assert!(inv.insert_at(InvSlotId::new(16, 0), bag).unwrap().is_none()); @@ -169,7 +170,7 @@ fn get_slot_range_for_equip_slot() { let mut inv = Inventory::with_empty(); let bag = get_test_bag(18); let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1); - inv.loadout.swap(bag1_slot, Some(bag)); + inv.loadout.swap(bag1_slot, Some(bag), Time(0.0)); let result = inv.get_slot_range_for_equip_slot(bag1_slot).unwrap(); @@ -198,7 +199,11 @@ fn can_swap_equipped_bag_into_empty_inv_slot( ) { let mut inv = Inventory::with_empty(); - inv.replace_loadout_item(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(18))); + inv.replace_loadout_item( + EquipSlot::Armor(ArmorSlot::Bag1), + Some(get_test_bag(18)), + Time(0.0), + ); fill_inv_slots(&mut inv, 18 - free_slots); @@ -211,7 +216,11 @@ fn can_swap_equipped_bag_into_empty_inv_slot( fn can_swap_equipped_bag_into_only_empty_slot_provided_by_itself_should_return_true() { let mut inv = Inventory::with_empty(); - inv.replace_loadout_item(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(18))); + inv.replace_loadout_item( + EquipSlot::Armor(ArmorSlot::Bag1), + Some(get_test_bag(18)), + Time(0.0), + ); fill_inv_slots(&mut inv, 35); @@ -231,22 +240,26 @@ fn unequip_items_both_hands() { inv.replace_loadout_item( EquipSlot::ActiveMainhand, Some(sword.duplicate(ability_map, msm)), + Time(0.0), ); inv.replace_loadout_item( EquipSlot::InactiveMainhand, Some(sword.duplicate(ability_map, msm)), + Time(0.0), ); // Fill all inventory slots except one fill_inv_slots(&mut inv, 17); - let result = inv.unequip(EquipSlot::ActiveMainhand); + let result = inv.unequip(EquipSlot::ActiveMainhand, Time(0.0)); // We have space in the inventory, so this should have unequipped assert_eq!(None, inv.equipped(EquipSlot::ActiveMainhand)); assert_eq!(18, inv.populated_slots()); assert!(result.is_ok()); - let result = inv.unequip(EquipSlot::InactiveMainhand).unwrap_err(); + let result = inv + .unequip(EquipSlot::InactiveMainhand, Time(0.0)) + .unwrap_err(); assert_eq!(SlotError::InventoryFull, result); // There is no more space in the inventory, so this should still be equipped @@ -274,9 +287,10 @@ fn equip_replace_already_equipped_item() { starting_sandles .as_ref() .map(|item| item.duplicate(ability_map, msm)), + Time(0.0), ); - let _ = inv.equip(InvSlotId::new(0, 0)); + let _ = inv.equip(InvSlotId::new(0, 0), Time(0.0)); // We should now have the testing boots equipped assert_eq!( @@ -302,7 +316,11 @@ fn equip_equipping_smaller_bag_from_last_slot_of_big_bag() { assert!( inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(large_bag)) + .swap( + EquipSlot::Armor(ArmorSlot::Bag1), + Some(large_bag), + Time(0.0) + ) .is_none() ); @@ -311,6 +329,7 @@ fn equip_equipping_smaller_bag_from_last_slot_of_big_bag() { let result = inv.swap( Slot::Equip(EquipSlot::Armor(ArmorSlot::Bag1)), Slot::Inventory(InvSlotId::new(15, 15)), + Time(0.0), ); assert_eq!( @@ -327,15 +346,18 @@ fn unequip_unequipping_bag_into_its_own_slot_with_no_other_free_slots_returns_on assert!( inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag)) + .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag), Time(0.0)) .is_none() ); // Fill all inventory built-in slots fill_inv_slots(&mut inv, 18); - let result = - inv.swap_inventory_loadout(InvSlotId::new(15, 0), EquipSlot::Armor(ArmorSlot::Bag1)); + let result = inv.swap_inventory_loadout( + InvSlotId::new(15, 0), + EquipSlot::Armor(ArmorSlot::Bag1), + Time(0.0), + ); assert_eq!(result.len(), 1); // Because the slot the bag was swapped with no longer exists as it was provided @@ -358,13 +380,14 @@ fn equip_one_bag_equipped_equip_second_bag() { .swap( EquipSlot::Armor(ArmorSlot::Bag1), Some(bag.duplicate(ability_map, msm)), + Time(0.0) ) .is_none() ); inv.push(bag).unwrap(); - let _ = inv.equip(InvSlotId::new(0, 0)); + let _ = inv.equip(InvSlotId::new(0, 0), Time(0.0)); assert!(inv.equipped(EquipSlot::Armor(ArmorSlot::Bag2)).is_some()); } @@ -376,7 +399,7 @@ fn free_after_swap_equipped_item_has_more_slots() { let bag = get_test_bag(18); assert!( inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag)) + .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag), Time(0.0)) .is_none() ); @@ -399,7 +422,7 @@ fn free_after_swap_equipped_item_has_less_slots() { let bag = get_test_bag(9); assert!( inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag)) + .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag), Time(0.0)) .is_none() ); @@ -422,7 +445,7 @@ fn free_after_swap_equipped_item_with_slots_swapped_with_empty_inv_slot() { let bag = get_test_bag(9); assert!( inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag)) + .swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag), Time(0.0)) .is_none() ); @@ -475,15 +498,18 @@ fn backpack_crash() { let backpack = Item::new_from_asset_expect("common.items.armor.misc.back.backpack"); inv.loadout - .swap(EquipSlot::Armor(ArmorSlot::Back), Some(backpack)); + .swap(EquipSlot::Armor(ArmorSlot::Back), Some(backpack), Time(0.0)); fill_inv_slots(&mut inv, 35); let cape = Item::new_from_asset_expect("common.items.armor.misc.back.admin"); assert!(inv.push(cape).is_ok()); - let returned_items = - inv.swap_inventory_loadout(InvSlotId::new(9, 17), EquipSlot::Armor(ArmorSlot::Back)); + let returned_items = inv.swap_inventory_loadout( + InvSlotId::new(9, 17), + EquipSlot::Armor(ArmorSlot::Back), + Time(0.0), + ); assert_eq!(18, returned_items.len()); assert_eq!( ItemDefinitionId::Simple("common.items.armor.misc.back.backpack"), diff --git a/common/src/recipe.rs b/common/src/recipe.rs index b14abb1a7c..0db1ca695e 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -976,7 +976,7 @@ impl RepairRecipe { } pub fn inputs(&self, item: &Item) -> impl Iterator { - let item_durability = item.durability().unwrap_or(0); + let item_durability = item.durability_lost().unwrap_or(0); self.inputs .iter() .filter_map(move |(input, original_amount)| { diff --git a/common/state/Cargo.toml b/common/state/Cargo.toml index 9ad729f934..a74ac21d53 100644 --- a/common/state/Cargo.toml +++ b/common/state/Cargo.toml @@ -22,7 +22,7 @@ tracing = { version = "0.1", default-features = false } vek = { version = "0.15.8", features = ["serde"] } # Data structures -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } # ECS specs = { version = "0.18", features = ["serde", "storage-event-control", "derive"] } diff --git a/common/systems/Cargo.toml b/common/systems/Cargo.toml index e9d91ca88e..7b094ccc1b 100644 --- a/common/systems/Cargo.toml +++ b/common/systems/Cargo.toml @@ -23,7 +23,7 @@ ordered-float = { version = "3", default-features = false } itertools = "0.10" # Data structures -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } indexmap = "1.3.0" slab = "0.4.2" diff --git a/network/Cargo.toml b/network/Cargo.toml index 4007971631..5e892e85c3 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -46,7 +46,7 @@ lz-fear = { version = "0.1.1", optional = true } async-trait = "0.1.42" bytes = "^1" # faster HashMaps -hashbrown = { version = ">=0.9, <0.13" } +hashbrown = { version = "0.13" } [dev-dependencies] tracing-subscriber = { version = "0.3.7", default-features = false, features = ["env-filter", "fmt", "time", "ansi", "smallvec"] } diff --git a/network/protocol/Cargo.toml b/network/protocol/Cargo.toml index ebc9b87e5e..0eed87d0f1 100644 --- a/network/protocol/Cargo.toml +++ b/network/protocol/Cargo.toml @@ -24,7 +24,7 @@ rand = { version = "0.8" } # async traits async-trait = "0.1.42" bytes = "^1" -hashbrown = { version = ">=0.12, <0.13" } +hashbrown = { version = "0.13" } [dev-dependencies] async-channel = "1.5.1" diff --git a/rtsim/Cargo.toml b/rtsim/Cargo.toml index 639680d709..3aafaf3624 100644 --- a/rtsim/Cargo.toml +++ b/rtsim/Cargo.toml @@ -8,7 +8,7 @@ common = { package = "veloren-common", path = "../common" } world = { package = "veloren-world", path = "../world" } ron = "0.8" serde = { version = "1.0.110", features = ["derive"] } -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } enum-map = { version = "2.4", features = ["serde"] } vek = { version = "0.15.8", features = ["serde"] } rmp-serde = "1.1.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 54347b0473..cac886931f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -54,7 +54,7 @@ ron = { version = "0.8", default-features = false } serde = { version = "1.0.110", features = ["derive"] } serde_json = "1.0.50" rand = { version = "0.8", features = ["small_rng"] } -hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } +hashbrown = { version = "0.13", features = ["rayon", "serde", "nightly"] } parking_lot = { version = "0.12" } rayon = "1.5" crossbeam-channel = "0.5" diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 9e39fa1198..ca06333997 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -28,6 +28,7 @@ use common::{ resources::{Secs, Time}, states::utils::StageSection, terrain::{Block, BlockKind, TerrainGrid}, + trade::{TradeResult, Trades}, uid::{Uid, UidAllocator}, util::Dir, vol::ReadVol, @@ -508,9 +509,11 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt // Modify durability on all equipped items if let Some(mut inventory) = state.ecs().write_storage::().get_mut(entity) { - let ability_map = state.ecs().read_resource::(); - let msm = state.ecs().read_resource::(); - inventory.damage_items(&ability_map, &msm); + let ecs = state.ecs(); + let ability_map = ecs.read_resource::(); + let msm = ecs.read_resource::(); + let time = ecs.read_resource::