Create Behavior component

This commit is contained in:
Vincent Foulon 2021-03-29 20:24:39 +02:00 committed by Marcel Märtens
parent 60efd682e2
commit 41314e9098
11 changed files with 153 additions and 23 deletions

View File

@ -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<SiteId>,
pub trading: bool,
pub trading_issuer: bool,
pub psyche: Psyche,
pub inbox: VecDeque<AgentEvent>,
pub action_timer: f32,
@ -230,7 +227,6 @@ impl Agent {
pub fn with_destination(pos: Vec3<f32>) -> 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<Vec3<f32>>,
can_speak: bool,
trade_for_site: Option<SiteId>,
body: &Body,
no_flee: bool,
) -> Self {
Agent {
patrol_origin,
can_speak,
trade_for_site,
psyche: if no_flee {
Psyche { aggro: 1.0 }

View File

@ -0,0 +1,73 @@
use specs::Component;
use specs_idvs::IdvStorage;
use std::mem;
/// Behavior Component
#[derive(Clone, Debug)]
pub struct Behavior {
tags: Vec<BehaviorTag>,
}
/// 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<Self>;
}

View File

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

View File

@ -120,6 +120,7 @@ pub enum ServerEvent {
loadout: comp::inventory::loadout::Loadout,
body: comp::Body,
agent: Option<comp::Agent>,
behavior: Option<comp::Behavior>,
alignment: comp::Alignment,
scale: comp::Scale,
home_chunk: Option<comp::HomeChunk>,

View File

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

View File

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

View File

@ -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<Option<Agent>>,
behavior: Option<Behavior>,
alignment: Alignment,
scale: Scale,
drop_item: Option<Item>,
@ -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))

View File

@ -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<Vec3<f32>> = {
let positions = state.ecs().read_storage::<comp::Pos>();
let agents = state.ecs().read_storage::<comp::Agent>();
(&agents, &positions)
.join()
.filter_map(|(a, p)| a.trade_for_site.map(|_| p.0))
.collect()
};
if let Some(agent) = state
.ecs()
.write_storage::<comp::Agent>()
.get_mut(npc_entity)
{
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
if let Some(mypos) = state.ecs().read_storage::<comp::Pos>().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();

View File

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

View File

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

View File

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