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)
This commit is contained in:
Ben Wallis 2020-06-26 19:40:28 +01:00
parent 8e67782a49
commit a9d3f984f0
10 changed files with 123 additions and 41 deletions

View File

@ -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,
)
}
)

BIN
assets/voxygen/audio/sfx/inventory/pickup_staff.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/inventory/pickup_sword.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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::<EventBus<SfxEventItem>>()
.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::<EventBus<SfxEventItem>>()
.emit_now(SfxEventItem::at_player_position(SfxEvent::Inventory(event)));
},
ServerMsg::TerrainChunkUpdate { key, chunk } => {
if let Ok(chunk) = chunk {

View File

@ -313,7 +313,7 @@ impl Component for Inventory {
type Storage = HashMapStorage<Self>;
}
#[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 {

View File

@ -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 {

View File

@ -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<comp::LightEmitter>, 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<comp::Item> = 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::<comp::Inventory>()
.get_mut(entity),
) {
if within_pickup_range(
picked_up_item = Some(item.clone());
if !within_pickup_range(
state.ecs().read_storage::<comp::Pos>().get(entity),
state.ecs().read_storage::<comp::Pos>().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) => {

View File

@ -57,12 +57,12 @@ impl StateExt for State {
.ecs()
.write_storage::<comp::Inventory>()
.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

View File

@ -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();
}

View File

@ -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 { .. }) => {