Use Behavior into Agent

This commit is contained in:
Vincent Foulon 2021-03-29 22:30:09 +02:00 committed by Marcel Märtens
parent 41314e9098
commit 18694b30ad
4 changed files with 58 additions and 90 deletions

View File

@ -3,7 +3,7 @@ use specs_idvs::IdvStorage;
use std::mem; use std::mem;
/// Behavior Component /// Behavior Component
#[derive(Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct Behavior { pub struct Behavior {
tags: Vec<BehaviorTag>, tags: Vec<BehaviorTag>,
} }
@ -15,8 +15,11 @@ pub enum BehaviorTag {
CanSpeak, CanSpeak,
/// The entity is able to trade /// The entity is able to trade
CanTrade, CanTrade,
/// The entity is currently trading
IsTrading,
/// The entity has issued a trade /// The entity has issued a trade
TradingIssuer, IsTradingIssuer,
} }
impl Behavior { impl Behavior {
@ -57,17 +60,13 @@ impl Behavior {
} }
/// Get a specific tag by variant /// 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 self.tags
.iter() .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 { impl Component for Behavior {
type Storage = IdvStorage<Self>; type Storage = IdvStorage<Self>;
} }

View File

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

View File

@ -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<Vec3<f32>> = {
let positions = state.ecs().read_storage::<comp::Pos>();
let agents = state.ecs().read_storage::<comp::Agent>();
(&agents, &positions)
.join()
.filter_map(|(a, p)| a.trade_for_site.map(|_| p.0))
.collect()
};
if let Some(agent) = state
.ecs()
.write_storage::<comp::Agent>()
.get_mut(npc_entity)
{
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
if let Some(mypos) = state.ecs().read_storage::<comp::Pos>().get(interactor) {
let mut closest = std::f32::INFINITY;
let mut closest_pos = None;
for p in trader_positions.iter() {
let diff = p - mypos.0;
let dist = diff.magnitude();
if dist < closest {
closest = dist;
closest_pos = Some(*p);
}
}
agent.inbox.push_front(AgentEvent::Talk(
interactor_uid,
Subject::Person(AskedPerson {
person_type: comp::dialogue::PersonType::Merchant,
origin: closest_pos,
}),
));
}
}
}
}
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) { pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
let state = server.state_mut(); let state = server.state_mut();

View File

@ -14,9 +14,9 @@ use common::{
ItemDesc, ItemKind, ItemDesc, ItemKind,
}, },
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy, Agent, Alignment, Behavior, BehaviorTag, Body, CharacterState, ControlAction, ControlEvent,
Health, InputKind, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState, Ori,
Stats, UnresolvedChatMsg, Vel, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel,
}, },
event::{Emitter, EventBus, ServerEvent}, event::{Emitter, EventBus, ServerEvent},
path::TraversalConfig, path::TraversalConfig,
@ -65,6 +65,7 @@ struct AgentData<'a> {
is_gliding: bool, is_gliding: bool,
health: Option<&'a Health>, health: Option<&'a Health>,
char_state: &'a CharacterState, char_state: &'a CharacterState,
behavior: &'a mut Behavior,
} }
#[derive(SystemData)] #[derive(SystemData)]
@ -121,6 +122,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
WriteExpect<'a, RtSim>, WriteExpect<'a, RtSim>,
WriteStorage<'a, Behavior>,
); );
const NAME: &'static str = "agent"; 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 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn run( fn run(
job: &mut Job<Self>, job: &mut Job<Self>,
(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; let rtsim = &mut *rtsim;
job.cpu_stats.measure(ParMode::Rayon); job.cpu_stats.measure(ParMode::Rayon);
( (
&read_data.entities, &read_data.entities,
(&read_data.energies, read_data.healths.maybe()), (&read_data.energies, read_data.healths.maybe()),
&read_data.positions, (
&read_data.velocities, &read_data.positions,
&read_data.orientations, &read_data.velocities,
&read_data.orientations,
),
read_data.bodies.maybe(), read_data.bodies.maybe(),
&read_data.inventories, &read_data.inventories,
&read_data.stats, &read_data.stats,
@ -151,16 +155,15 @@ impl<'a> System<'a> for Sys {
read_data.groups.maybe(), read_data.groups.maybe(),
read_data.mount_states.maybe(), read_data.mount_states.maybe(),
&read_data.char_states, &read_data.char_states,
&mut behaviors,
) )
.par_join() .par_join()
.filter( .filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _, _)| {
|(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state, _)| { // Skip mounted entities
// Skip mounted entities mount_state
mount_state .map(|ms| *ms == MountState::Unmounted)
.map(|ms| *ms == MountState::Unmounted) .unwrap_or(true)
.unwrap_or(true) })
},
)
.for_each_init( .for_each_init(
|| { || {
prof_span!(guard, "agent rayon job"); prof_span!(guard, "agent rayon job");
@ -170,9 +173,7 @@ impl<'a> System<'a> for Sys {
( (
entity, entity,
(energy, health), (energy, health),
pos, (pos, vel, ori),
vel,
ori,
body, body,
inventory, inventory,
stats, stats,
@ -184,6 +185,7 @@ impl<'a> System<'a> for Sys {
groups, groups,
_, _,
char_state, char_state,
behavior,
)| { )| {
//// Hack, replace with better system when groups are more sophisticated //// Hack, replace with better system when groups are more sophisticated
//// Override alignment if in a group unless entity is owned already //// 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)); .and_then(|rtsim_ent| rtsim.get_entity(rtsim_ent.0));
// Package all this agent's data into a convenient struct // Package all this agent's data into a convenient struct
let data = AgentData { let mut data = AgentData {
entity: &entity, entity: &entity,
rtsim_entity, rtsim_entity,
uid, uid,
@ -280,6 +282,7 @@ impl<'a> System<'a> for Sys {
is_gliding, is_gliding,
health: read_data.healths.get(entity), health: read_data.healths.get(entity),
char_state, char_state,
behavior,
}; };
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -527,7 +530,7 @@ impl<'a> AgentData<'a> {
// Subtrees // Subtrees
//////////////////////////////////////// ////////////////////////////////////////
fn idle_tree( fn idle_tree(
&self, &mut self,
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData, read_data: &ReadData,
@ -551,7 +554,7 @@ impl<'a> AgentData<'a> {
} }
if agent.action_timer > 0.0 { if agent.action_timer > 0.0 {
if agent.action_timer if agent.action_timer
< (if agent.trading { < (if self.behavior.has_tag(&BehaviorTag::IsTrading) {
TRADE_INTERACTION_TIME TRADE_INTERACTION_TIME
} else { } else {
DEFAULT_INTERACTION_TIME DEFAULT_INTERACTION_TIME
@ -588,7 +591,7 @@ impl<'a> AgentData<'a> {
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0); let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
// Should the agent flee? // Should the agent flee?
if 1.0 - agent.psyche.aggro > self.damage && self.flees { 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(); let msg = "npc.speech.villager_under_attack".to_string();
event_emitter event_emitter
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
@ -616,7 +619,7 @@ impl<'a> AgentData<'a> {
read_data.buffs.get(target), read_data.buffs.get(target),
) { ) {
agent.target = None; agent.target = None;
if agent.can_speak { if self.behavior.has_tag(&BehaviorTag::CanSpeak) {
let msg = "npc.speech.villager_enemy_killed".to_string(); let msg = "npc.speech.villager_enemy_killed".to_string();
event_emitter event_emitter
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
@ -855,7 +858,7 @@ impl<'a> AgentData<'a> {
} }
fn interact( fn interact(
&self, &mut self,
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData, read_data: &ReadData,
@ -879,7 +882,7 @@ impl<'a> AgentData<'a> {
let msg = agent.inbox.pop_back(); let msg = agent.inbox.pop_back();
match msg { match msg {
Some(AgentEvent::Talk(by, subject)) => { 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()) if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
{ {
agent.target = Some(Target { agent.target = Some(Target {
@ -945,8 +948,8 @@ impl<'a> AgentData<'a> {
} }
}, },
Subject::Trade => { Subject::Trade => {
if agent.trade_for_site.is_some() { if self.behavior.has_tag(&BehaviorTag::CanTrade) {
if !agent.trading { if !self.behavior.has_tag(&BehaviorTag::IsTrading) {
controller.events.push(ControlEvent::InitiateInvite( controller.events.push(ControlEvent::InitiateInvite(
by, by,
InviteKind::Trade, InviteKind::Trade,
@ -1094,8 +1097,8 @@ impl<'a> AgentData<'a> {
} }
}, },
Some(AgentEvent::TradeInvite(with)) => { Some(AgentEvent::TradeInvite(with)) => {
if agent.trade_for_site.is_some() { if self.behavior.has_tag(&BehaviorTag::CanTrade) {
if !agent.trading { if !self.behavior.has_tag(&BehaviorTag::IsTrading) {
// stand still and looking towards the trading player // stand still and looking towards the trading player
controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Stand);
controller.actions.push(ControlAction::Talk); controller.actions.push(ControlAction::Talk);
@ -1111,13 +1114,13 @@ impl<'a> AgentData<'a> {
controller controller
.events .events
.push(ControlEvent::InviteResponse(InviteResponse::Accept)); .push(ControlEvent::InviteResponse(InviteResponse::Accept));
agent.trading_issuer = false; self.behavior.remove_tag(BehaviorTag::IsTradingIssuer);
agent.trading = true; self.behavior.add_tag(BehaviorTag::IsTrading);
} else { } else {
controller controller
.events .events
.push(ControlEvent::InviteResponse(InviteResponse::Decline)); .push(ControlEvent::InviteResponse(InviteResponse::Decline));
if agent.can_speak { if self.behavior.has_tag(&BehaviorTag::CanSpeak) {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
*self.uid, *self.uid,
"npc.speech.merchant_busy".to_string(), "npc.speech.merchant_busy".to_string(),
@ -1129,7 +1132,7 @@ impl<'a> AgentData<'a> {
controller controller
.events .events
.push(ControlEvent::InviteResponse(InviteResponse::Decline)); .push(ControlEvent::InviteResponse(InviteResponse::Decline));
if agent.can_speak { if self.behavior.has_tag(&BehaviorTag::CanSpeak) {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
*self.uid, *self.uid,
"npc.speech.villager_decline_trade".to_string(), "npc.speech.villager_decline_trade".to_string(),
@ -1138,7 +1141,7 @@ impl<'a> AgentData<'a> {
} }
}, },
Some(AgentEvent::TradeAccepted(with)) => { Some(AgentEvent::TradeAccepted(with)) => {
if !agent.trading { if !self.behavior.has_tag(&BehaviorTag::IsTrading) {
if let Some(target) = if let Some(target) =
read_data.uid_allocator.retrieve_entity_internal(with.id()) read_data.uid_allocator.retrieve_entity_internal(with.id())
{ {
@ -1148,12 +1151,12 @@ impl<'a> AgentData<'a> {
selected_at: read_data.time.0, selected_at: read_data.time.0,
}); });
} }
agent.trading = true; self.behavior.add_tag(BehaviorTag::IsTrading);
agent.trading_issuer = true; self.behavior.add_tag(BehaviorTag::IsTradingIssuer);
} }
}, },
Some(AgentEvent::FinishedTrade(result)) => { Some(AgentEvent::FinishedTrade(result)) => {
if agent.trading { if self.behavior.has_tag(&BehaviorTag::IsTrading) {
match result { match result {
TradeResult::Completed => { TradeResult::Completed => {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
@ -1166,13 +1169,17 @@ impl<'a> AgentData<'a> {
"npc.speech.merchant_trade_declined".to_string(), "npc.speech.merchant_trade_declined".to_string(),
))), ))),
} }
agent.trading = false; self.behavior.remove_tag(BehaviorTag::IsTrading);
} }
}, },
Some(AgentEvent::UpdatePendingTrade(boxval)) => { Some(AgentEvent::UpdatePendingTrade(boxval)) => {
let (tradeid, pending, prices, inventories) = *boxval; let (tradeid, pending, prices, inventories) = *boxval;
if agent.trading { if self.behavior.has_tag(&BehaviorTag::IsTrading) {
let who: usize = if agent.trading_issuer { 0 } else { 1 }; let who: usize = if self.behavior.has_tag(&BehaviorTag::IsTradingIssuer) {
0
} else {
1
};
let balance0: f32 = let balance0: f32 =
prices.balance(&pending.offers, &inventories, 1 - who, true); prices.balance(&pending.offers, &inventories, 1 - who, true);
let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false); let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false);
@ -1202,7 +1209,7 @@ impl<'a> AgentData<'a> {
} }
if pending.phase != TradePhase::Mutate { if pending.phase != TradePhase::Mutate {
// we got into the review phase but without balanced goods, decline // 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( event_emitter.emit(ServerEvent::ProcessTradeAction(
*self.entity, *self.entity,
tradeid, tradeid,
@ -1213,7 +1220,7 @@ impl<'a> AgentData<'a> {
} }
}, },
None => { None => {
if agent.can_speak { if self.behavior.has_tag(&BehaviorTag::CanSpeak) {
// no new events, continue looking towards the last interacting player for some // no new events, continue looking towards the last interacting player for some
// time // time
if let Some(Target { target, .. }) = &agent.target { if let Some(Target { target, .. }) = &agent.target {
@ -1336,7 +1343,7 @@ impl<'a> AgentData<'a> {
( (
self.alignment.map_or(false, |alignment| { 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 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() { if self.rtsim_entity.is_some() {
agent.rtsim_controller.events.push( agent.rtsim_controller.events.push(
RtSimEvent::AddMemory(Memory { RtSimEvent::AddMemory(Memory {