diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index b7dfe5e81d..c0192b227a 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -2,14 +2,15 @@ use crate::{ comp::{ self, inventory::item::{armor::Protection, ItemKind}, - Inventory, + CharacterState, Inventory, }, + states, util::Dir, }; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; -use std::ops::Mul; +use std::{ops::Mul, time::Duration}; use vek::*; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -52,6 +53,52 @@ pub enum PoiseState { KnockedDown, } +impl PoiseState { + pub fn poise_effect(&self, was_wielded: bool) -> (Option, Option) { + use states::{ + stunned::{Data, StaticData}, + utils::StageSection, + }; + // charstate_parameters is Option<(buildup_duration, recover_duration, + // movement_speed)> + let (charstate_parameters, impulse) = match self { + PoiseState::Normal => (None, None), + PoiseState::Interrupted => ( + Some((Duration::from_millis(125), Duration::from_millis(125), 0.80)), + None, + ), + PoiseState::Stunned => ( + Some((Duration::from_millis(300), Duration::from_millis(300), 0.65)), + Some(5.0), + ), + PoiseState::Dazed => ( + Some((Duration::from_millis(600), Duration::from_millis(250), 0.45)), + Some(10.0), + ), + PoiseState::KnockedDown => ( + Some((Duration::from_millis(750), Duration::from_millis(500), 0.4)), + Some(10.0), + ), + }; + ( + charstate_parameters.map(|(buildup_duration, recover_duration, movement_speed)| { + CharacterState::Stunned(Data { + static_data: StaticData { + buildup_duration, + recover_duration, + movement_speed, + poise_state: *self, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + was_wielded, + }) + }), + impulse, + ) + } +} + impl Poise { /// Maximum value allowed for poise before scaling const MAX_POISE: u16 = u16::MAX - 1; diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 282523b561..e9ec5fb443 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -7,8 +7,7 @@ use common::{ comp::{ self, character_state::OutputEvents, inventory::item::MaterialStatManifest, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, Inventory, InventoryManip, - Mass, Melee, Mounting, Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, - Stats, Vel, + Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet, StateUpdate, Stats, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, outcome::Outcome, @@ -21,7 +20,6 @@ use common::{ uid::Uid, }; use common_ecs::{Job, Origin, Phase, System}; -use std::time::Duration; #[derive(SystemData)] pub struct ReadData<'a> { @@ -142,92 +140,22 @@ impl<'a> System<'a> for Sys { let was_wielded = char_state.is_wield(); let poise_state = poise.poise_state(); let pos = pos.0; - match poise_state { - PoiseState::Normal => {}, - PoiseState::Interrupted => { - poise.reset(); - *char_state = CharacterState::Stunned(common::states::stunned::Data { - static_data: common::states::stunned::StaticData { - buildup_duration: Duration::from_millis(125), - recover_duration: Duration::from_millis(125), - movement_speed: 0.80, - poise_state, - }, - timer: Duration::default(), - stage_section: common::states::utils::StageSection::Buildup, - was_wielded, - }); - outcomes.push(Outcome::PoiseChange { - pos, - state: PoiseState::Interrupted, - }); - }, - PoiseState::Stunned => { - poise.reset(); - *char_state = CharacterState::Stunned(common::states::stunned::Data { - static_data: common::states::stunned::StaticData { - buildup_duration: Duration::from_millis(300), - recover_duration: Duration::from_millis(300), - movement_speed: 0.65, - poise_state, - }, - timer: Duration::default(), - stage_section: common::states::utils::StageSection::Buildup, - was_wielded, - }); - outcomes.push(Outcome::PoiseChange { - pos, - state: PoiseState::Stunned, - }); + if let (Some(stunned_state), impulse_strength) = + poise_state.poise_effect(was_wielded) + { + // Reset poise if there is some stunned state to apply + poise.reset(); + *char_state = stunned_state; + outcomes.push(Outcome::PoiseChange { + pos, + state: poise_state, + }); + if let Some(impulse_strength) = impulse_strength { server_emitter.emit(ServerEvent::Knockback { entity, - impulse: 5.0 * *poise.knockback(), + impulse: impulse_strength * *poise.knockback(), }); - }, - PoiseState::Dazed => { - poise.reset(); - *char_state = CharacterState::Stunned(common::states::stunned::Data { - static_data: common::states::stunned::StaticData { - buildup_duration: Duration::from_millis(600), - recover_duration: Duration::from_millis(250), - movement_speed: 0.45, - poise_state, - }, - timer: Duration::default(), - stage_section: common::states::utils::StageSection::Buildup, - was_wielded, - }); - outcomes.push(Outcome::PoiseChange { - pos, - state: PoiseState::Dazed, - }); - server_emitter.emit(ServerEvent::Knockback { - entity, - impulse: 10.0 * *poise.knockback(), - }); - }, - PoiseState::KnockedDown => { - poise.reset(); - *char_state = CharacterState::Stunned(common::states::stunned::Data { - static_data: common::states::stunned::StaticData { - buildup_duration: Duration::from_millis(750), - recover_duration: Duration::from_millis(500), - movement_speed: 0.4, - poise_state, - }, - timer: Duration::default(), - stage_section: common::states::utils::StageSection::Buildup, - was_wielded, - }); - outcomes.push(Outcome::PoiseChange { - pos, - state: PoiseState::KnockedDown, - }); - server_emitter.emit(ServerEvent::Knockback { - entity, - impulse: 10.0 * *poise.knockback(), - }); - }, + } } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8b98efce3e..4c90ae483d 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1095,14 +1095,35 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) { let ecs = &server.state.ecs(); let server_eventbus = ecs.read_resource::>(); + let mut outcomes = ecs.write_resource::>(); - if let Some(mut char_state) = ecs.write_storage::().get_mut(entity) { + if let (Some(mut char_state), Some(mut poise), Some(pos)) = ( + ecs.write_storage::().get_mut(entity), + ecs.write_storage::().get_mut(entity), + ecs.read_storage::().get(entity), + ) { // Interrupt sprite interaction and item use if any attack is applied to entity if matches!( *char_state, CharacterState::SpriteInteract(_) | CharacterState::UseItem(_) ) { - *char_state = CharacterState::Idle(common::states::idle::Data { is_sneaking: false }); + let poise_state = comp::poise::PoiseState::Dazed; + let was_wielded = char_state.is_wield(); + if let (Some(stunned_state), impulse_strength) = poise_state.poise_effect(was_wielded) { + // Reset poise if there is some stunned state to apply + poise.reset(); + *char_state = stunned_state; + outcomes.push(Outcome::PoiseChange { + pos: pos.0, + state: poise_state, + }); + if let Some(impulse_strength) = impulse_strength { + server_eventbus.emit_now(ServerEvent::Knockback { + entity, + impulse: impulse_strength * *poise.knockback(), + }); + } + } } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 605de5e531..6a421a9730 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -1173,9 +1173,12 @@ fn consumable_bags(economy: Option<&trade::SiteInformation>, rng: &mut impl Rng) .and_then(|e| e.unconsumed_stock.get(&Good::Food)) .copied() .map_or(Some(10_000.0), |food| Some(food.max(10_000.0))); + // Reduce amount of potions so merchants do not oversupply potions. + // TODO: Maybe remove when merchants and their inventories are rtsim? let mut potions = economy .and_then(|e| e.unconsumed_stock.get(&Good::Potions)) - .copied(); + .copied() + .map(|potions| potions.powf(0.25)); let goods = [ (Good::Food, &mut food, &mut bag3),