Finally Behavior isn't good enough as a component, Remove it from ECS and include it onto Agent directly

This commit is contained in:
Vincent Foulon 2021-04-07 21:37:48 +02:00
parent 45fb9f3211
commit dbee13f9be
14 changed files with 95 additions and 128 deletions

View File

@ -10,7 +10,7 @@ use specs_idvs::IdvStorage;
use std::collections::VecDeque;
use vek::*;
use super::dialogue::Subject;
use super::{dialogue::Subject, Behavior};
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
@ -210,6 +210,7 @@ pub struct Agent {
pub patrol_origin: Option<Vec3<f32>>,
pub target: Option<Target>,
pub chaser: Chaser,
pub behavior: Behavior,
pub psyche: Psyche,
pub inbox: VecDeque<AgentEvent>,
pub action_timer: f32,
@ -217,20 +218,26 @@ pub struct Agent {
}
impl Agent {
pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
pub fn set_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
self.patrol_origin = Some(origin);
self
}
pub fn with_destination(pos: Vec3<f32>) -> Self {
pub fn with_destination(behavior: Behavior, pos: Vec3<f32>) -> Self {
Self {
psyche: Psyche { aggro: 1.0 },
rtsim_controller: RtSimController::with_destination(pos),
behavior,
..Default::default()
}
}
pub fn new(patrol_origin: Option<Vec3<f32>>, body: &Body, no_flee: bool) -> Self {
pub fn new(
patrol_origin: Option<Vec3<f32>>,
body: &Body,
behavior: Behavior,
no_flee: bool,
) -> Self {
Agent {
patrol_origin,
psyche: if no_flee {
@ -238,6 +245,7 @@ impl Agent {
} else {
Psyche::from(body)
},
behavior,
..Default::default()
}
}

View File

@ -1,16 +1,13 @@
use specs::Component;
use specs_idvs::IdvStorage;
use crate::trade::SiteId;
bitflags! {
bitflags::bitflags! {
#[derive(Default)]
pub struct BehaviorCapability: u8 {
const SPEAK = 0b00000001;
const TRADE = 0b00000010;
}
}
bitflags! {
bitflags::bitflags! {
#[derive(Default)]
pub struct BehaviorState: u8 {
const TRADING = 0b00000001;
@ -66,10 +63,6 @@ impl Behavior {
pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) }
}
impl Component for Behavior {
type Storage = IdvStorage<Self>;
}
#[cfg(test)]
mod tests {
use super::{Behavior, BehaviorCapability, BehaviorState};

View File

@ -120,7 +120,6 @@ 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

@ -18,8 +18,6 @@
type_alias_impl_trait
)]
#[macro_use] extern crate bitflags;
/// Re-exported crates
pub use uuid;

View File

@ -104,8 +104,12 @@ impl CharacterBehavior for Data {
poise: comp::Poise::new(body),
loadout,
body,
agent: Some(comp::Agent::new(None, &body, true)),
behavior: Some(Behavior::from(BehaviorCapability::SPEAK)),
agent: Some(comp::Agent::new(
None,
&body,
Behavior::from(BehaviorCapability::SPEAK),
true,
)),
alignment: comp::Alignment::Owned(*data.uid),
scale: self
.static_data

View File

@ -198,7 +198,6 @@ impl State {
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Behavior>();
ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();

View File

@ -31,6 +31,7 @@ use common_net::{
sync::WorldSyncExt,
};
use common_sys::state::BuildAreas;
use comp::Behavior;
use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use std::{
@ -888,7 +889,7 @@ fn handle_spawn(
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
comp::Agent::default()
} else {
comp::Agent::default().with_patrol_origin(pos.0)
comp::Agent::default().set_patrol_origin(pos.0)
};
for _ in 0..amount {
@ -1074,8 +1075,10 @@ fn handle_spawn_airship(
animated: true,
});
if let Some(pos) = destination {
builder = builder.with(comp::Agent::with_destination(pos));
builder = builder.with(comp::Behavior::from(BehaviorCapability::SPEAK))
builder = builder.with(comp::Agent::with_destination(
Behavior::from(BehaviorCapability::SPEAK),
pos,
));
}
builder.build();

View File

@ -7,9 +7,8 @@ use common::{
beam,
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
inventory::loadout::Loadout,
shockwave, Agent, Alignment, Behavior, Body, Gravity, Health, HomeChunk, Inventory, Item,
ItemDrop, LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel,
WaypointArea,
shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop,
LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
},
outcome::Outcome,
rtsim::RtSimEntity,
@ -55,7 +54,6 @@ 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>,
@ -76,8 +74,6 @@ pub fn handle_create_npc(
entity
};
let entity = entity.with(behavior.unwrap_or_default());
let entity = if let Some(drop_item) = drop_item {
entity.with(ItemDrop(drop_item))
} else {

View File

@ -6,7 +6,7 @@ use common::{
agent::{Agent, AgentEvent},
group::GroupManager,
invite::{Invite, InviteKind, InviteResponse, PendingInvites},
Behavior, ChatType,
ChatType,
},
trade::Trades,
uid::Uid,
@ -167,7 +167,6 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut agents = state.ecs().write_storage::<Agent>();
let behaviors = state.ecs().read_storage::<Behavior>();
let mut invites = state.ecs().write_storage::<Invite>();
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
@ -224,13 +223,13 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
.inbox
.push_front(AgentEvent::TradeAccepted(invitee_uid));
}
let pricing = behaviors
let pricing = agents
.get(inviter)
.and_then(|b| b.trade_site.map(|id| index.get_site_prices(id)))
.and_then(|a| a.behavior.trade_site.map(|id| index.get_site_prices(id)))
.or_else(|| {
behaviors
.get(entity)
.and_then(|b| b.trade_site.map(|id| index.get_site_prices(id)))
agents.get(entity).and_then(|a| {
a.behavior.trade_site.map(|id| index.get_site_prices(id))
})
})
.flatten();
clients.get(inviter).map(|c| {

View File

@ -142,7 +142,6 @@ impl Server {
loadout,
body,
agent,
behavior,
alignment,
scale,
home_chunk,
@ -157,7 +156,6 @@ impl Server {
loadout,
body,
agent,
behavior,
alignment,
scale,
drop_item,

View File

@ -3,7 +3,6 @@ use common::{
comp::{
agent::{Agent, AgentEvent},
inventory::{item::MaterialStatManifest, Inventory},
Behavior,
},
trade::{PendingTrade, ReducedInventory, TradeAction, TradeId, TradeResult, Trades},
};
@ -29,13 +28,12 @@ fn notify_agent_simple(
fn notify_agent_prices(
mut agents: specs::WriteStorage<Agent>,
behaviors: specs::ReadStorage<Behavior>,
index: &IndexOwned,
entity: EcsEntity,
event: AgentEvent,
) {
if let (Some(agent), Some(behavior)) = (agents.get_mut(entity), behaviors.get(entity)) {
if let Some(site_id) = behavior.trade_site {
if let Some(agent) = agents.get_mut(entity) {
if let Some(site_id) = agent.behavior.trade_site {
let prices = index.get_site_prices(site_id);
if let AgentEvent::UpdatePendingTrade(boxval) = event {
// Box<(tid, pend, _, inventories)>) = event {
@ -108,7 +106,6 @@ pub fn handle_process_trade_action(
let mut inventories: [Option<ReducedInventory>; 2] = [None, None];
let mut prices = None;
let agents = server.state.ecs().read_storage::<Agent>();
let behaviors = server.state.ecs().read_storage::<Behavior>();
// sadly there is no map and collect on arrays
for i in 0..2 {
// parties.len()) {
@ -123,10 +120,12 @@ pub fn handle_process_trade_action(
// Get price info from the first Agent in the trade (currently, an
// Agent will never initiate a trade with another agent though)
prices = prices.or_else(|| {
behaviors
agents
.get(e)
.and_then(|b| {
b.trade_site.map(|id| server.index.get_site_prices(id))
.and_then(|a| {
a.behavior
.trade_site
.map(|id| server.index.get_site_prices(id))
})
.flatten()
});
@ -145,7 +144,6 @@ pub fn handle_process_trade_action(
);
notify_agent_prices(
server.state.ecs().write_storage::<Agent>(),
server.state.ecs().read_storage::<Behavior>(),
&server.index,
e,
AgentEvent::UpdatePendingTrade(Box::new((

View File

@ -103,9 +103,17 @@ 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, &body, false));
let behavior = matches!(body, comp::Body::Humanoid(_))
.then(|| Behavior::from(BehaviorCapability::SPEAK));
let agent = Some(comp::Agent::new(
None,
&body,
if matches!(body, comp::Body::Humanoid(_)) {
Behavior::from(BehaviorCapability::SPEAK)
} else {
Behavior::default()
},
false,
));
let rtsim_entity = Some(RtSimEntity(id));
let event = match body {
comp::Body::Ship(ship) => ServerEvent::CreateShip {
@ -126,7 +134,6 @@ 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

@ -14,9 +14,9 @@ use common::{
ItemDesc, ItemKind,
},
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, Behavior, BehaviorCapability, BehaviorState, Body, CharacterState,
ControlAction, ControlEvent, Controller, Energy, Health, InputKind, Inventory,
LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel,
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction,
ControlEvent, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState,
Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel,
},
event::{Emitter, EventBus, ServerEvent},
path::TraversalConfig,
@ -121,7 +121,6 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>,
WriteExpect<'a, RtSim>,
WriteStorage<'a, Behavior>,
);
const NAME: &'static str = "agent";
@ -131,7 +130,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn run(
job: &mut Job<Self>,
(read_data, event_bus, mut agents, mut controllers, mut rtsim, mut behaviors): Self::SystemData,
(read_data, event_bus, mut agents, mut controllers, mut rtsim): Self::SystemData,
) {
let rtsim = &mut *rtsim;
job.cpu_stats.measure(ParMode::Rayon);
@ -154,10 +153,9 @@ impl<'a> System<'a> for Sys {
read_data.groups.maybe(),
read_data.mount_states.maybe(),
&read_data.char_states,
&mut behaviors,
)
.par_join()
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _, _)| {
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _)| {
// Skip mounted entities
mount_state
.map(|ms| *ms == MountState::Unmounted)
@ -184,7 +182,6 @@ impl<'a> System<'a> for Sys {
groups,
_,
char_state,
behavior,
)| {
//// Hack, replace with better system when groups are more sophisticated
//// Override alignment if in a group unless entity is owned already
@ -318,7 +315,6 @@ impl<'a> System<'a> for Sys {
if hostile {
data.hostile_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -396,7 +392,6 @@ impl<'a> System<'a> for Sys {
} else {
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -406,7 +401,6 @@ impl<'a> System<'a> for Sys {
} else {
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -414,23 +408,11 @@ impl<'a> System<'a> for Sys {
}
} else {
agent.target = None;
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
);
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
}
} else {
agent.target = None;
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
);
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
}
} else {
// Target an entity that's attacking us if the attack was recent and we
@ -454,7 +436,6 @@ impl<'a> System<'a> for Sys {
agent.target = None;
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -495,7 +476,6 @@ impl<'a> System<'a> for Sys {
agent.target = None;
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -506,7 +486,6 @@ impl<'a> System<'a> for Sys {
agent.target = None;
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
@ -514,13 +493,7 @@ impl<'a> System<'a> for Sys {
}
},
_ => {
data.idle_tree(
agent,
behavior,
controller,
&read_data,
&mut event_emitter,
);
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
},
}
}
@ -554,7 +527,6 @@ impl<'a> AgentData<'a> {
fn idle_tree(
&self,
agent: &mut Agent,
behavior: &mut Behavior,
controller: &mut Controller,
read_data: &ReadData,
event_emitter: &mut Emitter<'_, ServerEvent>,
@ -577,13 +549,13 @@ impl<'a> AgentData<'a> {
}
if agent.action_timer > 0.0 {
if agent.action_timer
< (if behavior.is(BehaviorState::TRADING) {
< (if agent.behavior.is(BehaviorState::TRADING) {
TRADE_INTERACTION_TIME
} else {
DEFAULT_INTERACTION_TIME
})
{
self.interact(agent, behavior, controller, &read_data, event_emitter);
self.interact(agent, controller, &read_data, event_emitter);
} else {
agent.action_timer = 0.0;
agent.target = None;
@ -591,7 +563,7 @@ impl<'a> AgentData<'a> {
self.idle(agent, controller, &read_data);
}
} else if thread_rng().gen::<f32>() < 0.1 {
self.choose_target(agent, behavior, controller, &read_data, event_emitter);
self.choose_target(agent, controller, &read_data, event_emitter);
} else {
self.idle(agent, controller, &read_data);
}
@ -600,7 +572,6 @@ impl<'a> AgentData<'a> {
fn hostile_tree(
&self,
agent: &mut Agent,
behavior: &mut Behavior,
controller: &mut Controller,
read_data: &ReadData,
event_emitter: &mut Emitter<'_, ServerEvent>,
@ -615,7 +586,7 @@ impl<'a> AgentData<'a> {
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
// Should the agent flee?
if 1.0 - agent.psyche.aggro > self.damage && self.flees {
if agent.action_timer == 0.0 && behavior.can(BehaviorCapability::SPEAK) {
if agent.action_timer == 0.0 && agent.behavior.can(BehaviorCapability::SPEAK) {
let msg = "npc.speech.villager_under_attack".to_string();
event_emitter
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
@ -643,7 +614,7 @@ impl<'a> AgentData<'a> {
read_data.buffs.get(target),
) {
agent.target = None;
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
let msg = "npc.speech.villager_enemy_killed".to_string();
event_emitter
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
@ -654,7 +625,7 @@ impl<'a> AgentData<'a> {
// weapon, etc, into the decision to change
// target.
} else if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS {
self.choose_target(agent, behavior, controller, &read_data, event_emitter);
self.choose_target(agent, controller, &read_data, event_emitter);
} else if dist_sqrd < SIGHT_DIST.powi(2) {
self.attack(
agent,
@ -884,7 +855,6 @@ impl<'a> AgentData<'a> {
fn interact(
&self,
agent: &mut Agent,
behavior: &mut Behavior,
controller: &mut Controller,
read_data: &ReadData,
event_emitter: &mut Emitter<'_, ServerEvent>,
@ -907,7 +877,7 @@ impl<'a> AgentData<'a> {
let msg = agent.inbox.pop_back();
match msg {
Some(AgentEvent::Talk(by, subject)) => {
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
{
agent.target = Some(Target {
@ -960,7 +930,7 @@ impl<'a> AgentData<'a> {
event_emitter.emit(ServerEvent::Chat(
UnresolvedChatMsg::npc(*self.uid, msg),
));
} else if behavior.can(BehaviorCapability::TRADE) {
} else if agent.behavior.can(BehaviorCapability::TRADE) {
let msg = "npc.speech.merchant_advertisement".to_string();
event_emitter.emit(ServerEvent::Chat(
UnresolvedChatMsg::npc(*self.uid, msg),
@ -973,8 +943,8 @@ impl<'a> AgentData<'a> {
}
},
Subject::Trade => {
if behavior.can(BehaviorCapability::TRADE) {
if !behavior.is(BehaviorState::TRADING) {
if agent.behavior.can(BehaviorCapability::TRADE) {
if !agent.behavior.is(BehaviorState::TRADING) {
controller.events.push(ControlEvent::InitiateInvite(
by,
InviteKind::Trade,
@ -1122,8 +1092,8 @@ impl<'a> AgentData<'a> {
}
},
Some(AgentEvent::TradeInvite(with)) => {
if behavior.can(BehaviorCapability::TRADE) {
if !behavior.is(BehaviorState::TRADING) {
if agent.behavior.can(BehaviorCapability::TRADE) {
if !agent.behavior.is(BehaviorState::TRADING) {
// stand still and looking towards the trading player
controller.actions.push(ControlAction::Stand);
controller.actions.push(ControlAction::Talk);
@ -1139,13 +1109,13 @@ impl<'a> AgentData<'a> {
controller
.events
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
behavior.unset(BehaviorState::TRADING_ISSUER);
behavior.set(BehaviorState::TRADING);
agent.behavior.unset(BehaviorState::TRADING_ISSUER);
agent.behavior.set(BehaviorState::TRADING);
} else {
controller
.events
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
*self.uid,
"npc.speech.merchant_busy".to_string(),
@ -1157,7 +1127,7 @@ impl<'a> AgentData<'a> {
controller
.events
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
*self.uid,
"npc.speech.villager_decline_trade".to_string(),
@ -1166,7 +1136,7 @@ impl<'a> AgentData<'a> {
}
},
Some(AgentEvent::TradeAccepted(with)) => {
if !behavior.is(BehaviorState::TRADING) {
if !agent.behavior.is(BehaviorState::TRADING) {
if let Some(target) =
read_data.uid_allocator.retrieve_entity_internal(with.id())
{
@ -1176,12 +1146,12 @@ impl<'a> AgentData<'a> {
selected_at: read_data.time.0,
});
}
behavior.set(BehaviorState::TRADING);
behavior.set(BehaviorState::TRADING_ISSUER);
agent.behavior.set(BehaviorState::TRADING);
agent.behavior.set(BehaviorState::TRADING_ISSUER);
}
},
Some(AgentEvent::FinishedTrade(result)) => {
if behavior.is(BehaviorState::TRADING) {
if agent.behavior.is(BehaviorState::TRADING) {
match result {
TradeResult::Completed => {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
@ -1194,13 +1164,13 @@ impl<'a> AgentData<'a> {
"npc.speech.merchant_trade_declined".to_string(),
))),
}
behavior.unset(BehaviorState::TRADING);
agent.behavior.unset(BehaviorState::TRADING);
}
},
Some(AgentEvent::UpdatePendingTrade(boxval)) => {
let (tradeid, pending, prices, inventories) = *boxval;
if behavior.is(BehaviorState::TRADING) {
let who: usize = if behavior.is(BehaviorState::TRADING_ISSUER) {
if agent.behavior.is(BehaviorState::TRADING) {
let who: usize = if agent.behavior.is(BehaviorState::TRADING_ISSUER) {
0
} else {
1
@ -1234,7 +1204,7 @@ impl<'a> AgentData<'a> {
}
if pending.phase != TradePhase::Mutate {
// we got into the review phase but without balanced goods, decline
behavior.unset(BehaviorState::TRADING);
agent.behavior.unset(BehaviorState::TRADING);
event_emitter.emit(ServerEvent::ProcessTradeAction(
*self.entity,
tradeid,
@ -1245,7 +1215,7 @@ impl<'a> AgentData<'a> {
}
},
None => {
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
// no new events, continue looking towards the last interacting player for some
// time
if let Some(Target { target, .. }) = &agent.target {
@ -1321,7 +1291,6 @@ impl<'a> AgentData<'a> {
fn choose_target(
&self,
agent: &mut Agent,
behavior: &mut Behavior,
controller: &mut Controller,
read_data: &ReadData,
event_emitter: &mut Emitter<'_, ServerEvent>,
@ -1369,7 +1338,7 @@ impl<'a> AgentData<'a> {
(
self.alignment.map_or(false, |alignment| {
if matches!(alignment, Alignment::Npc) && e_inventory.equipped_items().filter(|item| item.tags().contains(&ItemTag::Cultist)).count() > 2 {
if behavior.can(BehaviorCapability::SPEAK) {
if agent.behavior.can(BehaviorCapability::SPEAK) {
if self.rtsim_entity.is_some() {
agent.rtsim_controller.events.push(
RtSimEvent::AddMemory(Memory {

View File

@ -193,18 +193,6 @@ impl<'a> System<'a> for Sys {
poise,
loadout,
agent: if entity.has_agency {
Some(comp::Agent::new(
Some(entity.pos),
&body,
matches!(
loadout_config,
Some(comp::inventory::loadout_builder::LoadoutConfig::Guard)
),
))
} else {
None
},
behavior: if entity.has_agency {
let mut behavior = Behavior::default();
if can_speak {
behavior.allow(BehaviorCapability::SPEAK);
@ -213,7 +201,15 @@ impl<'a> System<'a> for Sys {
behavior.allow(BehaviorCapability::TRADE);
behavior.trade_site = trade_for_site
}
Some(behavior)
Some(comp::Agent::new(
Some(entity.pos),
&body,
behavior,
matches!(
loadout_config,
Some(comp::inventory::loadout_builder::LoadoutConfig::Guard)
),
))
} else {
None
},