From 41314e90982377a86e9a6c1c194fe9ecbec4d002 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Mon, 29 Mar 2021 20:24:39 +0200 Subject: [PATCH] Create Behavior component --- common/src/comp/agent.rs | 6 --- common/src/comp/behavior.rs | 73 ++++++++++++++++++++++++++++ common/src/comp/mod.rs | 2 + common/src/event.rs | 1 + common/src/states/basic_summon.rs | 5 +- server/src/cmd.rs | 3 +- server/src/events/entity_creation.rs | 11 ++++- server/src/events/interaction.rs | 51 +++++++++++++++++-- server/src/events/mod.rs | 2 + server/src/rtsim/tick.rs | 16 +++--- server/src/sys/terrain.rs | 6 ++- 11 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 common/src/comp/behavior.rs diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index c29f412a19..9de3e110f5 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -212,10 +212,7 @@ pub struct Agent { pub chaser: Chaser, /// Does the agent talk when e.g. hit by the player // TODO move speech patterns into a Behavior component - pub can_speak: bool, pub trade_for_site: Option, - pub trading: bool, - pub trading_issuer: bool, pub psyche: Psyche, pub inbox: VecDeque, pub action_timer: f32, @@ -230,7 +227,6 @@ impl Agent { pub fn with_destination(pos: Vec3) -> Self { Self { - can_speak: true, psyche: Psyche { aggro: 1.0 }, rtsim_controller: RtSimController::with_destination(pos), ..Default::default() @@ -239,14 +235,12 @@ impl Agent { pub fn new( patrol_origin: Option>, - can_speak: bool, trade_for_site: Option, body: &Body, no_flee: bool, ) -> Self { Agent { patrol_origin, - can_speak, trade_for_site, psyche: if no_flee { Psyche { aggro: 1.0 } diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs new file mode 100644 index 0000000000..4e4a7f9bd0 --- /dev/null +++ b/common/src/comp/behavior.rs @@ -0,0 +1,73 @@ +use specs::Component; +use specs_idvs::IdvStorage; +use std::mem; + +/// Behavior Component +#[derive(Clone, Debug)] +pub struct Behavior { + tags: Vec, +} + +/// Versatile tags attached to behaviors +#[derive(PartialEq, Clone, Debug)] +pub enum BehaviorTag { + /// The entity is allowed to speak + CanSpeak, + /// The entity is able to trade + CanTrade, + /// The entity has issued a trade + TradingIssuer, +} + +impl Behavior { + pub fn new(can_speak: bool, can_trade: bool) -> Self { + let mut behavior = Self::default(); + if can_speak { + behavior.add_tag(BehaviorTag::CanSpeak); + } + if can_trade { + behavior.add_tag(BehaviorTag::CanTrade); + } + behavior + } + + /// Apply a tag to the Behavior + pub fn add_tag(&mut self, tag: BehaviorTag) { + if !self.has_tag(&tag) { + self.tags.push(tag); + } + } + + /// Revoke a tag to the Behavior + pub fn remove_tag(&mut self, tag: BehaviorTag) { + if self.has_tag(&tag) { + while let Some(position) = self + .tags + .iter() + .position(|behavior_tag| behavior_tag == &tag) + { + self.tags.remove(position); + } + } + } + + /// Check if the Behavior possess a specific tag + pub fn has_tag(&self, tag: &BehaviorTag) -> bool { + self.tags.iter().any(|behavior_tag| behavior_tag == tag) + } + + /// Get a specific tag by variant + pub fn get_tag(&self, tag: &BehaviorTag) -> Option<&BehaviorTag> { + self.tags + .iter() + .find(|behavior_tag| mem::discriminant(*behavior_tag) == mem::discriminant(tag)) + } +} + +impl Default for Behavior { + fn default() -> Self { Behavior { tags: vec![] } } +} + +impl Component for Behavior { + type Storage = IdvStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 879a4dcee4..1bdb3b63aa 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -3,6 +3,7 @@ #[cfg(not(target_arch = "wasm32"))] pub mod agent; #[cfg(not(target_arch = "wasm32"))] pub mod aura; #[cfg(not(target_arch = "wasm32"))] pub mod beam; +pub mod behavior; #[cfg(not(target_arch = "wasm32"))] pub mod body; pub mod buff; #[cfg(not(target_arch = "wasm32"))] @@ -48,6 +49,7 @@ pub use self::{ agent::{Agent, Alignment}, aura::{Aura, AuraChange, AuraKind, Auras}, beam::{Beam, BeamSegment}, + behavior::{Behavior, BehaviorTag}, body::{ biped_large, biped_small, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object, quadruped_low, quadruped_medium, quadruped_small, ship, theropod, diff --git a/common/src/event.rs b/common/src/event.rs index db2e7e1a40..5240643991 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -120,6 +120,7 @@ pub enum ServerEvent { loadout: comp::inventory::loadout::Loadout, body: comp::Body, agent: Option, + behavior: Option, alignment: comp::Alignment, scale: comp::Scale, home_chunk: Option, diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 17dd549fcf..7f2838e229 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -2,7 +2,7 @@ use crate::{ comp::{ self, inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig}, - CharacterState, StateUpdate, + Behavior, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, outcome::Outcome, @@ -104,7 +104,8 @@ impl CharacterBehavior for Data { poise: comp::Poise::new(body), loadout, body, - agent: Some(comp::Agent::new(None, false, None, &body, true)), + agent: Some(comp::Agent::new(None, None, &body, true)), + behavior: Some(Behavior::new(true, false)), alignment: comp::Alignment::Owned(*data.uid), scale: self .static_data diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9c3407132b..17119d471b 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1074,7 +1074,8 @@ fn handle_spawn_airship( animated: true, }); if let Some(pos) = destination { - builder = builder.with(comp::Agent::with_destination(pos)) + builder = builder.with(comp::Agent::with_destination(pos)); + builder = builder.with(comp::Behavior::new(true, false)) } builder.build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index ed9c515d32..f6315d8ba0 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -7,8 +7,9 @@ use common::{ beam, buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::loadout::Loadout, - shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop, - LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea, + shockwave, Agent, Alignment, Behavior, Body, Gravity, Health, HomeChunk, Inventory, Item, + ItemDrop, LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, + WaypointArea, }, outcome::Outcome, rtsim::RtSimEntity, @@ -54,6 +55,7 @@ pub fn handle_create_npc( loadout: Loadout, body: Body, agent: impl Into>, + behavior: Option, alignment: Alignment, scale: Scale, drop_item: Option, @@ -73,6 +75,11 @@ pub fn handle_create_npc( } else { entity }; + let entity = if let Some(behavior) = behavior { + entity.with(behavior) + } else { + entity.with(Behavior::default()) + }; let entity = if let Some(drop_item) = drop_item { entity.with(ItemDrop(drop_item)) diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 0f40ddd4dd..532b6da1aa 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -1,11 +1,17 @@ -use specs::{world::WorldExt, Builder, Entity as EcsEntity}; +use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join}; use tracing::error; use vek::*; use common::{ comp::{ - self, agent::AgentEvent, dialogue::Subject, inventory::slot::EquipSlot, item, slot::Slot, - tool::ToolKind, Inventory, Pos, + self, + agent::AgentEvent, + dialogue::{AskedPerson, Subject}, + inventory::slot::EquipSlot, + item, + slot::Slot, + tool::ToolKind, + Inventory, Pos, }, consts::MAX_MOUNT_RANGE, outcome::Outcome, @@ -77,6 +83,45 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en } } +pub fn handle_npc_find_merchant(server: &mut Server, interactor: EcsEntity, npc_entity: EcsEntity) { + let state = server.state_mut(); + let trader_positions: Vec> = { + let positions = state.ecs().read_storage::(); + let agents = state.ecs().read_storage::(); + (&agents, &positions) + .join() + .filter_map(|(a, p)| a.trade_for_site.map(|_| p.0)) + .collect() + }; + if let Some(agent) = state + .ecs() + .write_storage::() + .get_mut(npc_entity) + { + if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) { + if let Some(mypos) = state.ecs().read_storage::().get(interactor) { + let mut closest = std::f32::INFINITY; + let mut closest_pos = None; + for p in trader_positions.iter() { + let diff = p - mypos.0; + let dist = diff.magnitude(); + if dist < closest { + closest = dist; + closest_pos = Some(*p); + } + } + agent.inbox.push_front(AgentEvent::Talk( + interactor_uid, + Subject::Person(AskedPerson { + person_type: comp::dialogue::PersonType::Merchant, + origin: closest_pos, + }), + )); + } + } + } +} + pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) { let state = server.state_mut(); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 348b0043ca..0186674ebf 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -142,6 +142,7 @@ impl Server { loadout, body, agent, + behavior, alignment, scale, home_chunk, @@ -156,6 +157,7 @@ impl Server { loadout, body, agent, + behavior, alignment, scale, drop_item, diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index c68d7a99b3..35bad1bcae 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -2,7 +2,7 @@ use super::*; use common::{ - comp::{self, inventory::loadout_builder::LoadoutBuilder}, + comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior}, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, terrain::TerrainGrid, @@ -103,13 +103,12 @@ impl<'a> System<'a> for Sys { .map(|e| e as f32) + Vec3::new(0.5, 0.5, body.flying_height()); let pos = comp::Pos(spawn_pos); - let agent = Some(comp::Agent::new( - None, - matches!(body, comp::Body::Humanoid(_)), - None, - &body, - false, - )); + let agent = Some(comp::Agent::new(None, None, &body, false)); + let behavior = if matches!(body, comp::Body::Humanoid(_)) { + Some(Behavior::new(true, false)) + } else { + None + }; let rtsim_entity = Some(RtSimEntity(id)); let event = match body { comp::Body::Ship(ship) => ServerEvent::CreateShip { @@ -130,6 +129,7 @@ impl<'a> System<'a> for Sys { poise: comp::Poise::new(body), body, agent, + behavior, alignment: match body { comp::Body::Humanoid(_) => comp::Alignment::Npc, _ => comp::Alignment::Wild, diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 34dd4284c3..e060bacd0e 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -191,7 +191,6 @@ impl<'a> System<'a> for Sys { agent: if entity.has_agency { Some(comp::Agent::new( Some(entity.pos), - can_speak, trade_for_site, &body, matches!( @@ -202,6 +201,11 @@ impl<'a> System<'a> for Sys { } else { None }, + behavior: if entity.has_agency { + Some(comp::Behavior::new(can_speak, trade_for_site.is_some())) + } else { + None + }, body, alignment, scale: comp::Scale(scale),