From a9d3f984f0d90c17b275f049dde9b0b6d0cc2cdc Mon Sep 17 00:00:00 2001 From: Ben Wallis Date: Fri, 26 Jun 2020 19:40:28 +0100 Subject: [PATCH] Implemented loot pickup chat messages and the option for playing different sounds for picked up items * Added chat message when an item is picked up * Changed InventoryUpdateEvent::Collected to InventoryUpdateEvent::Collected(Item) to facilitate the client being aware of what was picked up * Added SfxInventoryEvent enum to allow different sounds to be used based on the item type. The RON mapping/de-serialization doesn't support matching on structs so we have to give it fixed enum values which are determined in TryFrom<&InventoryUpdateEvent> for SfxEvent * Refactored InventoryManip::Pickup arm of match in inventory_manip::handle_inventory for clarity/better warning messages * Fixed a bug that prevented the CollectFailed event from being raised when a player's inventory is full * Added a panic for the situation where an item is pushed into the players inventory and then the deletion of the entity fails as this would indicate an item dupe bug - this could potentially be reworked to pull the item back from the player's inventory but this seems like there's be a more correct transactional way to do this. * Added two temporary sounds to prove the per-item sound functionality (pickup sounds for Swords and Staffs) --- assets/voxygen/audio/sfx.ron | 26 ++++++--- .../audio/sfx/inventory/pickup_staff.wav | 3 ++ .../audio/sfx/inventory/pickup_sword.wav | 3 ++ client/src/lib.rs | 19 +++++-- common/src/comp/inventory/mod.rs | 8 +-- common/src/event.rs | 53 +++++++++++++++++-- server/src/events/inventory_manip.rs | 42 +++++++++------ server/src/state_ext.rs | 4 +- .../src/audio/sfx/event_mapper/combat/mod.rs | 2 +- .../audio/sfx/event_mapper/movement/mod.rs | 4 +- 10 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 assets/voxygen/audio/sfx/inventory/pickup_staff.wav create mode 100644 assets/voxygen/audio/sfx/inventory/pickup_sword.wav diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index d890560890..a9ea7d468b 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -36,8 +36,26 @@ threshold: 0.5, ), Inventory(Collected): ( + files: [ + "voxygen.audio.sfx.inventory.add_item", + ], + threshold: 0.3, + ), + Inventory(CollectedTool(Sword)): ( + files: [ + "voxygen.audio.sfx.inventory.pickup_sword", + ], + threshold: 0.3, + ), + Inventory(CollectedTool(Staff)): ( + files: [ + "voxygen.audio.sfx.inventory.pickup_staff", + ], + threshold: 0.3, + ), + Inventory(CollectFailed): ( files: [ - "voxygen.audio.sfx.inventory.add_item", + "voxygen.audio.sfx.inventory.add_failed", ], threshold: 0.3, ), @@ -88,12 +106,6 @@ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, - ), - Inventory(CollectFailed): ( - files: [ - "voxygen.audio.sfx.inventory.add_failed", - ], - threshold: 0.3, ) } ) diff --git a/assets/voxygen/audio/sfx/inventory/pickup_staff.wav b/assets/voxygen/audio/sfx/inventory/pickup_staff.wav new file mode 100644 index 0000000000..aaa300fc40 --- /dev/null +++ b/assets/voxygen/audio/sfx/inventory/pickup_staff.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec108383091e16dd7e45a769ab676ffe595e067a6ee4c0dae7b6cb03c910520b +size 38418 diff --git a/assets/voxygen/audio/sfx/inventory/pickup_sword.wav b/assets/voxygen/audio/sfx/inventory/pickup_sword.wav new file mode 100644 index 0000000000..99eb77f675 --- /dev/null +++ b/assets/voxygen/audio/sfx/inventory/pickup_sword.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d3a5409656fb7bda8831e61b11d69344b950f63e0b7bf72a9ed599420379102 +size 165082 diff --git a/client/src/lib.rs b/client/src/lib.rs index 10b4b0467d..73e99c0445 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -867,6 +867,13 @@ impl Client { self.clean_state(); }, ServerMsg::InventoryUpdate(inventory, event) => { + // TODO: Move this SFX code to Voxygen + let sfx_event = SfxEvent::from(&event); + self.state + .ecs() + .read_resource::>() + .emit_now(SfxEventItem::at_player_position(sfx_event)); + match event { InventoryUpdateEvent::CollectFailed => { frontend_events.push(Event::Chat { @@ -877,14 +884,16 @@ impl Client { }) }, _ => { + if let InventoryUpdateEvent::Collected(item) = event { + frontend_events.push(Event::Chat { + message: format!("Picked up {}", item.name()), + chat_type: ChatType::Meta, + }); + } + self.state.write_component(self.entity, inventory); }, } - - self.state - .ecs() - .read_resource::>() - .emit_now(SfxEventItem::at_player_position(SfxEvent::Inventory(event))); }, ServerMsg::TerrainChunkUpdate { key, chunk } => { if let Ok(chunk) = chunk { diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 4a5fd63322..4ff88d819b 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -313,7 +313,7 @@ impl Component for Inventory { type Storage = HashMapStorage; } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub enum InventoryUpdateEvent { Init, Used, @@ -322,7 +322,7 @@ pub enum InventoryUpdateEvent { Given, Swapped, Dropped, - Collected, + Collected(Item), CollectFailed, Possession, Debug, @@ -332,7 +332,7 @@ impl Default for InventoryUpdateEvent { fn default() -> Self { Self::Init } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct InventoryUpdate { event: InventoryUpdateEvent, } @@ -340,7 +340,7 @@ pub struct InventoryUpdate { impl InventoryUpdate { pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } } - pub fn event(&self) -> InventoryUpdateEvent { self.event } + pub fn event(&self) -> InventoryUpdateEvent { self.event.clone() } } impl Component for InventoryUpdate { diff --git a/common/src/event.rs b/common/src/event.rs index f848ae4c7e..0c4c8ce966 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,9 +1,14 @@ -use crate::{comp, sync::Uid, util::Dir}; +use crate::{ + comp, + comp::item::{Consumable, ItemKind}, + sync::Uid, + util::Dir, +}; use comp::{item::ToolCategory, CharacterAbilityType, InventoryUpdateEvent, Item}; use parking_lot::Mutex; use serde::Deserialize; use specs::Entity as EcsEntity; -use std::{collections::VecDeque, ops::DerefMut}; +use std::{collections::VecDeque, convert::TryFrom, ops::DerefMut}; use vek::*; pub struct SfxEventItem { @@ -26,7 +31,7 @@ impl SfxEventItem { } } -#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] +#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)] pub enum SfxEvent { Idle, Run, @@ -42,7 +47,47 @@ pub enum SfxEvent { Attack(CharacterAbilityType, ToolCategory), Wield(ToolCategory), Unwield(ToolCategory), - Inventory(InventoryUpdateEvent), + Inventory(SfxInventoryEvent), +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)] +pub enum SfxInventoryEvent { + Collected, + CollectedTool(ToolCategory), + CollectFailed, + Consumed(Consumable), + Debug, + Dropped, + Given, + Swapped, +} + +impl From<&InventoryUpdateEvent> for SfxEvent { + fn from(value: &InventoryUpdateEvent) -> Self { + match value { + InventoryUpdateEvent::Collected(item) => { + // Handle sound effects for types of collected items, falling back to the + // default Collected event + match item.kind { + ItemKind::Tool(tool) => SfxEvent::Inventory(SfxInventoryEvent::CollectedTool( + ToolCategory::try_from(tool.kind).unwrap(), + )), + _ => SfxEvent::Inventory(SfxInventoryEvent::Collected), + } + }, + InventoryUpdateEvent::CollectFailed => { + SfxEvent::Inventory(SfxInventoryEvent::CollectFailed) + }, + InventoryUpdateEvent::Consumed(consumable) => { + SfxEvent::Inventory(SfxInventoryEvent::Consumed(*consumable)) + }, + InventoryUpdateEvent::Debug => SfxEvent::Inventory(SfxInventoryEvent::Debug), + InventoryUpdateEvent::Dropped => SfxEvent::Inventory(SfxInventoryEvent::Dropped), + InventoryUpdateEvent::Given => SfxEvent::Inventory(SfxInventoryEvent::Given), + InventoryUpdateEvent::Swapped => SfxEvent::Inventory(SfxInventoryEvent::Swapped), + _ => SfxEvent::Inventory(SfxInventoryEvent::Swapped), + } + } } pub enum LocalEvent { diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 4021de541e..ec4d157be9 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -11,7 +11,7 @@ use common::{ }; use rand::Rng; use specs::{join::Join, world::WorldExt, Builder, Entity as EcsEntity, WriteStorage}; -use tracing::error; +use tracing::{debug, error, warn}; use vek::Vec3; pub fn swap_lantern( @@ -29,14 +29,14 @@ pub fn snuff_lantern(storage: &mut WriteStorage, entity: Ecs storage.remove(entity); } -#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 -#[allow(clippy::let_and_return)] // TODO: Pending review in #587 +#[allow(clippy::blocks_in_if_conditions)] pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) { let state = server.state_mut(); let mut dropped_items = Vec::new(); match manip { comp::InventoryManip::Pickup(uid) => { + let mut picked_up_item: Option = None; let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( state .ecs() @@ -53,29 +53,39 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .write_storage::() .get_mut(entity), ) { - if within_pickup_range( + picked_up_item = Some(item.clone()); + if !within_pickup_range( state.ecs().read_storage::().get(entity), state.ecs().read_storage::().get(item_entity), - ) && inv.push(item).is_none() - { - Some(item_entity) - } else { - None + ) { + debug!("Failed to pick up item as not within range, Uid: {}", uid); + return; + }; + + // Attempt to add the item to the player's inventory + match inv.push(item) { + None => Some(item_entity), + Some(_) => None, // Inventory was full } } else { + warn!("Failed to get entity/component for item Uid: {}", uid); None }; - if let Some(item_entity) = item_entity { + let event = if let Some(item_entity) = item_entity { if let Err(err) = state.delete_entity_recorded(item_entity) { - error!("Failed to delete picked up item entity: {:?}", err); + // If this occurs it means the item was duped as it's been pushed to the + // player's inventory but also left on the ground + panic!("Failed to delete picked up item entity: {:?}", err); } - } + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected( + picked_up_item.unwrap(), + )) + } else { + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::CollectFailed) + }; - state.write_component( - entity, - comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected), - ); + state.write_component(entity, event); }, comp::InventoryManip::Collect(pos) => { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index a938fc34b0..36dfed6a00 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -57,12 +57,12 @@ impl StateExt for State { .ecs() .write_storage::() .get_mut(entity) - .map(|inv| inv.push(item).is_none()) + .map(|inv| inv.push(item.clone()).is_none()) .unwrap_or(false); if success { self.write_component( entity, - comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected), + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected(item)), ); } success diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs index 1bae3e4fc6..1898d6dd8e 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs @@ -68,7 +68,7 @@ impl EventMapper for CombatEventMapper { // Check for SFX config entry for this movement if Self::should_emit(state, triggers.get_key_value(&mapped_event)) { - sfx_emitter.emit(SfxEventItem::new(mapped_event, Some(pos.0), None)); + sfx_emitter.emit(SfxEventItem::new(mapped_event.clone(), Some(pos.0), None)); state.time = Instant::now(); } diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index e3292fb071..8a5e0b74ff 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -76,7 +76,7 @@ impl EventMapper for MovementEventMapper { // Check for SFX config entry for this movement if Self::should_emit(state, triggers.get_key_value(&mapped_event)) { sfx_emitter.emit(SfxEventItem::new( - mapped_event, + mapped_event.clone(), Some(pos.0), Some(Self::get_volume_for_body_type(body)), )); @@ -161,7 +161,7 @@ impl MovementEventMapper { } // Match all other Movemement and Action states - match (previous_state.event, character_state) { + match (previous_state.event.clone(), character_state) { (_, CharacterState::Climb { .. }) => SfxEvent::Climb, (SfxEvent::Glide, CharacterState::Idle { .. }) => SfxEvent::GliderClose, (previous_event, CharacterState::Glide { .. }) => {