mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'vfoulon80/behavior-component' into 'master'
New Component: Behavior See merge request veloren/veloren!2033
This commit is contained in:
commit
0eecc61ddf
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5394,6 +5394,7 @@ dependencies = [
|
||||
"approx 0.4.0",
|
||||
"arraygen",
|
||||
"assets_manager",
|
||||
"bitflags",
|
||||
"criterion",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils 0.8.3",
|
||||
|
@ -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"
|
||||
|
@ -98,6 +98,90 @@ impl Component for Alignment {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct BehaviorCapability: u8 {
|
||||
const SPEAK = 0b00000001;
|
||||
}
|
||||
}
|
||||
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<SiteId>,
|
||||
}
|
||||
|
||||
impl From<BehaviorCapability> for Behavior {
|
||||
fn from(capabilities: BehaviorCapability) -> Self {
|
||||
Behavior {
|
||||
capabilities,
|
||||
state: BehaviorState::default(),
|
||||
trade_site: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Behavior {
|
||||
/// Builder function
|
||||
/// Set capabilities if Option is Some
|
||||
pub fn maybe_with_capabilities(
|
||||
mut self,
|
||||
maybe_capabilities: Option<BehaviorCapability>,
|
||||
) -> Self {
|
||||
if let Some(capabilities) = maybe_capabilities {
|
||||
self.allow(capabilities)
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder function
|
||||
/// Set trade_site if Option is Some
|
||||
pub fn with_trade_site(mut self, trade_site: Option<SiteId>) -> Self {
|
||||
self.trade_site = trade_site;
|
||||
self
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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) }
|
||||
|
||||
/// 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
|
||||
@ -210,12 +294,7 @@ pub struct Agent {
|
||||
pub patrol_origin: Option<Vec3<f32>>,
|
||||
pub target: Option<Target>,
|
||||
pub chaser: Chaser,
|
||||
/// Does the agent talk when e.g. hit by the player
|
||||
// TODO move speech patterns into a Behavior component
|
||||
pub can_speak: bool,
|
||||
pub trade_for_site: Option<SiteId>,
|
||||
pub trading: bool,
|
||||
pub trading_issuer: bool,
|
||||
pub behavior: Behavior,
|
||||
pub psyche: Psyche,
|
||||
pub inbox: VecDeque<AgentEvent>,
|
||||
pub action_timer: f32,
|
||||
@ -228,31 +307,27 @@ impl Agent {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||
Self {
|
||||
can_speak: true,
|
||||
psyche: Psyche { aggro: 1.0 },
|
||||
rtsim_controller: RtSimController::with_destination(pos),
|
||||
..Default::default()
|
||||
}
|
||||
pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
|
||||
self.psyche = Psyche { aggro: 1.0 };
|
||||
self.rtsim_controller = RtSimController::with_destination(pos);
|
||||
self.behavior.allow(BehaviorCapability::SPEAK);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
patrol_origin: Option<Vec3<f32>>,
|
||||
can_speak: bool,
|
||||
trade_for_site: Option<SiteId>,
|
||||
body: &Body,
|
||||
behavior: Behavior,
|
||||
no_flee: bool,
|
||||
) -> Self {
|
||||
Agent {
|
||||
patrol_origin,
|
||||
can_speak,
|
||||
trade_for_site,
|
||||
psyche: if no_flee {
|
||||
Psyche { aggro: 1.0 }
|
||||
} else {
|
||||
Psyche::from(body)
|
||||
},
|
||||
behavior,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@ -261,3 +336,30 @@ impl Agent {
|
||||
impl Component for Agent {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[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);
|
||||
assert!(b.can(BehaviorCapability::SPEAK));
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ 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},
|
||||
body::{
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
comp::{
|
||||
self,
|
||||
inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig},
|
||||
CharacterState, StateUpdate,
|
||||
Behavior, BehaviorCapability, CharacterState, StateUpdate,
|
||||
},
|
||||
event::{LocalEvent, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -104,7 +104,12 @@ 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,
|
||||
&body,
|
||||
Behavior::from(BehaviorCapability::SPEAK),
|
||||
true,
|
||||
)),
|
||||
alignment: comp::Alignment::Owned(*data.uid),
|
||||
scale: self
|
||||
.static_data
|
||||
|
@ -1074,7 +1074,7 @@ fn handle_spawn_airship(
|
||||
animated: true,
|
||||
});
|
||||
if let Some(pos) = destination {
|
||||
builder = builder.with(comp::Agent::with_destination(pos))
|
||||
builder = builder.with(comp::Agent::default().with_destination(pos))
|
||||
}
|
||||
builder.build();
|
||||
|
||||
|
@ -225,8 +225,18 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
||||
}
|
||||
let pricing = agents
|
||||
.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(|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
|
||||
.and_then(|id| index.get_site_prices(id))
|
||||
})
|
||||
});
|
||||
clients.get(inviter).map(|c| {
|
||||
c.send(ServerGeneral::UpdatePendingTrade(
|
||||
id,
|
||||
|
@ -32,15 +32,16 @@ fn notify_agent_prices(
|
||||
entity: EcsEntity,
|
||||
event: AgentEvent,
|
||||
) {
|
||||
if let Some(agent) = agents.get_mut(entity) {
|
||||
let prices = index.get_site_prices(agent);
|
||||
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
|
||||
// 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),
|
||||
@ -118,7 +119,10 @@ 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))
|
||||
agents
|
||||
.get(e)
|
||||
.and_then(|a| a.behavior.trade_site)
|
||||
.and_then(|id| server.index.get_site_prices(id))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use super::*;
|
||||
use common::{
|
||||
comp::{self, inventory::loadout_builder::LoadoutBuilder},
|
||||
comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior, BehaviorCapability},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::{DeltaTime, Time},
|
||||
terrain::TerrainGrid,
|
||||
@ -104,12 +104,16 @@ 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,
|
||||
matches!(body, comp::Body::Humanoid(_)),
|
||||
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 {
|
||||
|
@ -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, 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,
|
||||
@ -137,9 +137,11 @@ impl<'a> System<'a> for Sys {
|
||||
(
|
||||
&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,
|
||||
@ -153,14 +155,12 @@ impl<'a> System<'a> for Sys {
|
||||
&read_data.char_states,
|
||||
)
|
||||
.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 +170,7 @@ impl<'a> System<'a> for Sys {
|
||||
(
|
||||
entity,
|
||||
(energy, health),
|
||||
pos,
|
||||
vel,
|
||||
ori,
|
||||
(pos, vel, ori),
|
||||
body,
|
||||
inventory,
|
||||
stats,
|
||||
@ -551,7 +549,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
if agent.action_timer > 0.0 {
|
||||
if agent.action_timer
|
||||
< (if agent.trading {
|
||||
< (if agent.behavior.is(BehaviorState::TRADING) {
|
||||
TRADE_INTERACTION_TIME
|
||||
} else {
|
||||
DEFAULT_INTERACTION_TIME
|
||||
@ -588,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 && agent.can_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)));
|
||||
@ -616,7 +614,7 @@ impl<'a> AgentData<'a> {
|
||||
read_data.buffs.get(target),
|
||||
) {
|
||||
agent.target = None;
|
||||
if agent.can_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)));
|
||||
@ -879,7 +877,7 @@ impl<'a> AgentData<'a> {
|
||||
let msg = agent.inbox.pop_back();
|
||||
match msg {
|
||||
Some(AgentEvent::Talk(by, subject)) => {
|
||||
if agent.can_speak {
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
|
||||
{
|
||||
agent.target = Some(Target {
|
||||
@ -933,7 +931,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 agent.behavior.can_trade() {
|
||||
let msg = "npc.speech.merchant_advertisement".to_string();
|
||||
event_emitter.emit(ServerEvent::Chat(
|
||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
||||
@ -946,8 +944,8 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
},
|
||||
Subject::Trade => {
|
||||
if agent.trade_for_site.is_some() {
|
||||
if !agent.trading {
|
||||
if agent.behavior.can_trade() {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.events.push(ControlEvent::InitiateInvite(
|
||||
by,
|
||||
InviteKind::Trade,
|
||||
@ -1095,8 +1093,8 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::TradeInvite(with)) => {
|
||||
if agent.trade_for_site.is_some() {
|
||||
if !agent.trading {
|
||||
if agent.behavior.can_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);
|
||||
@ -1112,13 +1110,13 @@ impl<'a> AgentData<'a> {
|
||||
controller
|
||||
.events
|
||||
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
|
||||
agent.trading_issuer = false;
|
||||
agent.trading = true;
|
||||
agent.behavior.unset(BehaviorState::TRADING_ISSUER);
|
||||
agent.behavior.set(BehaviorState::TRADING);
|
||||
} else {
|
||||
controller
|
||||
.events
|
||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||
if agent.can_speak {
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
*self.uid,
|
||||
"npc.speech.merchant_busy".to_string(),
|
||||
@ -1130,7 +1128,7 @@ impl<'a> AgentData<'a> {
|
||||
controller
|
||||
.events
|
||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||
if agent.can_speak {
|
||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
*self.uid,
|
||||
"npc.speech.villager_decline_trade".to_string(),
|
||||
@ -1139,7 +1137,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::TradeAccepted(with)) => {
|
||||
if !agent.trading {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
if let Some(target) =
|
||||
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
||||
{
|
||||
@ -1149,12 +1147,12 @@ impl<'a> AgentData<'a> {
|
||||
selected_at: read_data.time.0,
|
||||
});
|
||||
}
|
||||
agent.trading = true;
|
||||
agent.trading_issuer = true;
|
||||
agent.behavior.set(BehaviorState::TRADING);
|
||||
agent.behavior.set(BehaviorState::TRADING_ISSUER);
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::FinishedTrade(result)) => {
|
||||
if agent.trading {
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||
@ -1167,13 +1165,17 @@ impl<'a> AgentData<'a> {
|
||||
"npc.speech.merchant_trade_declined".to_string(),
|
||||
))),
|
||||
}
|
||||
agent.trading = false;
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::UpdatePendingTrade(boxval)) => {
|
||||
let (tradeid, pending, prices, inventories) = *boxval;
|
||||
if agent.trading {
|
||||
let who: usize = if agent.trading_issuer { 0 } else { 1 };
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
let who: usize = if agent.behavior.is(BehaviorState::TRADING_ISSUER) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let balance0: f32 =
|
||||
prices.balance(&pending.offers, &inventories, 1 - who, true);
|
||||
let balance1: f32 = prices.balance(&pending.offers, &inventories, who, false);
|
||||
@ -1203,7 +1205,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
if pending.phase != TradePhase::Mutate {
|
||||
// we got into the review phase but without balanced goods, decline
|
||||
agent.trading = false;
|
||||
agent.behavior.unset(BehaviorState::TRADING);
|
||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||
*self.entity,
|
||||
tradeid,
|
||||
@ -1214,7 +1216,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if agent.can_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 {
|
||||
@ -1337,7 +1339,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 agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||
if self.rtsim_entity.is_some() {
|
||||
agent.rtsim_controller.events.push(
|
||||
RtSimEvent::AddMemory(Memory {
|
||||
|
@ -2,7 +2,10 @@ 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,
|
||||
BehaviorCapability, Pos,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::get_npc_name,
|
||||
npc::NPC_NAMES,
|
||||
@ -12,6 +15,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::*;
|
||||
@ -191,9 +195,12 @@ impl<'a> System<'a> for Sys {
|
||||
agent: if entity.has_agency {
|
||||
Some(comp::Agent::new(
|
||||
Some(entity.pos),
|
||||
can_speak,
|
||||
trade_for_site,
|
||||
&body,
|
||||
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)
|
||||
|
@ -4,9 +4,8 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets::{AssetExt, AssetHandle},
|
||||
comp::Agent,
|
||||
store::Store,
|
||||
trade::SitePrices,
|
||||
trade::{SiteId, SitePrices},
|
||||
};
|
||||
use core::ops::Deref;
|
||||
use noise::{Seedable, SuperSimplex};
|
||||
@ -72,11 +71,9 @@ impl Index {
|
||||
|
||||
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
|
||||
|
||||
pub fn get_site_prices(&self, agent: &Agent) -> Option<SitePrices> {
|
||||
agent
|
||||
.trade_for_site
|
||||
.map(|i| self.sites.recreate_id(i))
|
||||
.flatten()
|
||||
pub fn get_site_prices(&self, site_id: SiteId) -> Option<SitePrices> {
|
||||
self.sites
|
||||
.recreate_id(site_id)
|
||||
.map(|i| self.sites.get(i))
|
||||
.map(|s| s.economy.get_site_prices())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user