Merge branch 'sam/sprite-interaction' into 'master'

Sprite Interaction

See merge request veloren/veloren!2790
This commit is contained in:
Joshua Barretto 2021-08-31 18:42:16 +00:00
commit 70b3374705
42 changed files with 582 additions and 106 deletions

View File

@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The 'spot' system, which generates smaller site-like structures and scenarios - The 'spot' system, which generates smaller site-like structures and scenarios
- Chestnut and cedar tree varieties - Chestnut and cedar tree varieties
- Shooting sprites, such as apples and hives, can knock them out of trees - Shooting sprites, such as apples and hives, can knock them out of trees
- Sprite pickup animations
### Changed ### Changed
@ -66,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Enemies no more spawn in dungeon boss room - Enemies no more spawn in dungeon boss room
- Melee critical hit no more applies after reduction by armour - Melee critical hit no more applies after reduction by armour
- Removed Healing Sceptre as a starting weapon as it is considered an advanced weapon - Removed Healing Sceptre as a starting weapon as it is considered an advanced weapon
- The ability to pickup sprites through walls
### Fixed ### Fixed

1
Cargo.lock generated
View File

@ -5859,6 +5859,7 @@ dependencies = [
"csv", "csv",
"dot_vox", "dot_vox",
"enum-iterator", "enum-iterator",
"fxhash",
"hashbrown 0.11.2", "hashbrown 0.11.2",
"indexmap", "indexmap",
"lazy_static", "lazy_static",

View File

@ -1369,8 +1369,8 @@ impl Client {
} }
pub fn collect_block(&mut self, pos: Vec3<i32>) { pub fn collect_block(&mut self, pos: Vec3<i32>) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( self.control_action(ControlAction::InventoryAction(InventoryAction::Collect(
InventoryEvent::Collect(pos), pos,
))); )));
} }

View File

@ -47,6 +47,7 @@ spin_sleep = "1.0"
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }
uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] } uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] }
rand = "0.8" rand = "0.8"
fxhash = "0.2.1"
# Assets # Assets
common-assets = {package = "veloren-common-assets", path = "assets"} common-assets = {package = "veloren-common-assets", path = "assets"}

View File

@ -452,6 +452,13 @@ impl Attack {
} }
} }
} }
// Emits event to handle things that should happen for any successful attack,
// regardless of if the attack had any damages or effects in it
if is_applied {
emit(ServerEvent::EntityAttackedHook {
entity: target.entity,
});
}
is_applied is_applied
} }
} }

View File

@ -108,6 +108,9 @@ pub enum CharacterState {
SpriteSummon(sprite_summon::Data), SpriteSummon(sprite_summon::Data),
/// Handles logic for using an item so it is not simply instant /// Handles logic for using an item so it is not simply instant
UseItem(use_item::Data), 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 { impl CharacterState {
@ -255,6 +258,7 @@ impl CharacterState {
CharacterState::SelfBuff(data) => data.behavior(j), CharacterState::SelfBuff(data) => data.behavior(j),
CharacterState::SpriteSummon(data) => data.behavior(j), CharacterState::SpriteSummon(data) => data.behavior(j),
CharacterState::UseItem(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::SelfBuff(data) => data.handle_event(j, action),
CharacterState::SpriteSummon(data) => data.handle_event(j, action), CharacterState::SpriteSummon(data) => data.handle_event(j, action),
CharacterState::UseItem(data) => data.handle_event(j, action), CharacterState::UseItem(data) => data.handle_event(j, action),
CharacterState::SpriteInteract(data) => data.handle_event(j, action),
} }
} }
} }

View File

