From 41314e90982377a86e9a6c1c194fe9ecbec4d002 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Mon, 29 Mar 2021 20:24:39 +0200 Subject: [PATCH 01/14] 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), From 18694b30ad7abedab0ad749f685a4ad00b2f37d5 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Mon, 29 Mar 2021 22:30:09 +0200 Subject: [PATCH 02/14] Use Behavior into Agent --- common/src/comp/behavior.rs | 15 +++--- common/sys/src/state.rs | 1 + server/src/events/interaction.rs | 39 -------------- server/src/sys/agent.rs | 93 +++++++++++++++++--------------- 4 files changed, 58 insertions(+), 90 deletions(-) diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 4e4a7f9bd0..524edcfe3f 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -3,7 +3,7 @@ use specs_idvs::IdvStorage; use std::mem; /// Behavior Component -#[derive(Clone, Debug)] +#[derive(Default, Clone, Debug)] pub struct Behavior { tags: Vec, } @@ -15,8 +15,11 @@ pub enum BehaviorTag { CanSpeak, /// The entity is able to trade CanTrade, + + /// The entity is currently trading + IsTrading, /// The entity has issued a trade - TradingIssuer, + IsTradingIssuer, } impl Behavior { @@ -57,17 +60,13 @@ impl Behavior { } /// Get a specific tag by variant - pub fn get_tag(&self, tag: &BehaviorTag) -> Option<&BehaviorTag> { + pub fn get_tag(&self, tag: BehaviorTag) -> Option<&BehaviorTag> { self.tags .iter() - .find(|behavior_tag| mem::discriminant(*behavior_tag) == mem::discriminant(tag)) + .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/sys/src/state.rs b/common/sys/src/state.rs index ca31775284..0c0fe24bac 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -198,6 +198,7 @@ impl State { ecs.register::>(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 532b6da1aa..ce155e19b2 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -83,45 +83,6 @@ 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/sys/agent.rs b/server/src/sys/agent.rs index 966e7cb8fa..909096f27b 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -14,9 +14,9 @@ use common::{ ItemDesc, ItemKind, }, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, - Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy, - Health, InputKind, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, - Stats, UnresolvedChatMsg, Vel, + Agent, Alignment, Behavior, BehaviorTag, Body, CharacterState, ControlAction, ControlEvent, + Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState, Ori, + PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel, }, event::{Emitter, EventBus, ServerEvent}, path::TraversalConfig, @@ -65,6 +65,7 @@ struct AgentData<'a> { is_gliding: bool, health: Option<&'a Health>, char_state: &'a CharacterState, + behavior: &'a mut Behavior, } #[derive(SystemData)] @@ -121,6 +122,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, WriteExpect<'a, RtSim>, + WriteStorage<'a, Behavior>, ); const NAME: &'static str = "agent"; @@ -130,16 +132,18 @@ impl<'a> System<'a> for Sys { #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn run( job: &mut Job, - (read_data, event_bus, mut agents, mut controllers, mut rtsim): Self::SystemData, + (read_data, event_bus, mut agents, mut controllers, mut rtsim, mut behaviors): Self::SystemData, ) { let rtsim = &mut *rtsim; job.cpu_stats.measure(ParMode::Rayon); ( &read_data.entities, (&read_data.energies, read_data.healths.maybe()), - &read_data.positions, - &read_data.velocities, - &read_data.orientations, + ( + &read_data.positions, + &read_data.velocities, + &read_data.orientations, + ), read_data.bodies.maybe(), &read_data.inventories, &read_data.stats, @@ -151,16 +155,15 @@ 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, _)| { - // Skip mounted entities - mount_state - .map(|ms| *ms == MountState::Unmounted) - .unwrap_or(true) - }, - ) + .filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _, _)| { + // Skip mounted entities + mount_state + .map(|ms| *ms == MountState::Unmounted) + .unwrap_or(true) + }) .for_each_init( || { prof_span!(guard, "agent rayon job"); @@ -170,9 +173,7 @@ impl<'a> System<'a> for Sys { ( entity, (energy, health), - pos, - vel, - ori, + (pos, vel, ori), body, inventory, stats, @@ -184,6 +185,7 @@ 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 @@ -258,7 +260,7 @@ impl<'a> System<'a> for Sys { .and_then(|rtsim_ent| rtsim.get_entity(rtsim_ent.0)); // Package all this agent's data into a convenient struct - let data = AgentData { + let mut data = AgentData { entity: &entity, rtsim_entity, uid, @@ -280,6 +282,7 @@ impl<'a> System<'a> for Sys { is_gliding, health: read_data.healths.get(entity), char_state, + behavior, }; /////////////////////////////////////////////////////////// @@ -527,7 +530,7 @@ impl<'a> AgentData<'a> { // Subtrees //////////////////////////////////////// fn idle_tree( - &self, + &mut self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData, @@ -551,7 +554,7 @@ impl<'a> AgentData<'a> { } if agent.action_timer > 0.0 { if agent.action_timer - < (if agent.trading { + < (if self.behavior.has_tag(&BehaviorTag::IsTrading) { TRADE_INTERACTION_TIME } else { DEFAULT_INTERACTION_TIME @@ -588,7 +591,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 && agent.can_speak { + if agent.action_timer == 0.0 && self.behavior.has_tag(&BehaviorTag::CanSpeak) { let msg = "npc.speech.villager_under_attack".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -616,7 +619,7 @@ impl<'a> AgentData<'a> { read_data.buffs.get(target), ) { agent.target = None; - if agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { let msg = "npc.speech.villager_enemy_killed".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -855,7 +858,7 @@ impl<'a> AgentData<'a> { } fn interact( - &self, + &mut self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData, @@ -879,7 +882,7 @@ impl<'a> AgentData<'a> { let msg = agent.inbox.pop_back(); match msg { Some(AgentEvent::Talk(by, subject)) => { - if agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { agent.target = Some(Target { @@ -945,8 +948,8 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if agent.trade_for_site.is_some() { - if !agent.trading { + if self.behavior.has_tag(&BehaviorTag::CanTrade) { + if !self.behavior.has_tag(&BehaviorTag::IsTrading) { controller.events.push(ControlEvent::InitiateInvite( by, InviteKind::Trade, @@ -1094,8 +1097,8 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if agent.trade_for_site.is_some() { - if !agent.trading { + if self.behavior.has_tag(&BehaviorTag::CanTrade) { + if !self.behavior.has_tag(&BehaviorTag::IsTrading) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Talk); @@ -1111,13 +1114,13 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Accept)); - agent.trading_issuer = false; - agent.trading = true; + self.behavior.remove_tag(BehaviorTag::IsTradingIssuer); + self.behavior.add_tag(BehaviorTag::IsTrading); } else { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.merchant_busy".to_string(), @@ -1129,7 +1132,7 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.villager_decline_trade".to_string(), @@ -1138,7 +1141,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeAccepted(with)) => { - if !agent.trading { + if !self.behavior.has_tag(&BehaviorTag::IsTrading) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(with.id()) { @@ -1148,12 +1151,12 @@ impl<'a> AgentData<'a> { selected_at: read_data.time.0, }); } - agent.trading = true; - agent.trading_issuer = true; + self.behavior.add_tag(BehaviorTag::IsTrading); + self.behavior.add_tag(BehaviorTag::IsTradingIssuer); } }, Some(AgentEvent::FinishedTrade(result)) => { - if agent.trading { + if self.behavior.has_tag(&BehaviorTag::IsTrading) { match result { TradeResult::Completed => { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( @@ -1166,13 +1169,17 @@ impl<'a> AgentData<'a> { "npc.speech.merchant_trade_declined".to_string(), ))), } - agent.trading = false; + self.behavior.remove_tag(BehaviorTag::IsTrading); } }, Some(AgentEvent::UpdatePendingTrade(boxval)) => { let (tradeid, pending, prices, inventories) = *boxval; - if agent.trading { - let who: usize = if agent.trading_issuer { 0 } else { 1 }; + if self.behavior.has_tag(&BehaviorTag::IsTrading) { + let who: usize = if self.behavior.has_tag(&BehaviorTag::IsTradingIssuer) { + 0 + } else { + 1 + }; let balance0: f32 = prices.balance(&pending.offers, &inventories, 1 - who, true); let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false); @@ -1202,7 +1209,7 @@ impl<'a> AgentData<'a> { } if pending.phase != TradePhase::Mutate { // we got into the review phase but without balanced goods, decline - agent.trading = false; + self.behavior.remove_tag(BehaviorTag::IsTrading); event_emitter.emit(ServerEvent::ProcessTradeAction( *self.entity, tradeid, @@ -1213,7 +1220,7 @@ impl<'a> AgentData<'a> { } }, None => { - if agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { // no new events, continue looking towards the last interacting player for some // time if let Some(Target { target, .. }) = &agent.target { @@ -1336,7 +1343,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 agent.can_speak { + if self.behavior.has_tag(&BehaviorTag::CanSpeak) { if self.rtsim_entity.is_some() { agent.rtsim_controller.events.push( RtSimEvent::AddMemory(Memory { From a483817fc2e235d38bc941c02c05123d164fd2c4 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Mon, 29 Mar 2021 22:34:24 +0200 Subject: [PATCH 03/14] Change check for trading NPC --- server/src/sys/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 909096f27b..86554c1997 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -935,7 +935,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if agent.trade_for_site.is_some() { + } else if self.behavior.has_tag(&BehaviorTag::CanTrade) { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), From 51ef3547a136f803664cae27f605f0c4b3755553 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Mon, 29 Mar 2021 22:45:45 +0200 Subject: [PATCH 04/14] cleanup old code --- server/src/events/interaction.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index ce155e19b2..0f40ddd4dd 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -1,17 +1,11 @@ -use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join}; +use specs::{world::WorldExt, Builder, Entity as EcsEntity}; use tracing::error; use vek::*; use common::{ comp::{ - self, - agent::AgentEvent, - dialogue::{AskedPerson, Subject}, - inventory::slot::EquipSlot, - item, - slot::Slot, - tool::ToolKind, - Inventory, Pos, + self, agent::AgentEvent, dialogue::Subject, inventory::slot::EquipSlot, item, slot::Slot, + tool::ToolKind, Inventory, Pos, }, consts::MAX_MOUNT_RANGE, outcome::Outcome, From 211ab0289797a09f8b53654132c6216ef94af716 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 30 Mar 2021 12:55:50 +0200 Subject: [PATCH 05/14] Use Behavior::CanTrade instead of Agent::trade_for_site + addressed comments --- common/src/comp/agent.rs | 11 ++--------- common/src/comp/behavior.rs | 23 ++++++++++++++--------- common/src/states/basic_summon.rs | 6 +++--- server/src/cmd.rs | 4 ++-- server/src/events/invite.rs | 9 +++++---- server/src/events/trade.rs | 12 +++++++++--- server/src/rtsim/tick.rs | 6 +++--- server/src/sys/agent.rs | 6 +++--- server/src/sys/terrain.rs | 14 +++++++++++--- world/src/index.rs | 20 ++++++++++++-------- 10 files changed, 64 insertions(+), 47 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 9de3e110f5..c908e02adb 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -2,7 +2,7 @@ use crate::{ comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body}, path::Chaser, rtsim::RtSimController, - trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, + trade::{PendingTrade, ReducedInventory, SitePrices, TradeId, TradeResult}, uid::Uid, }; use specs::{Component, Entity as EcsEntity}; @@ -212,7 +212,6 @@ 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 trade_for_site: Option, pub psyche: Psyche, pub inbox: VecDeque, pub action_timer: f32, @@ -233,15 +232,9 @@ impl Agent { } } - pub fn new( - patrol_origin: Option>, - trade_for_site: Option, - body: &Body, - no_flee: bool, - ) -> Self { + pub fn new(patrol_origin: Option>, body: &Body, no_flee: bool) -> Self { Agent { patrol_origin, - trade_for_site, psyche: if no_flee { Psyche { aggro: 1.0 } } else { diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 524edcfe3f..9773181f2f 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -2,7 +2,13 @@ use specs::Component; use specs_idvs::IdvStorage; use std::mem; -/// Behavior Component +use crate::trade::SiteId; + +/// # Behavior Component +/// This component allow an Entity to register one or more behavior tags. +/// These tags act as flags of what an Entity can do, or what it is doing. +/// Behaviors Tags can be added and removed as the Entity lives, to update its +/// state when needed #[derive(Default, Clone, Debug)] pub struct Behavior { tags: Vec, @@ -14,7 +20,7 @@ pub enum BehaviorTag { /// The entity is allowed to speak CanSpeak, /// The entity is able to trade - CanTrade, + CanTrade(Option), /// The entity is currently trading IsTrading, @@ -23,13 +29,10 @@ pub enum BehaviorTag { } impl Behavior { - pub fn new(can_speak: bool, can_trade: bool) -> Self { + pub fn new(behavior_tags: &[BehaviorTag]) -> Self { let mut behavior = Self::default(); - if can_speak { - behavior.add_tag(BehaviorTag::CanSpeak); - } - if can_trade { - behavior.add_tag(BehaviorTag::CanTrade); + for tag in behavior_tags.iter() { + behavior.add_tag(tag.clone()) } behavior } @@ -56,7 +59,9 @@ impl Behavior { /// 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) + self.tags + .iter() + .any(|behavior_tag| mem::discriminant(behavior_tag) == mem::discriminant(tag)) } /// Get a specific tag by variant diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 7f2838e229..392b2a2242 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}, - Behavior, CharacterState, StateUpdate, + Behavior, BehaviorTag, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, outcome::Outcome, @@ -104,8 +104,8 @@ impl CharacterBehavior for Data { poise: comp::Poise::new(body), loadout, body, - agent: Some(comp::Agent::new(None, None, &body, true)), - behavior: Some(Behavior::new(true, false)), + agent: Some(comp::Agent::new(None, &body, true)), + behavior: Some(Behavior::new(&[BehaviorTag::CanSpeak])), alignment: comp::Alignment::Owned(*data.uid), scale: self .static_data diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 17119d471b..d0a733525c 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -15,7 +15,7 @@ use common::{ buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::item::MaterialStatManifest, invite::InviteKind, - ChatType, Inventory, Item, LightEmitter, WaypointArea, + BehaviorTag, ChatType, Inventory, Item, LightEmitter, WaypointArea, }, effect::Effect, event::{EventBus, ServerEvent}, @@ -1075,7 +1075,7 @@ fn handle_spawn_airship( }); if let Some(pos) = destination { builder = builder.with(comp::Agent::with_destination(pos)); - builder = builder.with(comp::Behavior::new(true, false)) + builder = builder.with(comp::Behavior::new(&[BehaviorTag::CanSpeak])) } builder.build(); diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs index dd859c910e..caac3d3adc 100644 --- a/server/src/events/invite.rs +++ b/server/src/events/invite.rs @@ -6,7 +6,7 @@ use common::{ agent::{Agent, AgentEvent}, group::GroupManager, invite::{Invite, InviteKind, InviteResponse, PendingInvites}, - ChatType, + Behavior, ChatType, }, trade::Trades, uid::Uid, @@ -167,6 +167,7 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) { let clients = state.ecs().read_storage::(); let uids = state.ecs().read_storage::(); let mut agents = state.ecs().write_storage::(); + let behaviors = state.ecs().read_storage::(); let mut invites = state.ecs().write_storage::(); if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| { let Invite { inviter, kind } = invite; @@ -223,10 +224,10 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) { .inbox .push_front(AgentEvent::TradeAccepted(invitee_uid)); } - let pricing = agents + let pricing = behaviors .get(inviter) - .and_then(|a| index.get_site_prices(a)) - .or_else(|| agents.get(entity).and_then(|a| index.get_site_prices(a))); + .and_then(|b| index.get_site_prices(b)) + .or_else(|| behaviors.get(entity).and_then(|b| index.get_site_prices(b))); clients.get(inviter).map(|c| { c.send(ServerGeneral::UpdatePendingTrade( id, diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index ae39974cc6..ade0e6e3e6 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -3,6 +3,7 @@ use common::{ comp::{ agent::{Agent, AgentEvent}, inventory::{item::MaterialStatManifest, Inventory}, + Behavior, }, trade::{PendingTrade, ReducedInventory, TradeAction, TradeId, TradeResult, Trades}, }; @@ -28,12 +29,13 @@ fn notify_agent_simple( fn notify_agent_prices( mut agents: specs::WriteStorage, + behaviors: specs::ReadStorage, index: &IndexOwned, entity: EcsEntity, event: AgentEvent, ) { - if let Some(agent) = agents.get_mut(entity) { - let prices = index.get_site_prices(agent); + if let (Some(agent), Some(behavior)) = (agents.get_mut(entity), behaviors.get(entity)) { + let prices = index.get_site_prices(behavior); if let AgentEvent::UpdatePendingTrade(boxval) = event { // Box<(tid, pend, _, inventories)>) = event { agent @@ -104,6 +106,7 @@ pub fn handle_process_trade_action( let mut inventories: [Option; 2] = [None, None]; let mut prices = None; let agents = server.state.ecs().read_storage::(); + let behaviors = server.state.ecs().read_storage::(); // sadly there is no map and collect on arrays for i in 0..2 { // parties.len()) { @@ -118,7 +121,9 @@ 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(|| { - agents.get(e).and_then(|a| server.index.get_site_prices(a)) + behaviors + .get(e) + .and_then(|b| server.index.get_site_prices(b)) }); } } @@ -135,6 +140,7 @@ pub fn handle_process_trade_action( ); notify_agent_prices( server.state.ecs().write_storage::(), + server.state.ecs().read_storage::(), &server.index, e, AgentEvent::UpdatePendingTrade(Box::new(( diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 35bad1bcae..3c8895e2fa 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, Behavior}, + comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior, BehaviorTag}, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, terrain::TerrainGrid, @@ -103,9 +103,9 @@ 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, None, &body, false)); + let agent = Some(comp::Agent::new(None, &body, false)); let behavior = if matches!(body, comp::Body::Humanoid(_)) { - Some(Behavior::new(true, false)) + Some(Behavior::new(&[BehaviorTag::CanSpeak])) } else { None }; diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 86554c1997..0aa03c3b87 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -935,7 +935,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if self.behavior.has_tag(&BehaviorTag::CanTrade) { + } else if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), @@ -948,7 +948,7 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if self.behavior.has_tag(&BehaviorTag::CanTrade) { + if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { if !self.behavior.has_tag(&BehaviorTag::IsTrading) { controller.events.push(ControlEvent::InitiateInvite( by, @@ -1097,7 +1097,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if self.behavior.has_tag(&BehaviorTag::CanTrade) { + if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { if !self.behavior.has_tag(&BehaviorTag::IsTrading) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index e060bacd0e..9bee6fd9e8 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -2,7 +2,9 @@ use crate::{ chunk_generator::ChunkGenerator, client::Client, presence::Presence, rtsim::RtSim, Tick, }; use common::{ - comp::{self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, Pos}, + comp::{ + self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, BehaviorTag, Pos, + }, event::{EventBus, ServerEvent}, generation::get_npc_name, npc::NPC_NAMES, @@ -191,7 +193,6 @@ impl<'a> System<'a> for Sys { agent: if entity.has_agency { Some(comp::Agent::new( Some(entity.pos), - trade_for_site, &body, matches!( loadout_config, @@ -202,7 +203,14 @@ impl<'a> System<'a> for Sys { None }, behavior: if entity.has_agency { - Some(comp::Behavior::new(can_speak, trade_for_site.is_some())) + let mut behavior_tags = vec![]; + if can_speak { + behavior_tags.push(BehaviorTag::CanSpeak); + } + if trade_for_site.is_some() { + behavior_tags.push(BehaviorTag::CanTrade(trade_for_site)); + } + Some(comp::Behavior::new(&behavior_tags)) } else { None }, diff --git a/world/src/index.rs b/world/src/index.rs index feaef98cf0..ec5bc51e9f 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -4,7 +4,7 @@ use crate::{ }; use common::{ assets::{AssetExt, AssetHandle}, - comp::Agent, + comp::{Behavior, BehaviorTag}, store::Store, trade::SitePrices, }; @@ -72,13 +72,17 @@ impl Index { pub fn colors(&self) -> AssetHandle> { self.colors } - pub fn get_site_prices(&self, agent: &Agent) -> Option { - agent - .trade_for_site - .map(|i| self.sites.recreate_id(i)) - .flatten() - .map(|i| self.sites.get(i)) - .map(|s| s.economy.get_site_prices()) + pub fn get_site_prices(&self, behavior: &Behavior) -> Option { + if let Some(BehaviorTag::CanTrade(site_id)) = behavior.get_tag(BehaviorTag::CanTrade(None)) + { + site_id + .map(|i| self.sites.recreate_id(i)) + .flatten() + .map(|i| self.sites.get(i)) + .map(|s| s.economy.get_site_prices()) + } else { + None + } } } From b1718cf578a6f0bad89fd4c901b88857d8326024 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 30 Mar 2021 18:33:01 +0200 Subject: [PATCH 06/14] Use HashSet instead of Vec --- common/src/comp/behavior.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 9773181f2f..769fd2b9c1 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -1,6 +1,6 @@ use specs::Component; use specs_idvs::IdvStorage; -use std::mem; +use std::{collections::HashSet, mem}; use crate::trade::SiteId; @@ -11,11 +11,11 @@ use crate::trade::SiteId; /// state when needed #[derive(Default, Clone, Debug)] pub struct Behavior { - tags: Vec, + tags: HashSet, } /// Versatile tags attached to behaviors -#[derive(PartialEq, Clone, Debug)] +#[derive(Hash, Eq, PartialEq, Clone, Debug)] pub enum BehaviorTag { /// The entity is allowed to speak CanSpeak, @@ -40,20 +40,14 @@ impl Behavior { /// Apply a tag to the Behavior pub fn add_tag(&mut self, tag: BehaviorTag) { if !self.has_tag(&tag) { - self.tags.push(tag); + self.tags.insert(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); - } + self.tags.remove(&tag); } } From 114715908aebab4fae1eec2046ace74a95afe093 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 30 Mar 2021 19:01:38 +0200 Subject: [PATCH 07/14] fix tag removing --- common/src/comp/behavior.rs | 9 ++++++--- world/src/index.rs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 769fd2b9c1..3b2e0bdf0b 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -47,7 +47,10 @@ impl Behavior { /// Revoke a tag to the Behavior pub fn remove_tag(&mut self, tag: BehaviorTag) { if self.has_tag(&tag) { - self.tags.remove(&tag); + let tag = self.get_tag(&tag).cloned(); + if let Some(tag) = tag { + self.tags.remove(&tag); + } } } @@ -59,10 +62,10 @@ impl Behavior { } /// Get a specific tag by variant - pub fn get_tag(&self, tag: BehaviorTag) -> Option<&BehaviorTag> { + pub fn get_tag(&self, tag: &BehaviorTag) -> Option<&BehaviorTag> { self.tags .iter() - .find(|behavior_tag| mem::discriminant(*behavior_tag) == mem::discriminant(&tag)) + .find(|behavior_tag| mem::discriminant(*behavior_tag) == mem::discriminant(tag)) } } diff --git a/world/src/index.rs b/world/src/index.rs index ec5bc51e9f..390a0d293e 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -73,7 +73,7 @@ impl Index { pub fn colors(&self) -> AssetHandle> { self.colors } pub fn get_site_prices(&self, behavior: &Behavior) -> Option { - if let Some(BehaviorTag::CanTrade(site_id)) = behavior.get_tag(BehaviorTag::CanTrade(None)) + if let Some(BehaviorTag::CanTrade(site_id)) = behavior.get_tag(&BehaviorTag::CanTrade(None)) { site_id .map(|i| self.sites.recreate_id(i)) From a0157ac5f524341c59d6aa6b7e9443857105877d Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 6 Apr 2021 19:18:40 +0200 Subject: [PATCH 08/14] use bitflags instead of HashSet --- Cargo.lock | 1 + common/Cargo.toml | 1 + common/src/comp/behavior.rs | 74 +++++++++------------------- common/src/comp/mod.rs | 2 +- common/src/lib.rs | 2 + common/src/states/basic_summon.rs | 4 +- server/src/cmd.rs | 4 +- server/src/events/entity_creation.rs | 7 +-- server/src/events/invite.rs | 8 ++- server/src/events/trade.rs | 4 +- server/src/rtsim/tick.rs | 9 ++-- server/src/sys/agent.rs | 52 +++++++++---------- server/src/sys/terrain.rs | 12 +++-- world/src/index.rs | 20 +++----- 14 files changed, 85 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aea01c2083..9161f94c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5377,6 +5377,7 @@ dependencies = [ "approx 0.4.0", "arraygen", "assets_manager", + "bitflags", "criterion", "crossbeam-channel", "crossbeam-utils 0.8.3", diff --git a/common/Cargo.toml b/common/Cargo.toml index bca6817839..7e770dd2f7 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -24,6 +24,7 @@ serde = { version = "1.0.110", features = ["derive", "rc"] } approx = "0.4.0" arraygen = "0.1.13" crossbeam-utils = "0.8.1" +bitflags = "1.2" crossbeam-channel = "0.5" enum-iterator = "0.6" lazy_static = "1.4.0" diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 3b2e0bdf0b..305c9aba3b 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -1,72 +1,44 @@ use specs::Component; use specs_idvs::IdvStorage; -use std::{collections::HashSet, mem}; use crate::trade::SiteId; +bitflags! { + #[derive(Default)] + pub struct BehaviorFlag: u8 { + const CAN_SPEAK = 0b00000001; + const CAN_TRADE = 0b00000010; + const IS_TRADING = 0b00000100; + const IS_TRADING_ISSUER = 0b00001000; + } +} + /// # Behavior Component /// This component allow an Entity to register one or more behavior tags. /// These tags act as flags of what an Entity can do, or what it is doing. /// Behaviors Tags can be added and removed as the Entity lives, to update its /// state when needed -#[derive(Default, Clone, Debug)] +#[derive(Default, Copy, Clone, Debug)] pub struct Behavior { - tags: HashSet, + pub flags: BehaviorFlag, + pub trade_site: Option, } -/// Versatile tags attached to behaviors -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -pub enum BehaviorTag { - /// The entity is allowed to speak - CanSpeak, - /// The entity is able to trade - CanTrade(Option), - - /// The entity is currently trading - IsTrading, - /// The entity has issued a trade - IsTradingIssuer, +impl From for Behavior { + fn from(flags: BehaviorFlag) -> Self { + Behavior { + flags, + trade_site: None, + } + } } impl Behavior { - pub fn new(behavior_tags: &[BehaviorTag]) -> Self { - let mut behavior = Self::default(); - for tag in behavior_tags.iter() { - behavior.add_tag(tag.clone()) - } - behavior - } + pub fn set(&mut self, flags: BehaviorFlag) { self.flags.set(flags, true) } - /// Apply a tag to the Behavior - pub fn add_tag(&mut self, tag: BehaviorTag) { - if !self.has_tag(&tag) { - self.tags.insert(tag); - } - } + pub fn unset(&mut self, flags: BehaviorFlag) { self.flags.set(flags, false) } - /// Revoke a tag to the Behavior - pub fn remove_tag(&mut self, tag: BehaviorTag) { - if self.has_tag(&tag) { - let tag = self.get_tag(&tag).cloned(); - if let Some(tag) = tag { - self.tags.remove(&tag); - } - } - } - - /// Check if the Behavior possess a specific tag - pub fn has_tag(&self, tag: &BehaviorTag) -> bool { - self.tags - .iter() - .any(|behavior_tag| mem::discriminant(behavior_tag) == mem::discriminant(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)) - } + pub fn has(&self, flags: BehaviorFlag) -> bool { self.flags.contains(flags) } } impl Component for Behavior { diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 1bdb3b63aa..b36bae11ff 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -49,7 +49,7 @@ pub use self::{ agent::{Agent, Alignment}, aura::{Aura, AuraChange, AuraKind, Auras}, beam::{Beam, BeamSegment}, - behavior::{Behavior, BehaviorTag}, + behavior::{Behavior, BehaviorFlag}, 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/lib.rs b/common/src/lib.rs index c78e861453..a37812d95e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -18,6 +18,8 @@ type_alias_impl_trait )] +#[macro_use] extern crate bitflags; + /// Re-exported crates pub use uuid; diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 392b2a2242..95bf0d5c3d 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}, - Behavior, BehaviorTag, CharacterState, StateUpdate, + Behavior, BehaviorFlag, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, outcome::Outcome, @@ -105,7 +105,7 @@ impl CharacterBehavior for Data { loadout, body, agent: Some(comp::Agent::new(None, &body, true)), - behavior: Some(Behavior::new(&[BehaviorTag::CanSpeak])), + behavior: Some(Behavior::from(BehaviorFlag::CAN_SPEAK)), alignment: comp::Alignment::Owned(*data.uid), scale: self .static_data diff --git a/server/src/cmd.rs b/server/src/cmd.rs index d0a733525c..93c6c4ffff 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -15,7 +15,7 @@ use common::{ buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::item::MaterialStatManifest, invite::InviteKind, - BehaviorTag, ChatType, Inventory, Item, LightEmitter, WaypointArea, + BehaviorFlag, ChatType, Inventory, Item, LightEmitter, WaypointArea, }, effect::Effect, event::{EventBus, ServerEvent}, @@ -1075,7 +1075,7 @@ fn handle_spawn_airship( }); if let Some(pos) = destination { builder = builder.with(comp::Agent::with_destination(pos)); - builder = builder.with(comp::Behavior::new(&[BehaviorTag::CanSpeak])) + builder = builder.with(comp::Behavior::from(BehaviorFlag::CAN_SPEAK)) } builder.build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index f6315d8ba0..652a045e5a 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -75,11 +75,8 @@ pub fn handle_create_npc( } else { entity }; - let entity = if let Some(behavior) = behavior { - entity.with(behavior) - } else { - entity.with(Behavior::default()) - }; + + let entity = entity.with(behavior.unwrap_or_default()); let entity = if let Some(drop_item) = drop_item { entity.with(ItemDrop(drop_item)) diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs index caac3d3adc..fd8e909c6e 100644 --- a/server/src/events/invite.rs +++ b/server/src/events/invite.rs @@ -226,8 +226,12 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) { } let pricing = behaviors .get(inviter) - .and_then(|b| index.get_site_prices(b)) - .or_else(|| behaviors.get(entity).and_then(|b| index.get_site_prices(b))); + .and_then(|b| index.get_site_prices(b.trade_site)) + .or_else(|| { + behaviors + .get(entity) + .and_then(|b| index.get_site_prices(b.trade_site)) + }); clients.get(inviter).map(|c| { c.send(ServerGeneral::UpdatePendingTrade( id, diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index ade0e6e3e6..c5371481fa 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -35,7 +35,7 @@ fn notify_agent_prices( event: AgentEvent, ) { if let (Some(agent), Some(behavior)) = (agents.get_mut(entity), behaviors.get(entity)) { - let prices = index.get_site_prices(behavior); + let prices = index.get_site_prices(behavior.trade_site); if let AgentEvent::UpdatePendingTrade(boxval) = event { // Box<(tid, pend, _, inventories)>) = event { agent @@ -123,7 +123,7 @@ pub fn handle_process_trade_action( prices = prices.or_else(|| { behaviors .get(e) - .and_then(|b| server.index.get_site_prices(b)) + .and_then(|b| server.index.get_site_prices(b.trade_site)) }); } } diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 3c8895e2fa..ad922c10fe 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, Behavior, BehaviorTag}, + comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior, BehaviorFlag}, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, terrain::TerrainGrid, @@ -104,11 +104,8 @@ impl<'a> System<'a> for Sys { + 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 = if matches!(body, comp::Body::Humanoid(_)) { - Some(Behavior::new(&[BehaviorTag::CanSpeak])) - } else { - None - }; + let behavior = matches!(body, comp::Body::Humanoid(_)) + .then(|| Behavior::from(BehaviorFlag::CAN_SPEAK)); let rtsim_entity = Some(RtSimEntity(id)); let event = match body { comp::Body::Ship(ship) => ServerEvent::CreateShip { diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 0aa03c3b87..1a40e344f3 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -14,9 +14,9 @@ use common::{ ItemDesc, ItemKind, }, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, - Agent, Alignment, Behavior, BehaviorTag, Body, CharacterState, ControlAction, ControlEvent, - Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState, Ori, - PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel, + Agent, Alignment, Behavior, BehaviorFlag, Body, CharacterState, ControlAction, + ControlEvent, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState, + Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel, }, event::{Emitter, EventBus, ServerEvent}, path::TraversalConfig, @@ -554,7 +554,7 @@ impl<'a> AgentData<'a> { } if agent.action_timer > 0.0 { if agent.action_timer - < (if self.behavior.has_tag(&BehaviorTag::IsTrading) { + < (if self.behavior.has(BehaviorFlag::IS_TRADING) { TRADE_INTERACTION_TIME } else { DEFAULT_INTERACTION_TIME @@ -591,7 +591,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 && self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if agent.action_timer == 0.0 && self.behavior.has(BehaviorFlag::CAN_SPEAK) { let msg = "npc.speech.villager_under_attack".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -619,7 +619,7 @@ impl<'a> AgentData<'a> { read_data.buffs.get(target), ) { agent.target = None; - if self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { let msg = "npc.speech.villager_enemy_killed".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -882,7 +882,7 @@ impl<'a> AgentData<'a> { let msg = agent.inbox.pop_back(); match msg { Some(AgentEvent::Talk(by, subject)) => { - if self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { agent.target = Some(Target { @@ -935,7 +935,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { + } else if self.behavior.has(BehaviorFlag::CAN_TRADE) { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), @@ -948,8 +948,8 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { - if !self.behavior.has_tag(&BehaviorTag::IsTrading) { + if self.behavior.has(BehaviorFlag::CAN_TRADE) { + if !self.behavior.has(BehaviorFlag::IS_TRADING) { controller.events.push(ControlEvent::InitiateInvite( by, InviteKind::Trade, @@ -1097,8 +1097,8 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if self.behavior.has_tag(&BehaviorTag::CanTrade(None)) { - if !self.behavior.has_tag(&BehaviorTag::IsTrading) { + if self.behavior.has(BehaviorFlag::CAN_TRADE) { + if !self.behavior.has(BehaviorFlag::IS_TRADING) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Talk); @@ -1114,13 +1114,13 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Accept)); - self.behavior.remove_tag(BehaviorTag::IsTradingIssuer); - self.behavior.add_tag(BehaviorTag::IsTrading); + self.behavior.unset(BehaviorFlag::IS_TRADING_ISSUER); + self.behavior.set(BehaviorFlag::IS_TRADING); } else { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.merchant_busy".to_string(), @@ -1132,7 +1132,7 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.villager_decline_trade".to_string(), @@ -1141,7 +1141,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeAccepted(with)) => { - if !self.behavior.has_tag(&BehaviorTag::IsTrading) { + if !self.behavior.has(BehaviorFlag::IS_TRADING) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(with.id()) { @@ -1151,12 +1151,12 @@ impl<'a> AgentData<'a> { selected_at: read_data.time.0, }); } - self.behavior.add_tag(BehaviorTag::IsTrading); - self.behavior.add_tag(BehaviorTag::IsTradingIssuer); + self.behavior.set(BehaviorFlag::IS_TRADING); + self.behavior.set(BehaviorFlag::IS_TRADING_ISSUER); } }, Some(AgentEvent::FinishedTrade(result)) => { - if self.behavior.has_tag(&BehaviorTag::IsTrading) { + if self.behavior.has(BehaviorFlag::IS_TRADING) { match result { TradeResult::Completed => { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( @@ -1169,13 +1169,13 @@ impl<'a> AgentData<'a> { "npc.speech.merchant_trade_declined".to_string(), ))), } - self.behavior.remove_tag(BehaviorTag::IsTrading); + self.behavior.unset(BehaviorFlag::IS_TRADING); } }, Some(AgentEvent::UpdatePendingTrade(boxval)) => { let (tradeid, pending, prices, inventories) = *boxval; - if self.behavior.has_tag(&BehaviorTag::IsTrading) { - let who: usize = if self.behavior.has_tag(&BehaviorTag::IsTradingIssuer) { + if self.behavior.has(BehaviorFlag::IS_TRADING) { + let who: usize = if self.behavior.has(BehaviorFlag::IS_TRADING_ISSUER) { 0 } else { 1 @@ -1209,7 +1209,7 @@ impl<'a> AgentData<'a> { } if pending.phase != TradePhase::Mutate { // we got into the review phase but without balanced goods, decline - self.behavior.remove_tag(BehaviorTag::IsTrading); + self.behavior.unset(BehaviorFlag::IS_TRADING); event_emitter.emit(ServerEvent::ProcessTradeAction( *self.entity, tradeid, @@ -1220,7 +1220,7 @@ impl<'a> AgentData<'a> { } }, None => { - if self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { // no new events, continue looking towards the last interacting player for some // time if let Some(Target { target, .. }) = &agent.target { @@ -1343,7 +1343,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 self.behavior.has_tag(&BehaviorTag::CanSpeak) { + if self.behavior.has(BehaviorFlag::CAN_SPEAK) { if self.rtsim_entity.is_some() { agent.rtsim_controller.events.push( RtSimEvent::AddMemory(Memory { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 9bee6fd9e8..0aa2cfbf85 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -3,7 +3,7 @@ use crate::{ }; use common::{ comp::{ - self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, BehaviorTag, Pos, + self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, BehaviorFlag, Pos, }, event::{EventBus, ServerEvent}, generation::get_npc_name, @@ -14,6 +14,7 @@ use common::{ use common_ecs::{Job, Origin, Phase, System}; use common_net::msg::ServerGeneral; use common_sys::state::TerrainChanges; +use comp::Behavior; use specs::{Join, Read, ReadStorage, Write, WriteExpect}; use std::sync::Arc; use vek::*; @@ -203,14 +204,15 @@ impl<'a> System<'a> for Sys { None }, behavior: if entity.has_agency { - let mut behavior_tags = vec![]; + let mut behavior = Behavior::default(); if can_speak { - behavior_tags.push(BehaviorTag::CanSpeak); + behavior.set(BehaviorFlag::CAN_SPEAK); } if trade_for_site.is_some() { - behavior_tags.push(BehaviorTag::CanTrade(trade_for_site)); + behavior.set(BehaviorFlag::CAN_TRADE); + behavior.trade_site = trade_for_site } - Some(comp::Behavior::new(&behavior_tags)) + Some(behavior) } else { None }, diff --git a/world/src/index.rs b/world/src/index.rs index 390a0d293e..71e38edb25 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -4,9 +4,8 @@ use crate::{ }; use common::{ assets::{AssetExt, AssetHandle}, - comp::{Behavior, BehaviorTag}, store::Store, - trade::SitePrices, + trade::{SiteId, SitePrices}, }; use core::ops::Deref; use noise::{Seedable, SuperSimplex}; @@ -72,17 +71,12 @@ impl Index { pub fn colors(&self) -> AssetHandle> { self.colors } - pub fn get_site_prices(&self, behavior: &Behavior) -> Option { - if let Some(BehaviorTag::CanTrade(site_id)) = behavior.get_tag(&BehaviorTag::CanTrade(None)) - { - site_id - .map(|i| self.sites.recreate_id(i)) - .flatten() - .map(|i| self.sites.get(i)) - .map(|s| s.economy.get_site_prices()) - } else { - None - } + pub fn get_site_prices(&self, site_id: Option) -> Option { + site_id + .map(|i| self.sites.recreate_id(i)) + .flatten() + .map(|i| self.sites.get(i)) + .map(|s| s.economy.get_site_prices()) } } From 993ae8fff7bbe8e3376cd97438f5b2c81d500f70 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 6 Apr 2021 20:18:39 +0200 Subject: [PATCH 09/14] address mutability comment --- server/src/sys/agent.rs | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 1a40e344f3..3516e4459e 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -65,7 +65,6 @@ struct AgentData<'a> { is_gliding: bool, health: Option<&'a Health>, char_state: &'a CharacterState, - behavior: &'a mut Behavior, } #[derive(SystemData)] @@ -260,7 +259,7 @@ impl<'a> System<'a> for Sys { .and_then(|rtsim_ent| rtsim.get_entity(rtsim_ent.0)); // Package all this agent's data into a convenient struct - let mut data = AgentData { + let data = AgentData { entity: &entity, rtsim_entity, uid, @@ -282,7 +281,6 @@ impl<'a> System<'a> for Sys { is_gliding, health: read_data.healths.get(entity), char_state, - behavior, }; /////////////////////////////////////////////////////////// @@ -320,6 +318,7 @@ impl<'a> System<'a> for Sys { if hostile { data.hostile_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -397,6 +396,7 @@ impl<'a> System<'a> for Sys { } else { data.idle_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -406,6 +406,7 @@ impl<'a> System<'a> for Sys { } else { data.idle_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -413,11 +414,23 @@ impl<'a> System<'a> for Sys { } } else { agent.target = None; - data.idle_tree(agent, controller, &read_data, &mut event_emitter); + data.idle_tree( + agent, + behavior, + controller, + &read_data, + &mut event_emitter, + ); } } else { agent.target = None; - data.idle_tree(agent, controller, &read_data, &mut event_emitter); + data.idle_tree( + agent, + behavior, + controller, + &read_data, + &mut event_emitter, + ); } } else { // Target an entity that's attacking us if the attack was recent and we @@ -441,6 +454,7 @@ impl<'a> System<'a> for Sys { agent.target = None; data.idle_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -481,6 +495,7 @@ impl<'a> System<'a> for Sys { agent.target = None; data.idle_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -491,6 +506,7 @@ impl<'a> System<'a> for Sys { agent.target = None; data.idle_tree( agent, + behavior, controller, &read_data, &mut event_emitter, @@ -498,7 +514,13 @@ impl<'a> System<'a> for Sys { } }, _ => { - data.idle_tree(agent, controller, &read_data, &mut event_emitter); + data.idle_tree( + agent, + behavior, + controller, + &read_data, + &mut event_emitter, + ); }, } } @@ -530,8 +552,9 @@ impl<'a> AgentData<'a> { // Subtrees //////////////////////////////////////// fn idle_tree( - &mut self, + &self, agent: &mut Agent, + behavior: &mut Behavior, controller: &mut Controller, read_data: &ReadData, event_emitter: &mut Emitter<'_, ServerEvent>, @@ -554,13 +577,13 @@ impl<'a> AgentData<'a> { } if agent.action_timer > 0.0 { if agent.action_timer - < (if self.behavior.has(BehaviorFlag::IS_TRADING) { + < (if behavior.has(BehaviorFlag::IS_TRADING) { TRADE_INTERACTION_TIME } else { DEFAULT_INTERACTION_TIME }) { - self.interact(agent, controller, &read_data, event_emitter); + self.interact(agent, behavior, controller, &read_data, event_emitter); } else { agent.action_timer = 0.0; agent.target = None; @@ -568,7 +591,7 @@ impl<'a> AgentData<'a> { self.idle(agent, controller, &read_data); } } else if thread_rng().gen::() < 0.1 { - self.choose_target(agent, controller, &read_data, event_emitter); + self.choose_target(agent, behavior, controller, &read_data, event_emitter); } else { self.idle(agent, controller, &read_data); } @@ -577,6 +600,7 @@ 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>, @@ -591,7 +615,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 && self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if agent.action_timer == 0.0 && behavior.has(BehaviorFlag::CAN_SPEAK) { let msg = "npc.speech.villager_under_attack".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -619,7 +643,7 @@ impl<'a> AgentData<'a> { read_data.buffs.get(target), ) { agent.target = None; - if self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { let msg = "npc.speech.villager_enemy_killed".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -630,7 +654,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, controller, &read_data, event_emitter); + self.choose_target(agent, behavior, controller, &read_data, event_emitter); } else if dist_sqrd < SIGHT_DIST.powi(2) { self.attack( agent, @@ -858,8 +882,9 @@ impl<'a> AgentData<'a> { } fn interact( - &mut self, + &self, agent: &mut Agent, + behavior: &mut Behavior, controller: &mut Controller, read_data: &ReadData, event_emitter: &mut Emitter<'_, ServerEvent>, @@ -882,7 +907,7 @@ impl<'a> AgentData<'a> { let msg = agent.inbox.pop_back(); match msg { Some(AgentEvent::Talk(by, subject)) => { - if self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { agent.target = Some(Target { @@ -935,7 +960,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if self.behavior.has(BehaviorFlag::CAN_TRADE) { + } else if behavior.has(BehaviorFlag::CAN_TRADE) { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), @@ -948,8 +973,8 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if self.behavior.has(BehaviorFlag::CAN_TRADE) { - if !self.behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.has(BehaviorFlag::CAN_TRADE) { + if !behavior.has(BehaviorFlag::IS_TRADING) { controller.events.push(ControlEvent::InitiateInvite( by, InviteKind::Trade, @@ -1097,8 +1122,8 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if self.behavior.has(BehaviorFlag::CAN_TRADE) { - if !self.behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.has(BehaviorFlag::CAN_TRADE) { + if !behavior.has(BehaviorFlag::IS_TRADING) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Talk); @@ -1114,13 +1139,13 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Accept)); - self.behavior.unset(BehaviorFlag::IS_TRADING_ISSUER); - self.behavior.set(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorFlag::IS_TRADING_ISSUER); + behavior.set(BehaviorFlag::IS_TRADING); } else { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.merchant_busy".to_string(), @@ -1132,7 +1157,7 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.villager_decline_trade".to_string(), @@ -1141,7 +1166,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeAccepted(with)) => { - if !self.behavior.has(BehaviorFlag::IS_TRADING) { + if !behavior.has(BehaviorFlag::IS_TRADING) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(with.id()) { @@ -1151,12 +1176,12 @@ impl<'a> AgentData<'a> { selected_at: read_data.time.0, }); } - self.behavior.set(BehaviorFlag::IS_TRADING); - self.behavior.set(BehaviorFlag::IS_TRADING_ISSUER); + behavior.set(BehaviorFlag::IS_TRADING); + behavior.set(BehaviorFlag::IS_TRADING_ISSUER); } }, Some(AgentEvent::FinishedTrade(result)) => { - if self.behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.has(BehaviorFlag::IS_TRADING) { match result { TradeResult::Completed => { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( @@ -1169,13 +1194,13 @@ impl<'a> AgentData<'a> { "npc.speech.merchant_trade_declined".to_string(), ))), } - self.behavior.unset(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorFlag::IS_TRADING); } }, Some(AgentEvent::UpdatePendingTrade(boxval)) => { let (tradeid, pending, prices, inventories) = *boxval; - if self.behavior.has(BehaviorFlag::IS_TRADING) { - let who: usize = if self.behavior.has(BehaviorFlag::IS_TRADING_ISSUER) { + if behavior.has(BehaviorFlag::IS_TRADING) { + let who: usize = if behavior.has(BehaviorFlag::IS_TRADING_ISSUER) { 0 } else { 1 @@ -1209,7 +1234,7 @@ impl<'a> AgentData<'a> { } if pending.phase != TradePhase::Mutate { // we got into the review phase but without balanced goods, decline - self.behavior.unset(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorFlag::IS_TRADING); event_emitter.emit(ServerEvent::ProcessTradeAction( *self.entity, tradeid, @@ -1220,7 +1245,7 @@ impl<'a> AgentData<'a> { } }, None => { - if self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { // no new events, continue looking towards the last interacting player for some // time if let Some(Target { target, .. }) = &agent.target { @@ -1296,6 +1321,7 @@ 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>, @@ -1343,7 +1369,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 self.behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.has(BehaviorFlag::CAN_SPEAK) { if self.rtsim_entity.is_some() { agent.rtsim_controller.events.push( RtSimEvent::AddMemory(Memory { From 45fb9f32112050224b09e864dfcb7d3fdd18b854 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Tue, 6 Apr 2021 21:46:17 +0200 Subject: [PATCH 10/14] Address comments - make Behavior's capabilities and state private - Typo in a comment - add basic tests Apply 1 suggestion(s) to 1 file(s) Remove comments in agent.rs --- common/src/comp/agent.rs | 2 - common/src/comp/behavior.rs | 78 ++++++++++++++++++++++++++----- common/src/comp/mod.rs | 2 +- common/src/states/basic_summon.rs | 4 +- server/src/cmd.rs | 4 +- server/src/events/invite.rs | 7 +-- server/src/events/trade.rs | 33 +++++++------ server/src/rtsim/tick.rs | 4 +- server/src/sys/agent.rs | 52 ++++++++++----------- server/src/sys/terrain.rs | 7 +-- world/src/index.rs | 7 ++- 11 files changed, 129 insertions(+), 71 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index c908e02adb..e5673bfc98 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -210,8 +210,6 @@ pub struct Agent { pub patrol_origin: Option>, pub target: Option, pub chaser: Chaser, - /// Does the agent talk when e.g. hit by the player - // TODO move speech patterns into a Behavior component pub psyche: Psyche, pub inbox: VecDeque, pub action_timer: f32, diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index 305c9aba3b..f57ee7beb2 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -5,11 +5,16 @@ use crate::trade::SiteId; bitflags! { #[derive(Default)] - pub struct BehaviorFlag: u8 { - const CAN_SPEAK = 0b00000001; - const CAN_TRADE = 0b00000010; - const IS_TRADING = 0b00000100; - const IS_TRADING_ISSUER = 0b00001000; + pub struct BehaviorCapability: u8 { + const SPEAK = 0b00000001; + const TRADE = 0b00000010; + } +} +bitflags! { + #[derive(Default)] + pub struct BehaviorState: u8 { + const TRADING = 0b00000001; + const TRADING_ISSUER = 0b00000010; } } @@ -20,27 +25,76 @@ bitflags! { /// state when needed #[derive(Default, Copy, Clone, Debug)] pub struct Behavior { - pub flags: BehaviorFlag, + capabilities: BehaviorCapability, + state: BehaviorState, pub trade_site: Option, } -impl From for Behavior { - fn from(flags: BehaviorFlag) -> Self { +impl From for Behavior { + fn from(capabilities: BehaviorCapability) -> Self { Behavior { - flags, + capabilities, + state: BehaviorState::default(), trade_site: None, } } } impl Behavior { - pub fn set(&mut self, flags: BehaviorFlag) { self.flags.set(flags, true) } + /// Set capabilities to the Behavior + pub fn allow(&mut self, capabilities: BehaviorCapability) { + self.capabilities.set(capabilities, true) + } - pub fn unset(&mut self, flags: BehaviorFlag) { self.flags.set(flags, false) } + /// Unset capabilities to the Behavior + pub fn deny(&mut self, capabilities: BehaviorCapability) { + self.capabilities.set(capabilities, false) + } - pub fn has(&self, flags: BehaviorFlag) -> bool { self.flags.contains(flags) } + /// Check if the Behavior is able to do something + pub fn can(&self, capabilities: BehaviorCapability) -> bool { + self.capabilities.contains(capabilities) + } + + /// Set a state to the Behavior + pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) } + + /// Unset a state to the Behavior + pub fn unset(&mut self, state: BehaviorState) { self.state.set(state, false) } + + /// Check if the Behavior has a specific state + pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) } } impl Component for Behavior { type Storage = IdvStorage; } + +#[cfg(test)] +mod tests { + use super::{Behavior, BehaviorCapability, BehaviorState}; + + /// Test to verify that Behavior is working correctly at its most basic + /// usages + #[test] + pub fn basic() { + let mut b = Behavior::default(); + // test capabilities + assert!(!b.can(BehaviorCapability::SPEAK)); + b.allow(BehaviorCapability::SPEAK); + assert!(b.can(BehaviorCapability::SPEAK)); + b.deny(BehaviorCapability::SPEAK); + assert!(!b.can(BehaviorCapability::SPEAK)); + // test states + assert!(!b.is(BehaviorState::TRADING)); + b.set(BehaviorState::TRADING); + assert!(b.is(BehaviorState::TRADING)); + b.unset(BehaviorState::TRADING); + assert!(!b.is(BehaviorState::TRADING)); + // test `from` + let b = Behavior::from(BehaviorCapability::SPEAK | BehaviorCapability::TRADE); + assert!(b.can(BehaviorCapability::SPEAK)); + assert!(b.can(BehaviorCapability::TRADE)); + assert!(b.can(BehaviorCapability::SPEAK | BehaviorCapability::TRADE)); + } +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index b36bae11ff..1a02032630 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -49,7 +49,7 @@ pub use self::{ agent::{Agent, Alignment}, aura::{Aura, AuraChange, AuraKind, Auras}, beam::{Beam, BeamSegment}, - behavior::{Behavior, BehaviorFlag}, + behavior::{Behavior, BehaviorCapability, BehaviorState}, 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/states/basic_summon.rs b/common/src/states/basic_summon.rs index 95bf0d5c3d..f27e1e6951 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}, - Behavior, BehaviorFlag, CharacterState, StateUpdate, + Behavior, BehaviorCapability, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, outcome::Outcome, @@ -105,7 +105,7 @@ impl CharacterBehavior for Data { loadout, body, agent: Some(comp::Agent::new(None, &body, true)), - behavior: Some(Behavior::from(BehaviorFlag::CAN_SPEAK)), + behavior: Some(Behavior::from(BehaviorCapability::SPEAK)), alignment: comp::Alignment::Owned(*data.uid), scale: self .static_data diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 93c6c4ffff..c44abda3c8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -15,7 +15,7 @@ use common::{ buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::item::MaterialStatManifest, invite::InviteKind, - BehaviorFlag, ChatType, Inventory, Item, LightEmitter, WaypointArea, + BehaviorCapability, ChatType, Inventory, Item, LightEmitter, WaypointArea, }, effect::Effect, event::{EventBus, ServerEvent}, @@ -1075,7 +1075,7 @@ fn handle_spawn_airship( }); if let Some(pos) = destination { builder = builder.with(comp::Agent::with_destination(pos)); - builder = builder.with(comp::Behavior::from(BehaviorFlag::CAN_SPEAK)) + builder = builder.with(comp::Behavior::from(BehaviorCapability::SPEAK)) } builder.build(); diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs index fd8e909c6e..430bc44b8c 100644 --- a/server/src/events/invite.rs +++ b/server/src/events/invite.rs @@ -226,12 +226,13 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) { } let pricing = behaviors .get(inviter) - .and_then(|b| index.get_site_prices(b.trade_site)) + .and_then(|b| b.trade_site.map(|id| index.get_site_prices(id))) .or_else(|| { behaviors .get(entity) - .and_then(|b| index.get_site_prices(b.trade_site)) - }); + .and_then(|b| b.trade_site.map(|id| index.get_site_prices(id))) + }) + .flatten(); clients.get(inviter).map(|c| { c.send(ServerGeneral::UpdatePendingTrade( id, diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index c5371481fa..70f637f6cf 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -35,19 +35,21 @@ fn notify_agent_prices( event: AgentEvent, ) { if let (Some(agent), Some(behavior)) = (agents.get_mut(entity), behaviors.get(entity)) { - let prices = index.get_site_prices(behavior.trade_site); - if let AgentEvent::UpdatePendingTrade(boxval) = event { - // Box<(tid, pend, _, inventories)>) = event { - agent - .inbox - .push_front(AgentEvent::UpdatePendingTrade(Box::new(( - // Prefer using this Agent's price data, but use the counterparty's price data - // if we don't have price data - boxval.0, - boxval.1, - prices.unwrap_or(boxval.2), - boxval.3, - )))); + if let Some(site_id) = behavior.trade_site { + let prices = index.get_site_prices(site_id); + if let AgentEvent::UpdatePendingTrade(boxval) = event { + // Box<(tid, pend, _, inventories)>) = event { + agent + .inbox + .push_front(AgentEvent::UpdatePendingTrade(Box::new(( + // Prefer using this Agent's price data, but use the counterparty's price + // data if we don't have price data + boxval.0, + boxval.1, + prices.unwrap_or(boxval.2), + boxval.3, + )))); + } } } } @@ -123,7 +125,10 @@ pub fn handle_process_trade_action( prices = prices.or_else(|| { behaviors .get(e) - .and_then(|b| server.index.get_site_prices(b.trade_site)) + .and_then(|b| { + b.trade_site.map(|id| server.index.get_site_prices(id)) + }) + .flatten() }); } } diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index ad922c10fe..48dc60a44d 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, Behavior, BehaviorFlag}, + comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior, BehaviorCapability}, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, terrain::TerrainGrid, @@ -105,7 +105,7 @@ impl<'a> System<'a> for Sys { 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(BehaviorFlag::CAN_SPEAK)); + .then(|| Behavior::from(BehaviorCapability::SPEAK)); let rtsim_entity = Some(RtSimEntity(id)); let event = match body { comp::Body::Ship(ship) => ServerEvent::CreateShip { diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 3516e4459e..cfb951e7dd 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -14,9 +14,9 @@ use common::{ ItemDesc, ItemKind, }, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, - Agent, Alignment, Behavior, BehaviorFlag, Body, CharacterState, ControlAction, - ControlEvent, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState, - Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel, + Agent, Alignment, Behavior, 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, @@ -577,7 +577,7 @@ impl<'a> AgentData<'a> { } if agent.action_timer > 0.0 { if agent.action_timer - < (if behavior.has(BehaviorFlag::IS_TRADING) { + < (if behavior.is(BehaviorState::TRADING) { TRADE_INTERACTION_TIME } else { DEFAULT_INTERACTION_TIME @@ -615,7 +615,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.has(BehaviorFlag::CAN_SPEAK) { + if agent.action_timer == 0.0 && 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 +643,7 @@ impl<'a> AgentData<'a> { read_data.buffs.get(target), ) { agent.target = None; - if behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { let msg = "npc.speech.villager_enemy_killed".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); @@ -907,7 +907,7 @@ impl<'a> AgentData<'a> { let msg = agent.inbox.pop_back(); match msg { Some(AgentEvent::Talk(by, subject)) => { - if behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { agent.target = Some(Target { @@ -960,7 +960,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if behavior.has(BehaviorFlag::CAN_TRADE) { + } else if behavior.can(BehaviorCapability::TRADE) { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), @@ -973,8 +973,8 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if behavior.has(BehaviorFlag::CAN_TRADE) { - if !behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.can(BehaviorCapability::TRADE) { + if !behavior.is(BehaviorState::TRADING) { controller.events.push(ControlEvent::InitiateInvite( by, InviteKind::Trade, @@ -1122,8 +1122,8 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if behavior.has(BehaviorFlag::CAN_TRADE) { - if !behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.can(BehaviorCapability::TRADE) { + if !behavior.is(BehaviorState::TRADING) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Talk); @@ -1139,13 +1139,13 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Accept)); - behavior.unset(BehaviorFlag::IS_TRADING_ISSUER); - behavior.set(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorState::TRADING_ISSUER); + behavior.set(BehaviorState::TRADING); } else { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.merchant_busy".to_string(), @@ -1157,7 +1157,7 @@ impl<'a> AgentData<'a> { controller .events .push(ControlEvent::InviteResponse(InviteResponse::Decline)); - if behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( *self.uid, "npc.speech.villager_decline_trade".to_string(), @@ -1166,7 +1166,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeAccepted(with)) => { - if !behavior.has(BehaviorFlag::IS_TRADING) { + if !behavior.is(BehaviorState::TRADING) { if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(with.id()) { @@ -1176,12 +1176,12 @@ impl<'a> AgentData<'a> { selected_at: read_data.time.0, }); } - behavior.set(BehaviorFlag::IS_TRADING); - behavior.set(BehaviorFlag::IS_TRADING_ISSUER); + behavior.set(BehaviorState::TRADING); + behavior.set(BehaviorState::TRADING_ISSUER); } }, Some(AgentEvent::FinishedTrade(result)) => { - if behavior.has(BehaviorFlag::IS_TRADING) { + if behavior.is(BehaviorState::TRADING) { match result { TradeResult::Completed => { event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( @@ -1194,13 +1194,13 @@ impl<'a> AgentData<'a> { "npc.speech.merchant_trade_declined".to_string(), ))), } - behavior.unset(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorState::TRADING); } }, Some(AgentEvent::UpdatePendingTrade(boxval)) => { let (tradeid, pending, prices, inventories) = *boxval; - if behavior.has(BehaviorFlag::IS_TRADING) { - let who: usize = if behavior.has(BehaviorFlag::IS_TRADING_ISSUER) { + if behavior.is(BehaviorState::TRADING) { + let who: usize = if behavior.is(BehaviorState::TRADING_ISSUER) { 0 } else { 1 @@ -1234,7 +1234,7 @@ impl<'a> AgentData<'a> { } if pending.phase != TradePhase::Mutate { // we got into the review phase but without balanced goods, decline - behavior.unset(BehaviorFlag::IS_TRADING); + behavior.unset(BehaviorState::TRADING); event_emitter.emit(ServerEvent::ProcessTradeAction( *self.entity, tradeid, @@ -1245,7 +1245,7 @@ impl<'a> AgentData<'a> { } }, None => { - if behavior.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { // no new events, continue looking towards the last interacting player for some // time if let Some(Target { target, .. }) = &agent.target { @@ -1369,7 +1369,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.has(BehaviorFlag::CAN_SPEAK) { + if behavior.can(BehaviorCapability::SPEAK) { if self.rtsim_entity.is_some() { agent.rtsim_controller.events.push( RtSimEvent::AddMemory(Memory { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 0aa2cfbf85..a345b8dc53 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -3,7 +3,8 @@ use crate::{ }; use common::{ comp::{ - self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, BehaviorFlag, Pos, + self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, + BehaviorCapability, Pos, }, event::{EventBus, ServerEvent}, generation::get_npc_name, @@ -206,10 +207,10 @@ impl<'a> System<'a> for Sys { behavior: if entity.has_agency { let mut behavior = Behavior::default(); if can_speak { - behavior.set(BehaviorFlag::CAN_SPEAK); + behavior.allow(BehaviorCapability::SPEAK); } if trade_for_site.is_some() { - behavior.set(BehaviorFlag::CAN_TRADE); + behavior.allow(BehaviorCapability::TRADE); behavior.trade_site = trade_for_site } Some(behavior) diff --git a/world/src/index.rs b/world/src/index.rs index 71e38edb25..bdc8aecbae 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -71,10 +71,9 @@ impl Index { pub fn colors(&self) -> AssetHandle> { self.colors } - pub fn get_site_prices(&self, site_id: Option) -> Option { - site_id - .map(|i| self.sites.recreate_id(i)) - .flatten() + pub fn get_site_prices(&self, site_id: SiteId) -> Option { + self.sites + .recreate_id(site_id) .map(|i| self.sites.get(i)) .map(|s| s.economy.get_site_prices()) } From dbee13f9befd8a19758f369c8f99807667007c13 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Wed, 7 Apr 2021 21:37:48 +0200 Subject: [PATCH 11/14] Finally Behavior isn't good enough as a component, Remove it from ECS and include it onto Agent directly --- common/src/comp/agent.rs | 16 +++-- common/src/comp/behavior.rs | 11 +--- common/src/event.rs | 1 - common/src/lib.rs | 2 - common/src/states/basic_summon.rs | 8 ++- common/sys/src/state.rs | 1 - server/src/cmd.rs | 9 ++- server/src/events/entity_creation.rs | 8 +-- server/src/events/invite.rs | 13 ++-- server/src/events/mod.rs | 2 - server/src/events/trade.rs | 16 ++--- server/src/rtsim/tick.rs | 15 +++-- server/src/sys/agent.rs | 99 ++++++++++------------------ server/src/sys/terrain.rs | 22 +++---- 14 files changed, 95 insertions(+), 128 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index e5673bfc98..d6349628f4 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -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>, pub target: Option, pub chaser: Chaser, + pub behavior: Behavior, pub psyche: Psyche, pub inbox: VecDeque, pub action_timer: f32, @@ -217,20 +218,26 @@ pub struct Agent { } impl Agent { - pub fn with_patrol_origin(mut self, origin: Vec3) -> Self { + pub fn set_patrol_origin(mut self, origin: Vec3) -> Self { self.patrol_origin = Some(origin); self } - pub fn with_destination(pos: Vec3) -> Self { + pub fn with_destination(behavior: Behavior, pos: Vec3) -> Self { Self { psyche: Psyche { aggro: 1.0 }, rtsim_controller: RtSimController::with_destination(pos), + behavior, ..Default::default() } } - pub fn new(patrol_origin: Option>, body: &Body, no_flee: bool) -> Self { + pub fn new( + patrol_origin: Option>, + 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() } } diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs index f57ee7beb2..84e3bbdf89 100644 --- a/common/src/comp/behavior.rs +++ b/common/src/comp/behavior.rs @@ -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; -} - #[cfg(test)] mod tests { use super::{Behavior, BehaviorCapability, BehaviorState}; diff --git a/common/src/event.rs b/common/src/event.rs index 5240643991..db2e7e1a40 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -120,7 +120,6 @@ 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/lib.rs b/common/src/lib.rs index a37812d95e..c78e861453 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -18,8 +18,6 @@ type_alias_impl_trait )] -#[macro_use] extern crate bitflags; - /// Re-exported crates pub use uuid; diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index f27e1e6951..86f3f857fd 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -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 diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index 0c0fe24bac..ca31775284 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -198,7 +198,6 @@ impl State { ecs.register::>(); ecs.register::(); ecs.register::(); - ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index c44abda3c8..96d73e625d 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 652a045e5a..ed9c515d32 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -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>, - behavior: Option, alignment: Alignment, scale: Scale, drop_item: Option, @@ -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 { diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs index 430bc44b8c..47aadb64d2 100644 --- a/server/src/events/invite.rs +++ b/server/src/events/invite.rs @@ -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::(); let uids = state.ecs().read_storage::(); let mut agents = state.ecs().write_storage::(); - let behaviors = state.ecs().read_storage::(); let mut invites = state.ecs().write_storage::(); 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| { diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 0186674ebf..348b0043ca 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -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, diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index 70f637f6cf..7c1d6c792d 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -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, - behaviors: specs::ReadStorage, 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; 2] = [None, None]; let mut prices = None; let agents = server.state.ecs().read_storage::(); - let behaviors = server.state.ecs().read_storage::(); // 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::(), - server.state.ecs().read_storage::(), &server.index, e, AgentEvent::UpdatePendingTrade(Box::new(( diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 48dc60a44d..fa526e995d 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -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, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index cfb951e7dd..1110c5bfd1 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -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, - (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::() < 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 { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index a345b8dc53..0b3b7d8c85 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -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 }, From a30cbaf7358aed501b97a2b62b9bc2db490961bd Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Wed, 7 Apr 2021 22:11:29 +0200 Subject: [PATCH 12/14] Move Behavior to Agent's Component --- common/src/comp/agent.rs | 96 ++++++++++++++++++++++++++++++++++++- common/src/comp/behavior.rs | 93 ----------------------------------- common/src/comp/mod.rs | 4 +- 3 files changed, 95 insertions(+), 98 deletions(-) delete mode 100644 common/src/comp/behavior.rs diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index d6349628f4..547459240f 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -2,7 +2,7 @@ use crate::{ comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body}, path::Chaser, rtsim::RtSimController, - trade::{PendingTrade, ReducedInventory, SitePrices, TradeId, TradeResult}, + trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, uid::Uid, }; use specs::{Component, Entity as EcsEntity}; @@ -10,7 +10,7 @@ use specs_idvs::IdvStorage; use std::collections::VecDeque; use vek::*; -use super::{dialogue::Subject, Behavior}; +use super::dialogue::Subject; pub const DEFAULT_INTERACTION_TIME: f32 = 3.0; pub const TRADE_INTERACTION_TIME: f32 = 300.0; @@ -98,6 +98,69 @@ impl Component for Alignment { type Storage = IdvStorage; } +bitflags::bitflags! { + #[derive(Default)] + pub struct BehaviorCapability: u8 { + const SPEAK = 0b00000001; + const TRADE = 0b00000010; + } +} +bitflags::bitflags! { + #[derive(Default)] + pub struct BehaviorState: u8 { + const TRADING = 0b00000001; + const TRADING_ISSUER = 0b00000010; + } +} + +/// # Behavior Component +/// This component allow an Entity to register one or more behavior tags. +/// These tags act as flags of what an Entity can do, or what it is doing. +/// Behaviors Tags can be added and removed as the Entity lives, to update its +/// state when needed +#[derive(Default, Copy, Clone, Debug)] +pub struct Behavior { + capabilities: BehaviorCapability, + state: BehaviorState, + pub trade_site: Option, +} + +impl From for Behavior { + fn from(capabilities: BehaviorCapability) -> Self { + Behavior { + capabilities, + state: BehaviorState::default(), + trade_site: None, + } + } +} + +impl Behavior { + /// Set capabilities to the Behavior + pub fn allow(&mut self, capabilities: BehaviorCapability) { + self.capabilities.set(capabilities, true) + } + + /// Unset capabilities to the Behavior + pub fn deny(&mut self, capabilities: BehaviorCapability) { + self.capabilities.set(capabilities, false) + } + + /// Check if the Behavior is able to do something + pub fn can(&self, capabilities: BehaviorCapability) -> bool { + self.capabilities.contains(capabilities) + } + + /// Set a state to the Behavior + pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) } + + /// Unset a state to the Behavior + pub fn unset(&mut self, state: BehaviorState) { self.state.set(state, false) } + + /// Check if the Behavior has a specific state + pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) } +} + #[derive(Clone, Debug, Default)] pub struct Psyche { pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks, 0.5 = flee at 50% health @@ -254,3 +317,32 @@ impl Agent { impl Component for Agent { type Storage = IdvStorage; } + +#[cfg(test)] +mod tests { + use super::{Behavior, BehaviorCapability, BehaviorState}; + + /// Test to verify that Behavior is working correctly at its most basic + /// usages + #[test] + pub fn behavior_basic() { + let mut b = Behavior::default(); + // test capabilities + assert!(!b.can(BehaviorCapability::SPEAK)); + b.allow(BehaviorCapability::SPEAK); + assert!(b.can(BehaviorCapability::SPEAK)); + b.deny(BehaviorCapability::SPEAK); + assert!(!b.can(BehaviorCapability::SPEAK)); + // test states + assert!(!b.is(BehaviorState::TRADING)); + b.set(BehaviorState::TRADING); + assert!(b.is(BehaviorState::TRADING)); + b.unset(BehaviorState::TRADING); + assert!(!b.is(BehaviorState::TRADING)); + // test `from` + let b = Behavior::from(BehaviorCapability::SPEAK | BehaviorCapability::TRADE); + assert!(b.can(BehaviorCapability::SPEAK)); + assert!(b.can(BehaviorCapability::TRADE)); + assert!(b.can(BehaviorCapability::SPEAK | BehaviorCapability::TRADE)); + } +} diff --git a/common/src/comp/behavior.rs b/common/src/comp/behavior.rs deleted file mode 100644 index 84e3bbdf89..0000000000 --- a/common/src/comp/behavior.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::trade::SiteId; - -bitflags::bitflags! { - #[derive(Default)] - pub struct BehaviorCapability: u8 { - const SPEAK = 0b00000001; - const TRADE = 0b00000010; - } -} -bitflags::bitflags! { - #[derive(Default)] - pub struct BehaviorState: u8 { - const TRADING = 0b00000001; - const TRADING_ISSUER = 0b00000010; - } -} - -/// # Behavior Component -/// This component allow an Entity to register one or more behavior tags. -/// These tags act as flags of what an Entity can do, or what it is doing. -/// Behaviors Tags can be added and removed as the Entity lives, to update its -/// state when needed -#[derive(Default, Copy, Clone, Debug)] -pub struct Behavior { - capabilities: BehaviorCapability, - state: BehaviorState, - pub trade_site: Option, -} - -impl From for Behavior { - fn from(capabilities: BehaviorCapability) -> Self { - Behavior { - capabilities, - state: BehaviorState::default(), - trade_site: None, - } - } -} - -impl Behavior { - /// Set capabilities to the Behavior - pub fn allow(&mut self, capabilities: BehaviorCapability) { - self.capabilities.set(capabilities, true) - } - - /// Unset capabilities to the Behavior - pub fn deny(&mut self, capabilities: BehaviorCapability) { - self.capabilities.set(capabilities, false) - } - - /// Check if the Behavior is able to do something - pub fn can(&self, capabilities: BehaviorCapability) -> bool { - self.capabilities.contains(capabilities) - } - - /// Set a state to the Behavior - pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) } - - /// Unset a state to the Behavior - pub fn unset(&mut self, state: BehaviorState) { self.state.set(state, false) } - - /// Check if the Behavior has a specific state - pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) } -} - -#[cfg(test)] -mod tests { - use super::{Behavior, BehaviorCapability, BehaviorState}; - - /// Test to verify that Behavior is working correctly at its most basic - /// usages - #[test] - pub fn basic() { - let mut b = Behavior::default(); - // test capabilities - assert!(!b.can(BehaviorCapability::SPEAK)); - b.allow(BehaviorCapability::SPEAK); - assert!(b.can(BehaviorCapability::SPEAK)); - b.deny(BehaviorCapability::SPEAK); - assert!(!b.can(BehaviorCapability::SPEAK)); - // test states - assert!(!b.is(BehaviorState::TRADING)); - b.set(BehaviorState::TRADING); - assert!(b.is(BehaviorState::TRADING)); - b.unset(BehaviorState::TRADING); - assert!(!b.is(BehaviorState::TRADING)); - // test `from` - let b = Behavior::from(BehaviorCapability::SPEAK | BehaviorCapability::TRADE); - assert!(b.can(BehaviorCapability::SPEAK)); - assert!(b.can(BehaviorCapability::TRADE)); - assert!(b.can(BehaviorCapability::SPEAK | BehaviorCapability::TRADE)); - } -} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 1a02032630..00ea323c21 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -3,7 +3,6 @@ #[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"))] @@ -46,10 +45,9 @@ pub mod visual; pub use self::{ ability::{CharacterAbility, CharacterAbilityType}, admin::Admin, - agent::{Agent, Alignment}, + agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState}, aura::{Aura, AuraChange, AuraKind, Auras}, beam::{Beam, BeamSegment}, - behavior::{Behavior, BehaviorCapability, BehaviorState}, 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, From bc1797a240aa01ef1882c7fab1670c6ba55bf8b5 Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Thu, 8 Apr 2021 18:33:00 +0200 Subject: [PATCH 13/14] Address comments --- common/src/comp/agent.rs | 38 ++++++++++++++++++++++++++++-------- server/src/cmd.rs | 10 +++------- server/src/events/invite.rs | 13 +++++++++---- server/src/events/trade.rs | 39 ++++++++++++++++--------------------- server/src/sys/terrain.rs | 14 +++++-------- 5 files changed, 64 insertions(+), 50 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 547459240f..9ef11f572d 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -136,6 +136,30 @@ impl From for Behavior { } impl Behavior { + /// Builder function + /// Set capabilities if Option is Some + pub fn maybe_with_capabilities( + mut self, + maybe_capabilities: Option, + ) -> Self { + if let Some(capabilities) = maybe_capabilities { + self.allow(capabilities) + } + self + } + + /// Builder function + /// Set trade_site and TRADE capability if Option is Some + pub fn with_trade_site(mut self, trade_site: Option) -> Self { + self.trade_site = trade_site; + if trade_site.is_some() { + self.allow(BehaviorCapability::TRADE); + } else { + self.deny(BehaviorCapability::TRADE); + } + self + } + /// Set capabilities to the Behavior pub fn allow(&mut self, capabilities: BehaviorCapability) { self.capabilities.set(capabilities, true) @@ -281,18 +305,16 @@ pub struct Agent { } impl Agent { - pub fn set_patrol_origin(mut self, origin: Vec3) -> Self { + pub fn with_patrol_origin(mut self, origin: Vec3) -> Self { self.patrol_origin = Some(origin); self } - pub fn with_destination(behavior: Behavior, pos: Vec3) -> Self { - Self { - psyche: Psyche { aggro: 1.0 }, - rtsim_controller: RtSimController::with_destination(pos), - behavior, - ..Default::default() - } + pub fn with_destination(mut self, pos: Vec3) -> Self { + self.psyche = Psyche { aggro: 1.0 }; + self.rtsim_controller = RtSimController::with_destination(pos); + self.behavior.allow(BehaviorCapability::SPEAK); + self } pub fn new( diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 96d73e625d..d9a5a89bf2 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -15,7 +15,7 @@ use common::{ buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::item::MaterialStatManifest, invite::InviteKind, - BehaviorCapability, ChatType, Inventory, Item, LightEmitter, WaypointArea, + ChatType, Inventory, Item, LightEmitter, WaypointArea, }, effect::Effect, event::{EventBus, ServerEvent}, @@ -31,7 +31,6 @@ 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::{ @@ -889,7 +888,7 @@ fn handle_spawn( if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment { comp::Agent::default() } else { - comp::Agent::default().set_patrol_origin(pos.0) + comp::Agent::default().with_patrol_origin(pos.0) }; for _ in 0..amount { @@ -1075,10 +1074,7 @@ fn handle_spawn_airship( animated: true, }); if let Some(pos) = destination { - builder = builder.with(comp::Agent::with_destination( - Behavior::from(BehaviorCapability::SPEAK), - pos, - )); + builder = builder.with(comp::Agent::default().with_destination(pos)) } builder.build(); diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs index 47aadb64d2..7df7dd20af 100644 --- a/server/src/events/invite.rs +++ b/server/src/events/invite.rs @@ -225,13 +225,18 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) { } let pricing = agents .get(inviter) - .and_then(|a| a.behavior.trade_site.map(|id| index.get_site_prices(id))) + .and_then(|a| { + a.behavior + .trade_site + .and_then(|id| index.get_site_prices(id)) + }) .or_else(|| { agents.get(entity).and_then(|a| { - a.behavior.trade_site.map(|id| index.get_site_prices(id)) + a.behavior + .trade_site + .and_then(|id| index.get_site_prices(id)) }) - }) - .flatten(); + }); clients.get(inviter).map(|c| { c.send(ServerGeneral::UpdatePendingTrade( id, diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index 7c1d6c792d..657602673b 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -32,22 +32,21 @@ fn notify_agent_prices( entity: EcsEntity, event: AgentEvent, ) { - 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 { - agent - .inbox - .push_front(AgentEvent::UpdatePendingTrade(Box::new(( - // Prefer using this Agent's price data, but use the counterparty's price - // data if we don't have price data - boxval.0, - boxval.1, - prices.unwrap_or(boxval.2), - boxval.3, - )))); - } + if let Some((Some(site_id), agent)) = agents.get_mut(entity).map(|a| (a.behavior.trade_site, a)) + { + let prices = index.get_site_prices(site_id); + if let AgentEvent::UpdatePendingTrade(boxval) = event { + // Box<(tid, pend, _, inventories)>) = event { + agent + .inbox + .push_front(AgentEvent::UpdatePendingTrade(Box::new(( + // Prefer using this Agent's price data, but use the counterparty's price + // data if we don't have price data + boxval.0, + boxval.1, + prices.unwrap_or(boxval.2), + boxval.3, + )))); } } } @@ -122,12 +121,8 @@ pub fn handle_process_trade_action( prices = prices.or_else(|| { agents .get(e) - .and_then(|a| { - a.behavior - .trade_site - .map(|id| server.index.get_site_prices(id)) - }) - .flatten() + .and_then(|a| a.behavior.trade_site) + .and_then(|id| server.index.get_site_prices(id)) }); } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 0b3b7d8c85..1ed8c054f6 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -193,18 +193,14 @@ impl<'a> System<'a> for Sys { poise, loadout, agent: if entity.has_agency { - let mut behavior = Behavior::default(); - if can_speak { - behavior.allow(BehaviorCapability::SPEAK); - } - if trade_for_site.is_some() { - behavior.allow(BehaviorCapability::TRADE); - behavior.trade_site = trade_for_site - } Some(comp::Agent::new( Some(entity.pos), &body, - behavior, + Behavior::default() + .maybe_with_capabilities( + can_speak.then(|| BehaviorCapability::SPEAK), + ) + .with_trade_site(trade_for_site), matches!( loadout_config, Some(comp::inventory::loadout_builder::LoadoutConfig::Guard) From d80db38bc9e6a1e79b43bd199ca77f150ca07acd Mon Sep 17 00:00:00 2001 From: Vincent Foulon Date: Thu, 8 Apr 2021 19:06:57 +0200 Subject: [PATCH 14/14] Remove TRADE capability, use trade_site attribute instead --- common/src/comp/agent.rs | 15 +++++---------- server/src/sys/agent.rs | 6 +++--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 9ef11f572d..a777055891 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -102,7 +102,6 @@ bitflags::bitflags! { #[derive(Default)] pub struct BehaviorCapability: u8 { const SPEAK = 0b00000001; - const TRADE = 0b00000010; } } bitflags::bitflags! { @@ -149,14 +148,9 @@ impl Behavior { } /// Builder function - /// Set trade_site and TRADE capability if Option is Some + /// Set trade_site if Option is Some pub fn with_trade_site(mut self, trade_site: Option) -> Self { self.trade_site = trade_site; - if trade_site.is_some() { - self.allow(BehaviorCapability::TRADE); - } else { - self.deny(BehaviorCapability::TRADE); - } self } @@ -175,6 +169,9 @@ impl Behavior { self.capabilities.contains(capabilities) } + /// Check if the Behavior is able to trade + pub fn can_trade(&self) -> bool { self.trade_site.is_some() } + /// Set a state to the Behavior pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) } @@ -362,9 +359,7 @@ mod tests { b.unset(BehaviorState::TRADING); assert!(!b.is(BehaviorState::TRADING)); // test `from` - let b = Behavior::from(BehaviorCapability::SPEAK | BehaviorCapability::TRADE); + let b = Behavior::from(BehaviorCapability::SPEAK); assert!(b.can(BehaviorCapability::SPEAK)); - assert!(b.can(BehaviorCapability::TRADE)); - assert!(b.can(BehaviorCapability::SPEAK | BehaviorCapability::TRADE)); } } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 1110c5bfd1..574d806a0d 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -930,7 +930,7 @@ impl<'a> AgentData<'a> { event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), )); - } else if agent.behavior.can(BehaviorCapability::TRADE) { + } else if agent.behavior.can_trade() { let msg = "npc.speech.merchant_advertisement".to_string(); event_emitter.emit(ServerEvent::Chat( UnresolvedChatMsg::npc(*self.uid, msg), @@ -943,7 +943,7 @@ impl<'a> AgentData<'a> { } }, Subject::Trade => { - if agent.behavior.can(BehaviorCapability::TRADE) { + if agent.behavior.can_trade() { if !agent.behavior.is(BehaviorState::TRADING) { controller.events.push(ControlEvent::InitiateInvite( by, @@ -1092,7 +1092,7 @@ impl<'a> AgentData<'a> { } }, Some(AgentEvent::TradeInvite(with)) => { - if agent.behavior.can(BehaviorCapability::TRADE) { + if agent.behavior.can_trade() { if !agent.behavior.is(BehaviorState::TRADING) { // stand still and looking towards the trading player controller.actions.push(ControlAction::Stand);