diff --git a/client/src/lib.rs b/client/src/lib.rs index 018ca47a64..17eb989464 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1369,8 +1369,8 @@ impl Client { } pub fn collect_block(&mut self, pos: Vec3) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::Collect(pos), + self.control_action(ControlAction::InventoryAction(InventoryAction::Collect( + pos, ))); } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index e3697f8678..9038594052 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -108,6 +108,9 @@ pub enum CharacterState { SpriteSummon(sprite_summon::Data), /// Handles logic for using an item so it is not simply instant UseItem(use_item::Data), + /// Handles logic for interacting with a sprite, e.g. using a chest or + /// picking a plant + SpriteInteract(sprite_interact::Data), } impl CharacterState { @@ -255,6 +258,7 @@ impl CharacterState { CharacterState::SelfBuff(data) => data.behavior(j), CharacterState::SpriteSummon(data) => data.behavior(j), CharacterState::UseItem(data) => data.behavior(j), + CharacterState::SpriteInteract(data) => data.behavior(j), } } @@ -295,6 +299,7 @@ impl CharacterState { CharacterState::SelfBuff(data) => data.handle_event(j, action), CharacterState::SpriteSummon(data) => data.handle_event(j, action), CharacterState::UseItem(data) => data.handle_event(j, action), + CharacterState::SpriteInteract(data) => data.handle_event(j, action), } } } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 54604d49f1..90e85909f8 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -17,7 +17,6 @@ use vek::*; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum InventoryEvent { Pickup(Uid), - Collect(Vec3), Swap(InvSlotId, InvSlotId), SplitSwap(InvSlotId, InvSlotId), Drop(InvSlotId), @@ -35,6 +34,7 @@ pub enum InventoryAction { Drop(EquipSlot), Use(Slot), Sort, + Collect(Vec3), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -61,6 +61,7 @@ impl From for InventoryManip { InventoryAction::Swap(equip, slot) => Self::Swap(Slot::Equip(equip), slot), InventoryAction::Drop(equip) => Self::Drop(Slot::Equip(equip)), InventoryAction::Sort => Self::Sort, + InventoryAction::Collect(collect) => Self::Collect(collect), } } } @@ -69,7 +70,6 @@ impl From for InventoryManip { fn from(inv_event: InventoryEvent) -> Self { match inv_event { InventoryEvent::Pickup(pickup) => Self::Pickup(pickup), - InventoryEvent::Collect(collect) => Self::Collect(collect), InventoryEvent::Swap(inv1, inv2) => { Self::Swap(Slot::Inventory(inv1), Slot::Inventory(inv2)) }, diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 7fcd03f9c2..608f815e6e 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -25,6 +25,7 @@ pub mod shockwave; pub mod sit; pub mod sneak; pub mod spin_melee; +pub mod sprite_interact; pub mod sprite_summon; pub mod stunned; pub mod talk; diff --git a/common/src/states/sprite_interact.rs b/common/src/states/sprite_interact.rs new file mode 100644 index 0000000000..2d7570a881 --- /dev/null +++ b/common/src/states/sprite_interact.rs @@ -0,0 +1,184 @@ +use super::utils::*; +use crate::{ + comp::{CharacterState, InventoryManip, StateUpdate}, + event::ServerEvent, + states::behavior::{CharacterBehavior, JoinData}, + terrain::SpriteKind, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use vek::Vec3; + +/// Separated out to condense update portions of character state +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// Buildup to item use + pub buildup_duration: Duration, + /// Duration of item use + pub use_duration: Duration, + /// Recovery after item use + pub recover_duration: Duration, + /// Position sprite is located at + pub sprite_pos: Vec3, + /// Kind of sprite interacted with + pub sprite_kind: SpriteInteractKind, + /// Had weapon wielded + pub was_wielded: bool, + /// Was sneaking + pub was_sneak: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_orientation(data, &mut update, 0.0); + handle_move(data, &mut update, 0.0); + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::SpriteInteract(Data { + timer: tick_attack_or_default(data, self.timer, None), + ..*self + }); + } else { + // Transitions to use section of stage + update.character = CharacterState::SpriteInteract(Data { + timer: Duration::default(), + stage_section: StageSection::Action, + ..*self + }); + } + }, + StageSection::Action => { + if self.timer < self.static_data.use_duration { + // Item use + update.character = CharacterState::SpriteInteract(Data { + timer: tick_attack_or_default(data, self.timer, None), + ..*self + }); + } else { + // Transitions to recover section of stage + update.character = CharacterState::SpriteInteract(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovery + update.character = CharacterState::SpriteInteract(Data { + timer: tick_attack_or_default(data, self.timer, None), + ..*self + }); + } else { + // Create inventory manipulation event + let inv_manip = InventoryManip::Collect(self.static_data.sprite_pos); + update + .server_events + .push_front(ServerEvent::InventoryManip(data.entity, inv_manip)); + // Done + if self.static_data.was_wielded { + update.character = CharacterState::Wielding; + } else if self.static_data.was_sneak { + update.character = CharacterState::Sneak; + } else { + update.character = CharacterState::Idle; + } + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Idle; + }, + } + + // At end of state logic so an interrupt isn't overwritten + handle_state_interrupt(data, &mut update, false); + + update + } +} + +/// Used to control effects based off of the type of item used +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum SpriteInteractKind { + Chest, + Harvestable, + Collectible, +} + +impl From for Option { + fn from(sprite_kind: SpriteKind) -> Self { + match sprite_kind { + SpriteKind::Apple + | SpriteKind::Mushroom + | SpriteKind::RedFlower + | SpriteKind::Sunflower + | SpriteKind::Coconut + | SpriteKind::Beehive + | SpriteKind::Cotton + | SpriteKind::Moonbell + | SpriteKind::Pyrebloom + | SpriteKind::WildFlax + | SpriteKind::RoundCactus + | SpriteKind::ShortFlatCactus + | SpriteKind::MedFlatCactus => Some(SpriteInteractKind::Harvestable), + SpriteKind::Stones + | SpriteKind::Twigs + | SpriteKind::VialEmpty + | SpriteKind::Bowl + | SpriteKind::PotionMinor + | SpriteKind::Seashells => Some(SpriteInteractKind::Collectible), + SpriteKind::DungeonChest0 + | SpriteKind::DungeonChest1 + | SpriteKind::DungeonChest2 + | SpriteKind::DungeonChest3 + | SpriteKind::DungeonChest4 + | SpriteKind::DungeonChest5 + | SpriteKind::Chest + | SpriteKind::ChestBuried + | SpriteKind::Mud + | SpriteKind::Crate => Some(SpriteInteractKind::Chest), + _ => None, + } + } +} + +impl SpriteInteractKind { + /// Returns (buildup, use, recover) + pub fn durations(&self) -> (Duration, Duration, Duration) { + match self { + Self::Chest => ( + Duration::from_secs_f32(0.5), + Duration::from_secs_f32(2.0), + Duration::from_secs_f32(0.5), + ), + Self::Collectible => ( + Duration::from_secs_f32(0.1), + Duration::from_secs_f32(0.3), + Duration::from_secs_f32(0.1), + ), + Self::Harvestable => ( + Duration::from_secs_f32(0.3), + Duration::from_secs_f32(0.5), + Duration::from_secs_f32(0.2), + ), + } + } +} diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index c9be9a3605..e060a68d74 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -13,6 +13,7 @@ use crate::{ event::{LocalEvent, ServerEvent}, states::{behavior::JoinData, *}, util::Dir, + vol::ReadVol, }; use serde::{Deserialize, Serialize}; use std::{ @@ -584,44 +585,87 @@ pub fn handle_manipulate_loadout( update: &mut StateUpdate, inv_action: InventoryAction, ) { - use use_item::ItemUseKind; - if let InventoryAction::Use(Slot::Inventory(inv_slot)) = inv_action { - // If inventory action is using a slot, and slot is in the inventory - // TODO: Do some non lazy way of handling the possibility that items equipped in - // the loadout will have effects that are desired to be non-instantaneous - if let Some((item_kind, item)) = data - .inventory - .and_then(|inv| inv.get(inv_slot)) - .and_then(|item| Option::::from(item.kind()).zip(Some(item))) - { - let (buildup_duration, use_duration, recover_duration) = item_kind.durations(); - // If item returns a valid kind for item use, do into use item character state - update.character = CharacterState::UseItem(use_item::Data { - static_data: use_item::StaticData { - buildup_duration, - use_duration, - recover_duration, - inv_slot, - item_kind, - item_definition_id: item.item_definition_id().to_string(), - was_wielded: matches!(data.character, CharacterState::Wielding), - was_sneak: matches!(data.character, CharacterState::Sneak), - }, - timer: Duration::default(), - stage_section: StageSection::Buildup, - }); - } else { - // Else emit inventory action instantnaneously + match inv_action { + InventoryAction::Use(Slot::Inventory(inv_slot)) => { + // If inventory action is using a slot, and slot is in the inventory + // TODO: Do some non lazy way of handling the possibility that items equipped in + // the loadout will have effects that are desired to be non-instantaneous + use use_item::ItemUseKind; + if let Some((item_kind, item)) = data + .inventory + .and_then(|inv| inv.get(inv_slot)) + .and_then(|item| Option::::from(item.kind()).zip(Some(item))) + { + let (buildup_duration, use_duration, recover_duration) = item_kind.durations(); + // If item returns a valid kind for item use, do into use item character state + update.character = CharacterState::UseItem(use_item::Data { + static_data: use_item::StaticData { + buildup_duration, + use_duration, + recover_duration, + inv_slot, + item_kind, + item_definition_id: item.item_definition_id().to_string(), + was_wielded: matches!(data.character, CharacterState::Wielding), + was_sneak: matches!(data.character, CharacterState::Sneak), + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }); + } else { + // Else emit inventory action instantnaneously + update + .server_events + .push_front(ServerEvent::InventoryManip(data.entity, inv_action.into())); + } + }, + InventoryAction::Collect(pos) => { + // First, get sprite data for position, if there is a sprite + use sprite_interact::SpriteInteractKind; + let sprite_at_pos = data + .terrain + .get(pos) + .ok() + .copied() + .and_then(|b| b.get_sprite()); + + // Checks if position has a collectible sprite as wella s what sprite is at the + // position + let sprite_interact = sprite_at_pos.and_then(Option::::from); + + if let Some(sprite_interact) = sprite_interact { + // If the sprite is collectible, enter the sprite interaction character state + // TODO: Handle cases for sprite being interactible, but not collectible (none + // currently exist) + let (buildup_duration, use_duration, recover_duration) = + sprite_interact.durations(); + + update.character = CharacterState::SpriteInteract(sprite_interact::Data { + static_data: sprite_interact::StaticData { + buildup_duration, + use_duration, + recover_duration, + sprite_pos: pos, + sprite_kind: sprite_interact, + was_wielded: matches!(data.character, CharacterState::Wielding), + was_sneak: matches!(data.character, CharacterState::Sneak), + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }) + } else { + // Otherwise send server event immediately + update + .server_events + .push_front(ServerEvent::InventoryManip(data.entity, inv_action.into())); + } + }, + _ => { + // Else just do event instantaneously update .server_events .push_front(ServerEvent::InventoryManip(data.entity, inv_action.into())); - } - } else { - // Else if inventory action is not item use, or if slot is in loadout, just do - // event instantaneously - update - .server_events - .push_front(ServerEvent::InventoryManip(data.entity, inv_action.into())); + }, } } diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index e3cc55faa3..77288da764 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -261,7 +261,8 @@ impl<'a> System<'a> for Sys { | CharacterState::Climb { .. } | CharacterState::Stunned { .. } | CharacterState::BasicBlock { .. } - | CharacterState::UseItem { .. } => {}, + | CharacterState::UseItem { .. } + | CharacterState::SpriteInteract { .. } => {}, } }