@ -17,7 +17,6 @@ use vek::*;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InventoryEvent { pub enum InventoryEvent {
Pickup(Uid), Pickup(Uid),
Collect(Vec3<i32>),
Swap(InvSlotId, InvSlotId), Swap(InvSlotId, InvSlotId),
SplitSwap(InvSlotId, InvSlotId), SplitSwap(InvSlotId, InvSlotId),
Drop(InvSlotId), Drop(InvSlotId),
@ -35,6 +34,7 @@ pub enum InventoryAction {
Drop(EquipSlot), Drop(EquipSlot),
Use(Slot), Use(Slot),
Sort, Sort,
Collect(Vec3<i32>),
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -61,6 +61,7 @@ impl From<InventoryAction> for InventoryManip {
InventoryAction::Swap(equip, slot) => Self::Swap(Slot::Equip(equip), slot), InventoryAction::Swap(equip, slot) => Self::Swap(Slot::Equip(equip), slot),
InventoryAction::Drop(equip) => Self::Drop(Slot::Equip(equip)), InventoryAction::Drop(equip) => Self::Drop(Slot::Equip(equip)),
InventoryAction::Sort => Self::Sort, InventoryAction::Sort => Self::Sort,
InventoryAction::Collect(collect) => Self::Collect(collect),
} }
} }
} }
@ -69,7 +70,6 @@ impl From<InventoryEvent> for InventoryManip {
fn from(inv_event: InventoryEvent) -> Self { fn from(inv_event: InventoryEvent) -> Self {
match inv_event { match inv_event {
InventoryEvent::Pickup(pickup) => Self::Pickup(pickup), InventoryEvent::Pickup(pickup) => Self::Pickup(pickup),
InventoryEvent::Collect(collect) => Self::Collect(collect),
InventoryEvent::Swap(inv1, inv2) => { InventoryEvent::Swap(inv1, inv2) => {
Self::Swap(Slot::Inventory(inv1), Slot::Inventory(inv2)) Self::Swap(Slot::Inventory(inv1), Slot::Inventory(inv2))
}, },

View File

@ -1,5 +1,5 @@
// The limit on distance between the entity and a collectible (squared) // The limit on distance between the entity and a collectible (squared)
pub const MAX_PICKUP_RANGE: f32 = 8.0; pub const MAX_PICKUP_RANGE: f32 = 5.0;
pub const MAX_MOUNT_RANGE: f32 = 14.0; pub const MAX_MOUNT_RANGE: f32 = 14.0;
pub const GRAVITY: f32 = 25.0; pub const GRAVITY: f32 = 25.0;

View File

@ -197,6 +197,9 @@ pub enum ServerEvent {
pet_entity: EcsEntity, pet_entity: EcsEntity,
owner_entity: EcsEntity, owner_entity: EcsEntity,
}, },
EntityAttackedHook {
entity: EcsEntity,
},
} }
pub struct EventBus<E> { pub struct EventBus<E> {

View File

@ -55,7 +55,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.8); handle_move(data, &mut update, 0.8);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -67,7 +67,7 @@ impl CharacterBehavior for Data {
let ori_rate = self.static_data.ori_rate; let ori_rate = self.static_data.ori_rate;
handle_orientation(data, &mut update, ori_rate); handle_orientation(data, &mut update, ori_rate, None);
handle_move(data, &mut update, 0.4); handle_move(data, &mut update, 0.4);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -36,7 +36,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.4); handle_move(data, &mut update, 0.4);
match self.stage_section { match self.stage_section {

View File

@ -56,10 +56,9 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.7); handle_move(data, &mut update, 0.7);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);
handle_orientation(data, &mut update, 0.35);
match self.stage_section { match self.stage_section {
StageSection::Buildup => { StageSection::Buildup => {

View File

@ -48,7 +48,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.3); handle_move(data, &mut update, 0.3);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -37,7 +37,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
match self.stage_section { match self.stage_section {
StageSection::Buildup => { StageSection::Buildup => {

View File

@ -72,7 +72,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.7); handle_move(data, &mut update, 0.7);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -74,7 +74,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, self.static_data.move_speed); handle_move(data, &mut update, self.static_data.move_speed);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -175,7 +175,12 @@ impl CharacterBehavior for Data {
match self.stage_section { match self.stage_section {
StageSection::Buildup => { StageSection::Buildup => {
if self.timer < self.static_data.stage_data[stage_index].base_buildup_duration { if self.timer < self.static_data.stage_data[stage_index].base_buildup_duration {
handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier); handle_orientation(
data,
&mut update,
0.4 * self.static_data.ori_modifier,
None,
);
// Build up // Build up
update.character = CharacterState::ComboMelee(Data { update.character = CharacterState::ComboMelee(Data {
@ -289,7 +294,12 @@ impl CharacterBehavior for Data {
}); });
} else if self.timer < self.static_data.stage_data[stage_index].base_swing_duration } else if self.timer < self.static_data.stage_data[stage_index].base_swing_duration
{ {
handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier); handle_orientation(
data,
&mut update,
0.4 * self.static_data.ori_modifier,
None,
);
// Forward movement // Forward movement
handle_forced_movement(data, &mut update, ForcedMovement::Forward { handle_forced_movement(data, &mut update, ForcedMovement::Forward {
@ -314,7 +324,12 @@ impl CharacterBehavior for Data {
}, },
StageSection::Recover => { StageSection::Recover => {
if self.timer < self.static_data.stage_data[stage_index].base_recover_duration { if self.timer < self.static_data.stage_data[stage_index].base_recover_duration {
handle_orientation(data, &mut update, 0.8 * self.static_data.ori_modifier); handle_orientation(
data,
&mut update,
0.8 * self.static_data.ori_modifier,
None,
);
// Recovers // Recovers
update.character = CharacterState::ComboMelee(Data { update.character = CharacterState::ComboMelee(Data {
static_data: self.static_data.clone(), static_data: self.static_data.clone(),

View File

@ -82,7 +82,7 @@ impl CharacterBehavior for Data {
match self.stage_section { match self.stage_section {
StageSection::Buildup => { StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration { if self.timer < self.static_data.buildup_duration {
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
// Build up // Build up
update.character = CharacterState::DashMelee(Data { update.character = CharacterState::DashMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(data, self.timer, None),
@ -109,7 +109,7 @@ impl CharacterBehavior for Data {
/ self.static_data.charge_duration.as_secs_f32()) / self.static_data.charge_duration.as_secs_f32())
.min(1.0); .min(1.0);
handle_orientation(data, &mut update, self.static_data.ori_modifier); handle_orientation(data, &mut update, self.static_data.ori_modifier, None);
handle_forced_movement(data, &mut update, ForcedMovement::Forward { handle_forced_movement(data, &mut update, ForcedMovement::Forward {
strength: self.static_data.forward_speed * charge_frac.sqrt(), strength: self.static_data.forward_speed * charge_frac.sqrt(),
}); });

View File

@ -26,7 +26,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 1.0); handle_move(data, &mut update, 1.0);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -35,7 +35,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 1.0); handle_move(data, &mut update, 1.0);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);
handle_dodge_input(data, &mut update); handle_dodge_input(data, &mut update);

View File

@ -10,7 +10,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 1.0); handle_move(data, &mut update, 1.0);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);
handle_wield(data, &mut update); handle_wield(data, &mut update);

View File

@ -60,7 +60,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.3); handle_move(data, &mut update, 0.3);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);

View File

@ -25,6 +25,7 @@ pub mod shockwave;
pub mod sit; pub mod sit;
pub mod sneak; pub mod sneak;
pub mod spin_melee; pub mod spin_melee;
pub mod sprite_interact;
pub mod sprite_summon; pub mod sprite_summon;
pub mod stunned; pub mod stunned;
pub mod talk; pub mod talk;

View File

@ -54,7 +54,7 @@ pub struct Data {
impl CharacterBehavior for Data { impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.3); handle_move(data, &mut update, 0.3);
match self.stage_section { match self.stage_section {

View File

@ -54,7 +54,7 @@ impl CharacterBehavior for Data {
update.should_strafe = false; update.should_strafe = false;
// Smooth orientation // Smooth orientation
handle_orientation(data, &mut update, 2.5); handle_orientation(data, &mut update, 2.5, None);
match self.stage_section { match self.stage_section {
StageSection::Buildup => { StageSection::Buildup => {

View File

@ -65,7 +65,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, self.static_data.move_efficiency); handle_move(data, &mut update, self.static_data.move_efficiency);
match self.stage_section { match self.stage_section {

View File

@ -10,7 +10,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 0.4); handle_move(data, &mut update, 0.4);
handle_jump(data, &mut update, 1.0); handle_jump(data, &mut update, 1.0);
handle_wield(data, &mut update); handle_wield(data, &mut update);

View File

@ -0,0 +1,190 @@
use super::utils::*;
use crate::{
comp::{CharacterState, InventoryManip, StateUpdate},
event::ServerEvent,
states::behavior::{CharacterBehavior, JoinData},
terrain::SpriteKind,
util::Dir,
};
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 sprite interaction
pub buildup_duration: Duration,
/// Duration of sprite interaction
pub use_duration: Duration,
/// Recovery after sprite interaction
pub recover_duration: Duration,
/// Position sprite is located at
pub sprite_pos: Vec3<i32>,
/// 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);
let ori_dir = Dir::from_unnormalized(Vec3::from(
(self.static_data.sprite_pos.map(|x| x as f32) - data.pos.0).xy(),
));
handle_orientation(data, &mut update, 1.0, ori_dir);
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 {
// sprite interaction
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 sprite interacted with
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SpriteInteractKind {
Chest,
Harvestable,
Collectible,
Fallback,
}
impl From<SpriteKind> for Option<SpriteInteractKind> {
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),
// Collectible checked in addition to container for case that sprite requires a tool to
// collect and cannot be collected by hand, yet still meets the container check
_ if sprite_kind.is_container() && sprite_kind.is_collectible() => {
Some(SpriteInteractKind::Chest)
},
_ if sprite_kind.is_collectible() => Some(SpriteInteractKind::Fallback),
_ => 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),
),
Self::Fallback => (
Duration::from_secs_f32(5.0),
Duration::from_secs_f32(5.0),
Duration::from_secs_f32(5.0),
),
}
}
}

View File

@ -36,7 +36,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, self.static_data.movement_speed); handle_move(data, &mut update, self.static_data.movement_speed);
match self.stage_section { match self.stage_section {

View File

@ -15,7 +15,7 @@ impl CharacterBehavior for Data {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_wield(data, &mut update); handle_wield(data, &mut update);
handle_orientation(data, &mut update, TURN_RATE); handle_orientation(data, &mut update, TURN_RATE, None);
update update
} }

View File

@ -52,11 +52,11 @@ impl CharacterBehavior for Data {
match self.static_data.item_kind { match self.static_data.item_kind {
ItemUseKind::Consumable(ConsumableKind::Drink) => { ItemUseKind::Consumable(ConsumableKind::Drink) => {
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 1.0); handle_move(data, &mut update, 1.0);
}, },
ItemUseKind::Consumable(ConsumableKind::Food | ConsumableKind::ComplexFood) => { ItemUseKind::Consumable(ConsumableKind::Food | ConsumableKind::ComplexFood) => {
handle_orientation(data, &mut update, 0.0); handle_orientation(data, &mut update, 0.0, None);
handle_move(data, &mut update, 0.0); handle_move(data, &mut update, 0.0);
}, },
} }

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
astar::Astar,
combat, combat,
comp::{ comp::{
biped_large, biped_small, biped_large, biped_small,
@ -9,11 +10,14 @@ use crate::{
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, StateUpdate, InventoryAction, StateUpdate,
}, },
consts::{FRIC_GROUND, GRAVITY}, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
states::{behavior::JoinData, *}, states::{behavior::JoinData, *},
util::Dir, util::Dir,
vol::ReadVol,
}; };
use core::hash::BuildHasherDefault;
use fxhash::FxHasher64;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
ops::{Add, Div}, ops::{Add, Div},
@ -348,11 +352,23 @@ pub fn handle_forced_movement(
} }
} }
pub fn handle_orientation(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) { pub fn handle_orientation(
let dir = (is_strafing(data, update) || update.character.is_attack()) data: &JoinData<'_>,
.then(|| data.inputs.look_dir.to_horizontal().unwrap_or_default()) update: &mut StateUpdate,
.or_else(|| Dir::from_unnormalized(data.inputs.move_dir.into())) efficiency: f32,
.unwrap_or_else(|| data.ori.to_horizontal().look_dir()); dir_override: Option<Dir>,
) {
// Direction is set to the override if one is provided, else if entity is
// strafing or attacking the horiontal component of the look direction is used,
// else the current horizontal movement direction is used
let dir = if let Some(dir_override) = dir_override {
dir_override
} else if is_strafing(data, update) || update.character.is_attack() {
data.inputs.look_dir.to_horizontal().unwrap_or_default()
} else {
Dir::from_unnormalized(data.inputs.move_dir.into())
.unwrap_or_else(|| data.ori.to_horizontal().look_dir())
};
let rate = { let rate = {
let angle = update.ori.look_dir().angle_between(*dir); let angle = update.ori.look_dir().angle_between(*dir);
data.body.base_ori_rate() * efficiency * std::f32::consts::PI / angle data.body.base_ori_rate() * efficiency * std::f32::consts::PI / angle
@ -421,7 +437,7 @@ pub fn fly_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32)
let thrust = efficiency * force; let thrust = efficiency * force;
let accel = thrust / data.mass.0; let accel = thrust / data.mass.0;
handle_orientation(data, update, efficiency); handle_orientation(data, update, efficiency, None);
// Elevation control // Elevation control
match data.body { match data.body {
@ -584,44 +600,145 @@ pub fn handle_manipulate_loadout(
update: &mut StateUpdate, update: &mut StateUpdate,
inv_action: InventoryAction, inv_action: InventoryAction,
) { ) {
use use_item::ItemUseKind; match inv_action {
if let InventoryAction::Use(Slot::Inventory(inv_slot)) = inv_action { InventoryAction::Use(Slot::Inventory(inv_slot)) => {
// If inventory action is using a slot, and slot is in the inventory // 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 // 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 // the loadout will have effects that are desired to be non-instantaneous
if let Some((item_kind, item)) = data use use_item::ItemUseKind;
.inventory if let Some((item_kind, item)) = data
.and_then(|inv| inv.get(inv_slot)) .inventory
.and_then(|item| Option::<ItemUseKind>::from(item.kind()).zip(Some(item))) .and_then(|inv| inv.get(inv_slot))
{ .and_then(|item| Option::<ItemUseKind>::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 let (buildup_duration, use_duration, recover_duration) = item_kind.durations();
update.character = CharacterState::UseItem(use_item::Data { // If item returns a valid kind for item use, do into use item character state
static_data: use_item::StaticData { update.character = CharacterState::UseItem(use_item::Data {
buildup_duration, static_data: use_item::StaticData {
use_duration, buildup_duration,
recover_duration, use_duration,
inv_slot, recover_duration,
item_kind, inv_slot,
item_definition_id: item.item_definition_id().to_string(), item_kind,
was_wielded: matches!(data.character, CharacterState::Wielding), item_definition_id: item.item_definition_id().to_string(),
was_sneak: matches!(data.character, CharacterState::Sneak), was_wielded: matches!(data.character, CharacterState::Wielding),
}, was_sneak: matches!(data.character, CharacterState::Sneak),
timer: Duration::default(), },
stage_section: StageSection::Buildup, timer: Duration::default(),
}); stage_section: StageSection::Buildup,
} else { });
// Else emit inventory action instantnaneously } else {
// Else emit inventory action instantnaneously
update
.server_events
.push_front(ServerEvent::InventoryManip(data.entity, inv_action.into()));
}
},
InventoryAction::Collect(sprite_pos) => {
let sprite_pos_f32 = sprite_pos.map(|x| x as f32);
// CLosure to check if distance between a point and the sprite is less than
// MAX_PICKUP_RANGE and the radius of the body
let sprite_range_check = |pos: Vec3<f32>| {
(sprite_pos_f32 - pos).magnitude_squared()
< (MAX_PICKUP_RANGE + data.body.radius()).powi(2)
};
// Checks if player's feet or head is near to sprite
let close_to_sprite = sprite_range_check(data.pos.0)
|| sprite_range_check(data.pos.0 + Vec3::new(0.0, 0.0, data.body.height()));
if close_to_sprite {
// First, get sprite data for position, if there is a sprite
use sprite_interact::SpriteInteractKind;
let sprite_at_pos = data
.terrain
.get(sprite_pos)
.ok()
.copied()
.and_then(|b| b.get_sprite());
// Checks if position has a collectible sprite as well as what sprite is at the
// position
let sprite_interact = sprite_at_pos.and_then(Option::<SpriteInteractKind>::from);
if let Some(sprite_interact) = sprite_interact {
// Do a check that a path can be found between sprite and entity
// interacting with sprite Use manhattan distance * 1.5 for number
// of iterations
let iters =
(3.0 * (sprite_pos_f32 - data.pos.0).map(|x| x.abs()).sum()) as usize;
// Heuristic compares manhattan distance of start and end pos
let heuristic =
move |pos: &Vec3<i32>| (sprite_pos - pos).map(|x| x.abs()).sum() as f32;
let mut astar = Astar::new(
iters,
data.pos.0.map(|x| x.floor() as i32),
heuristic,
BuildHasherDefault::<FxHasher64>::default(),
);
// Neighbors are all neighboring blocks that are air
let neighbors = |pos: &Vec3<i32>| {
const DIRS: [Vec3<i32>; 6] = [
Vec3::new(1, 0, 0),
Vec3::new(-1, 0, 0),
Vec3::new(0, 1, 0),
Vec3::new(0, -1, 0),
Vec3::new(0, 0, 1),
Vec3::new(0, 0, -1),
];
let pos = *pos;
DIRS.iter().map(move |dir| dir + pos).filter(|pos| {
data.terrain
.get(*pos)
.ok()
.map_or(false, |block| !block.is_filled())
})
};
// Transition uses manhattan distance as the cost, which is always 1 since we
// only ever step one block at a time
let transition = |_: &Vec3<i32>, _: &Vec3<i32>| 1.0;
// Pathing satisfied when it reaches the sprite position
let satisfied = |pos: &Vec3<i32>| *pos == sprite_pos;
let not_blocked_by_terrain = astar
.poll(iters, heuristic, neighbors, transition, satisfied)
.into_path()
.is_some();
// If path can be found between entity interacting with sprite and entity, start
// interaction with sprite
if not_blocked_by_terrain {
// 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,
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 just do event instantaneously
update update
.server_events .server_events
.push_front(ServerEvent::InventoryManip(data.entity, inv_action.into())); .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()));
} }
} }

View File

@ -13,7 +13,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0); handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, 1.0); handle_move(data, &mut update, 1.0);
handle_climb(data, &mut update); handle_climb(data, &mut update);
attempt_input(data, &mut update); attempt_input(data, &mut update);

View File

@ -134,18 +134,6 @@ impl<'a> System<'a> for Sys {
let was_wielded = char_state.is_wield(); let was_wielded = char_state.is_wield();
let poise_state = poise.poise_state(); let poise_state = poise.poise_state();
let pos = pos.0; let pos = pos.0;
// Remove potion/saturation buff if knocked into poise state
if !matches!(poise_state, PoiseState::Normal) {
use comp::buff::{BuffChange, BuffKind};
server_emitter.emit(ServerEvent::Buff {
entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Potion),
});
server_emitter.emit(ServerEvent::Buff {
entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Saturation),
});
}
match poise_state { match poise_state {
PoiseState::Normal => {}, PoiseState::Normal => {},
PoiseState::Interrupted => { PoiseState::Interrupted => {

View File

@ -261,7 +261,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::Climb { .. } | CharacterState::Climb { .. }
| CharacterState::Stunned { .. } | CharacterState::Stunned { .. }
| CharacterState::BasicBlock { .. } | CharacterState::BasicBlock { .. }
| CharacterState::UseItem { .. } => {}, | CharacterState::UseItem { .. }
| CharacterState::SpriteInteract { .. } => {},
} }
} }

View File

@ -1230,3 +1230,30 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r
} }
} }
} }
/// Intended to handle things that should happen for any successful attack,
/// regardless of the damages and effects specific to that attack
pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
let ecs = &server.state.ecs();
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
if let Some(mut char_state) = ecs.write_storage::<CharacterState>().get_mut(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;
}
}
// Remove potion/saturation buff if attacked
server_eventbus.emit_now(ServerEvent::Buff {
entity,
buff_change: buff::BuffChange::RemoveByKind(buff::BuffKind::Potion),
});
server_eventbus.emit_now(ServerEvent::Buff {
entity,
buff_change: buff::BuffChange::RemoveByKind(buff::BuffKind::Saturation),
});
}

View File

@ -188,21 +188,6 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
if let Some(block) = block { if let Some(block) = block {
if block.is_collectible() && state.can_set_block(pos) { if block.is_collectible() && state.can_set_block(pos) {
// Check if the block is within pickup range
let entity_cylinder = get_cylinder(state, entity);
if !within_pickup_range(entity_cylinder, || {
Some(find_dist::Cube {
min: pos.as_(),
side_length: 1.0,
})
}) {
debug!(
?entity_cylinder,
"Failed to pick up block as not within range, block pos: {}", pos
);
return;
};
if let Some(item) = comp::Item::try_reclaim_from_block(block) { if let Some(item) = comp::Item::try_reclaim_from_block(block) {
// NOTE: We dup the item for message purposes. // NOTE: We dup the item for message purposes.
let item_msg = item.duplicate( let item_msg = item.duplicate(

View File

@ -7,8 +7,8 @@ use entity_creation::{
}; };
use entity_manipulation::{ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_combo_change, handle_damage, handle_delete, handle_aura, handle_bonk, handle_buff, handle_combo_change, handle_damage, handle_delete,
handle_destroy, handle_energy_change, handle_explosion, handle_knockback, handle_destroy, handle_energy_change, handle_entity_attacked_hook, handle_explosion,
handle_land_on_ground, handle_poise, handle_respawn, handle_teleport_to, handle_knockback, handle_land_on_ground, handle_poise, handle_respawn, handle_teleport_to,
}; };
use group_manip::handle_group; use group_manip::handle_group;
use information::handle_site_info; use information::handle_site_info;
@ -229,6 +229,9 @@ impl Server {
pet_entity, pet_entity,
owner_entity, owner_entity,
} => handle_tame_pet(self, pet_entity, owner_entity), } => handle_tame_pet(self, pet_entity, owner_entity),
ServerEvent::EntityAttackedHook { entity } => {
handle_entity_attacked_hook(self, entity)
},
} }
} }

View File

@ -0,0 +1,104 @@
use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use common::states::utils::StageSection;
use std::f32::consts::PI;
pub struct CollectAnimation;
impl Animation for CollectAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (Vec3<f32>, f32, Option<StageSection>, Vec3<f32>);
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"character_collect\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_collect")]
#[allow(clippy::single_match)] // TODO: Pending review in #587
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(position, _global_time, stage_section, sprite_pos): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
*rate = 1.0;
let mut next = (*skeleton).clone();
let (movement1, move2, move2alt, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0, 0.0),
Some(StageSection::Action) => (
1.0,
(anim_time * 12.0).sin(),
(anim_time * 9.0 + PI / 2.0).sin(),
0.0,
),
Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time.powi(4)),
_ => (0.0, 0.0, 0.0, 0.0),
};
let z_diff = (sprite_pos.z - position.z).round();
let z_diff = if z_diff > 0.0 { z_diff / 9.0 } else { 0.0 };
let squat = (1.0 - z_diff).powf(4.0);
let pullback = 1.0 - move3;
let move1 = movement1 * pullback * squat;
let move1_nosquat = movement1 * pullback;
let upshift = if squat < 0.35 {
move1_nosquat * 0.3
} else {
0.0
};
next.head.orientation = Quaternion::rotation_x(move1_nosquat * 0.2 + upshift * 1.3);
next.chest.position = Vec3::new(
0.0,
s_a.chest.0 + upshift * 3.0,
s_a.chest.1 + move2 * 0.15 + upshift * 3.0,
);
next.chest.orientation = Quaternion::rotation_x(move1 * -1.0 + move2alt * 0.015);
next.belt.position = Vec3::new(0.0, s_a.belt.0 + move1 * 1.0, s_a.belt.1 + move1 * -0.0);
next.belt.orientation = Quaternion::rotation_x(move1 * 0.2);
next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1);
next.shorts.position =
Vec3::new(0.0, s_a.shorts.0 + move1 * 2.0, s_a.shorts.1 + move1 * -0.0);
next.shorts.orientation = Quaternion::rotation_x(move1 * 0.3);
next.hand_l.position = Vec3::new(
-s_a.hand.0 + move1_nosquat * 4.0 - move2alt * 1.0,
s_a.hand.1 + move1_nosquat * 8.0 + move2 * 1.0 + upshift * -5.0,
s_a.hand.2 + move1_nosquat * 5.0 + upshift * 15.0,
);
next.hand_l.orientation = Quaternion::rotation_x(move1_nosquat * 1.9 + upshift * 2.0)
* Quaternion::rotation_y(move1_nosquat * -0.3 + move2alt * -0.2);
next.hand_r.position = Vec3::new(
s_a.hand.0 + move1_nosquat * -4.0 - move2 * 1.0,
s_a.hand.1 + move1_nosquat * 8.0 + move2alt * -1.0 + upshift * -5.0,
s_a.hand.2 + move1_nosquat * 5.0 + upshift * 15.0,
);
next.hand_r.orientation = Quaternion::rotation_x(move1_nosquat * 1.9 + upshift * 2.0)
* Quaternion::rotation_y(move1_nosquat * 0.3 + move2 * 0.3);
next.foot_l.position = Vec3::new(
-s_a.foot.0,
s_a.foot.1 + move1 * 2.0 + upshift * -3.5,
s_a.foot.2 + upshift * 2.0,
);
next.foot_l.orientation = Quaternion::rotation_x(move1 * -0.2 + upshift * -2.2);
next.foot_r.position = Vec3::new(
s_a.foot.0,
s_a.foot.1 + move1 * -4.0 + upshift * -0.5,
s_a.foot.2 + upshift * 2.0,
);
next.foot_r.orientation = Quaternion::rotation_x(move1 * -0.8 + upshift * -1.2);
next
}
}

View File

@ -4,6 +4,7 @@ pub mod beta;
pub mod block; pub mod block;
pub mod chargeswing; pub mod chargeswing;
pub mod climb; pub mod climb;
pub mod collect;
pub mod consume; pub mod consume;
pub mod dance; pub mod dance;
pub mod dash; pub mod dash;
@ -34,8 +35,8 @@ pub mod wield;
// Reexports // Reexports
pub use self::{ pub use self::{
alpha::AlphaAnimation, beam::BeamAnimation, beta::BetaAnimation, block::BlockAnimation, alpha::AlphaAnimation, beam::BeamAnimation, beta::BetaAnimation, block::BlockAnimation,
chargeswing::ChargeswingAnimation, climb::ClimbAnimation, consume::ConsumeAnimation, chargeswing::ChargeswingAnimation, climb::ClimbAnimation, collect::CollectAnimation,
dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation, consume::ConsumeAnimation, dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation,
glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation, glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation,
jump::JumpAnimation, leapmelee::LeapAnimation, mount::MountAnimation, jump::JumpAnimation, leapmelee::LeapAnimation, mount::MountAnimation,
repeater::RepeaterAnimation, roll::RollAnimation, run::RunAnimation, repeater::RepeaterAnimation, roll::RollAnimation, run::RunAnimation,

View File

@ -1133,6 +1133,32 @@ impl FigureMgr {
skeleton_attr, skeleton_attr,
) )
}, },
CharacterState::SpriteInteract(s) => {
let stage_time = s.timer.as_secs_f32();
let sprite_pos = s.static_data.sprite_pos;
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Action => s.timer.as_secs_f32(),
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::character::CollectAnimation::update_skeleton(
&target_base,
(
pos.0,
time,
Some(s.stage_section),
anim::vek::Vec3::from(sprite_pos.map(|x| x as f32)),
),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::Boost(_) => { CharacterState::Boost(_) => {
anim::character::AlphaAnimation::update_skeleton( anim::character::AlphaAnimation::update_skeleton(
&target_base, &target_base,