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",
|
"approx 0.4.0",
|
||||||
"arraygen",
|
"arraygen",
|
||||||
"assets_manager",
|
"assets_manager",
|
||||||
|
"bitflags",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"crossbeam-utils 0.8.3",
|
"crossbeam-utils 0.8.3",
|
||||||
|
@ -24,6 +24,7 @@ serde = { version = "1.0.110", features = ["derive", "rc"] }
|
|||||||
approx = "0.4.0"
|
approx = "0.4.0"
|
||||||
arraygen = "0.1.13"
|
arraygen = "0.1.13"
|
||||||
crossbeam-utils = "0.8.1"
|
crossbeam-utils = "0.8.1"
|
||||||
|
bitflags = "1.2"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
enum-iterator = "0.6"
|
enum-iterator = "0.6"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
@ -98,6 +98,90 @@ impl Component for Alignment {
|
|||||||
type Storage = IdvStorage<Self>;
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Psyche {
|
pub struct Psyche {
|
||||||
pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks, 0.5 = flee at 50% health
|
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 patrol_origin: Option<Vec3<f32>>,
|
||||||
pub target: Option<Target>,
|
pub target: Option<Target>,
|
||||||
pub chaser: Chaser,
|
pub chaser: Chaser,
|
||||||
/// Does the agent talk when e.g. hit by the player
|
pub behavior: Behavior,
|
||||||
// TODO move speech patterns into a Behavior component
|
|
||||||
pub can_speak: bool,
|
|
||||||
pub trade_for_site: Option<SiteId>,
|
|
||||||
pub trading: bool,
|
|
||||||
pub trading_issuer: bool,
|
|
||||||
pub psyche: Psyche,
|
pub psyche: Psyche,
|
||||||
pub inbox: VecDeque<AgentEvent>,
|
pub inbox: VecDeque<AgentEvent>,
|
||||||
pub action_timer: f32,
|
pub action_timer: f32,
|
||||||
@ -228,31 +307,27 @@ impl Agent {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
|
||||||
Self {
|
self.psyche = Psyche { aggro: 1.0 };
|
||||||
can_speak: true,
|
self.rtsim_controller = RtSimController::with_destination(pos);
|
||||||
psyche: Psyche { aggro: 1.0 },
|
self.behavior.allow(BehaviorCapability::SPEAK);
|
||||||
rtsim_controller: RtSimController::with_destination(pos),
|
self
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
patrol_origin: Option<Vec3<f32>>,
|
patrol_origin: Option<Vec3<f32>>,
|
||||||
can_speak: bool,
|
|
||||||
trade_for_site: Option<SiteId>,
|
|
||||||
body: &Body,
|
body: &Body,
|
||||||
|
behavior: Behavior,
|
||||||
no_flee: bool,
|
no_flee: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Agent {
|
Agent {
|
||||||
patrol_origin,
|
patrol_origin,
|
||||||
can_speak,
|
|
||||||
trade_for_site,
|
|
||||||
psyche: if no_flee {
|
psyche: if no_flee {
|
||||||
Psyche { aggro: 1.0 }
|
Psyche { aggro: 1.0 }
|
||||||
} else {
|
} else {
|
||||||
Psyche::from(body)
|
Psyche::from(body)
|
||||||
},
|
},
|
||||||
|
behavior,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,3 +336,30 @@ impl Agent {
|
|||||||
impl Component for Agent {
|
impl Component for Agent {
|
||||||
type Storage = IdvStorage<Self>;
|
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::{
|
pub use self::{
|
||||||
ability::{CharacterAbility, CharacterAbilityType},
|
ability::{CharacterAbility, CharacterAbilityType},
|
||||||
admin::Admin,
|
admin::Admin,
|
||||||
agent::{Agent, Alignment},
|
agent::{Agent, Alignment, Behavior, BehaviorCapability, BehaviorState},
|
||||||
aura::{Aura, AuraChange, AuraKind, Auras},
|
aura::{Aura, AuraChange, AuraKind, Auras},
|
||||||
beam::{Beam, BeamSegment},
|
beam::{Beam, BeamSegment},
|
||||||
body::{
|
body::{
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig},
|
inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig},
|
||||||
CharacterState, StateUpdate,
|
Behavior, BehaviorCapability, CharacterState, StateUpdate,
|
||||||
},
|
},
|
||||||
event::{LocalEvent, ServerEvent},
|
event::{LocalEvent, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -104,7 +104,12 @@ impl CharacterBehavior for Data {
|
|||||||
poise: comp::Poise::new(body),
|
poise: comp::Poise::new(body),
|
||||||
loadout,
|
loadout,
|
||||||
body,
|
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),
|
alignment: comp::Alignment::Owned(*data.uid),
|
||||||
scale: self
|
scale: self
|
||||||
.static_data
|
.static_data
|
||||||
|
@ -1074,7 +1074,7 @@ fn handle_spawn_airship(
|
|||||||
animated: true,
|
animated: true,
|
||||||
});
|
});
|
||||||
if let Some(pos) = destination {
|
if let Some(pos) = destination {
|
||||||
builder = builder.with(comp::Agent::with_destination(pos))
|
builder = builder.with(comp::Agent::default().with_destination(pos))
|
||||||
}
|
}
|
||||||
builder.build();
|
builder.build();
|
||||||
|
|
||||||
|
@ -225,8 +225,18 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
|||||||
}
|
}
|
||||||
let pricing = agents
|
let pricing = agents
|
||||||
.get(inviter)
|
.get(inviter)
|
||||||
.and_then(|a| index.get_site_prices(a))
|
.and_then(|a| {
|
||||||
.or_else(|| agents.get(entity).and_then(|a| index.get_site_prices(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| {
|
clients.get(inviter).map(|c| {
|
||||||
c.send(ServerGeneral::UpdatePendingTrade(
|
c.send(ServerGeneral::UpdatePendingTrade(
|
||||||
id,
|
id,
|
||||||
|
@ -32,15 +32,16 @@ fn notify_agent_prices(
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
event: AgentEvent,
|
event: AgentEvent,
|
||||||
) {
|
) {
|
||||||
if let Some(agent) = agents.get_mut(entity) {
|
if let Some((Some(site_id), agent)) = agents.get_mut(entity).map(|a| (a.behavior.trade_site, a))
|
||||||
let prices = index.get_site_prices(agent);
|
{
|
||||||
|
let prices = index.get_site_prices(site_id);
|
||||||
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
if let AgentEvent::UpdatePendingTrade(boxval) = event {
|
||||||
// Box<(tid, pend, _, inventories)>) = event {
|
// Box<(tid, pend, _, inventories)>) = event {
|
||||||
agent
|
agent
|
||||||
.inbox
|
.inbox
|
||||||
.push_front(AgentEvent::UpdatePendingTrade(Box::new((
|
.push_front(AgentEvent::UpdatePendingTrade(Box::new((
|
||||||
// Prefer using this Agent's price data, but use the counterparty's price data
|
// Prefer using this Agent's price data, but use the counterparty's price
|
||||||
// if we don't have price data
|
// data if we don't have price data
|
||||||
boxval.0,
|
boxval.0,
|
||||||
boxval.1,
|
boxval.1,
|
||||||
prices.unwrap_or(boxval.2),
|
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
|
// Get price info from the first Agent in the trade (currently, an
|
||||||
// Agent will never initiate a trade with another agent though)
|
// Agent will never initiate a trade with another agent though)
|
||||||
prices = prices.or_else(|| {
|
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 super::*;
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, inventory::loadout_builder::LoadoutBuilder},
|
comp::{self, inventory::loadout_builder::LoadoutBuilder, Behavior, BehaviorCapability},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
resources::{DeltaTime, Time},
|
resources::{DeltaTime, Time},
|
||||||
terrain::TerrainGrid,
|
terrain::TerrainGrid,
|
||||||
@ -104,12 +104,16 @@ impl<'a> System<'a> for Sys {
|
|||||||
+ Vec3::new(0.5, 0.5, body.flying_height());
|
+ Vec3::new(0.5, 0.5, body.flying_height());
|
||||||
let pos = comp::Pos(spawn_pos);
|
let pos = comp::Pos(spawn_pos);
|
||||||
let agent = Some(comp::Agent::new(
|
let agent = Some(comp::Agent::new(
|
||||||
None,
|
|
||||||
matches!(body, comp::Body::Humanoid(_)),
|
|
||||||
None,
|
None,
|
||||||
&body,
|
&body,
|
||||||
|
if matches!(body, comp::Body::Humanoid(_)) {
|
||||||
|
Behavior::from(BehaviorCapability::SPEAK)
|
||||||
|
} else {
|
||||||
|
Behavior::default()
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
|
|
||||||
let rtsim_entity = Some(RtSimEntity(id));
|
let rtsim_entity = Some(RtSimEntity(id));
|
||||||
let event = match body {
|
let event = match body {
|
||||||
comp::Body::Ship(ship) => ServerEvent::CreateShip {
|
comp::Body::Ship(ship) => ServerEvent::CreateShip {
|
||||||
|
@ -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, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction,
|
||||||
Health, InputKind, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale,
|
ControlEvent, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState,
|
||||||
Stats, UnresolvedChatMsg, Vel,
|
Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel,
|
||||||
},
|
},
|
||||||
event::{Emitter, EventBus, ServerEvent},
|
event::{Emitter, EventBus, ServerEvent},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
@ -137,9 +137,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
(
|
(
|
||||||
&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,
|
||||||
@ -153,14 +155,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
&read_data.char_states,
|
&read_data.char_states,
|
||||||
)
|
)
|
||||||
.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 +170,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,
|
||||||
@ -551,7 +549,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 agent.behavior.is(BehaviorState::TRADING) {
|
||||||
TRADE_INTERACTION_TIME
|
TRADE_INTERACTION_TIME
|
||||||
} else {
|
} else {
|
||||||
DEFAULT_INTERACTION_TIME
|
DEFAULT_INTERACTION_TIME
|
||||||
@ -588,7 +586,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 && agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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 +614,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 agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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)));
|
||||||
@ -879,7 +877,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 agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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 {
|
||||||
@ -933,7 +931,7 @@ impl<'a> AgentData<'a> {
|
|||||||
event_emitter.emit(ServerEvent::Chat(
|
event_emitter.emit(ServerEvent::Chat(
|
||||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
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();
|
let msg = "npc.speech.merchant_advertisement".to_string();
|
||||||
event_emitter.emit(ServerEvent::Chat(
|
event_emitter.emit(ServerEvent::Chat(
|
||||||
UnresolvedChatMsg::npc(*self.uid, msg),
|
UnresolvedChatMsg::npc(*self.uid, msg),
|
||||||
@ -946,8 +944,8 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Trade => {
|
Subject::Trade => {
|
||||||
if agent.trade_for_site.is_some() {
|
if agent.behavior.can_trade() {
|
||||||
if !agent.trading {
|
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||||
controller.events.push(ControlEvent::InitiateInvite(
|
controller.events.push(ControlEvent::InitiateInvite(
|
||||||
by,
|
by,
|
||||||
InviteKind::Trade,
|
InviteKind::Trade,
|
||||||
@ -1095,8 +1093,8 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AgentEvent::TradeInvite(with)) => {
|
Some(AgentEvent::TradeInvite(with)) => {
|
||||||
if agent.trade_for_site.is_some() {
|
if agent.behavior.can_trade() {
|
||||||
if !agent.trading {
|
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||||
// 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);
|
||||||
@ -1112,13 +1110,13 @@ impl<'a> AgentData<'a> {
|
|||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
|
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
|
||||||
agent.trading_issuer = false;
|
agent.behavior.unset(BehaviorState::TRADING_ISSUER);
|
||||||
agent.trading = true;
|
agent.behavior.set(BehaviorState::TRADING);
|
||||||
} else {
|
} else {
|
||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||||
if agent.can_speak {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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(),
|
||||||
@ -1130,7 +1128,7 @@ impl<'a> AgentData<'a> {
|
|||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||||
if agent.can_speak {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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(),
|
||||||
@ -1139,7 +1137,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AgentEvent::TradeAccepted(with)) => {
|
Some(AgentEvent::TradeAccepted(with)) => {
|
||||||
if !agent.trading {
|
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||||
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())
|
||||||
{
|
{
|
||||||
@ -1149,12 +1147,12 @@ impl<'a> AgentData<'a> {
|
|||||||
selected_at: read_data.time.0,
|
selected_at: read_data.time.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
agent.trading = true;
|
agent.behavior.set(BehaviorState::TRADING);
|
||||||
agent.trading_issuer = true;
|
agent.behavior.set(BehaviorState::TRADING_ISSUER);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AgentEvent::FinishedTrade(result)) => {
|
Some(AgentEvent::FinishedTrade(result)) => {
|
||||||
if agent.trading {
|
if agent.behavior.is(BehaviorState::TRADING) {
|
||||||
match result {
|
match result {
|
||||||
TradeResult::Completed => {
|
TradeResult::Completed => {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||||
@ -1167,13 +1165,17 @@ impl<'a> AgentData<'a> {
|
|||||||
"npc.speech.merchant_trade_declined".to_string(),
|
"npc.speech.merchant_trade_declined".to_string(),
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
agent.trading = false;
|
agent.behavior.unset(BehaviorState::TRADING);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AgentEvent::UpdatePendingTrade(boxval)) => {
|
Some(AgentEvent::UpdatePendingTrade(boxval)) => {
|
||||||
let (tradeid, pending, prices, inventories) = *boxval;
|
let (tradeid, pending, prices, inventories) = *boxval;
|
||||||
if agent.trading {
|
if agent.behavior.is(BehaviorState::TRADING) {
|
||||||
let who: usize = if agent.trading_issuer { 0 } else { 1 };
|
let who: usize = if agent.behavior.is(BehaviorState::TRADING_ISSUER) {
|
||||||
|
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);
|
||||||
@ -1203,7 +1205,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;
|
agent.behavior.unset(BehaviorState::TRADING);
|
||||||
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
event_emitter.emit(ServerEvent::ProcessTradeAction(
|
||||||
*self.entity,
|
*self.entity,
|
||||||
tradeid,
|
tradeid,
|
||||||
@ -1214,7 +1216,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if agent.can_speak {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
// 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 {
|
||||||
@ -1337,7 +1339,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 agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
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 {
|
||||||
|
@ -2,7 +2,10 @@ use crate::{
|
|||||||
chunk_generator::ChunkGenerator, client::Client, presence::Presence, rtsim::RtSim, Tick,
|
chunk_generator::ChunkGenerator, client::Client, presence::Presence, rtsim::RtSim, Tick,
|
||||||
};
|
};
|
||||||
use common::{
|
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},
|
event::{EventBus, ServerEvent},
|
||||||
generation::get_npc_name,
|
generation::get_npc_name,
|
||||||
npc::NPC_NAMES,
|
npc::NPC_NAMES,
|
||||||
@ -12,6 +15,7 @@ use common::{
|
|||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::ServerGeneral;
|
use common_net::msg::ServerGeneral;
|
||||||
use common_sys::state::TerrainChanges;
|
use common_sys::state::TerrainChanges;
|
||||||
|
use comp::Behavior;
|
||||||
use specs::{Join, Read, ReadStorage, Write, WriteExpect};
|
use specs::{Join, Read, ReadStorage, Write, WriteExpect};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -191,9 +195,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
agent: if entity.has_agency {
|
agent: if entity.has_agency {
|
||||||
Some(comp::Agent::new(
|
Some(comp::Agent::new(
|
||||||
Some(entity.pos),
|
Some(entity.pos),
|
||||||
can_speak,
|
|
||||||
trade_for_site,
|
|
||||||
&body,
|
&body,
|
||||||
|
Behavior::default()
|
||||||
|
.maybe_with_capabilities(
|
||||||
|
can_speak.then(|| BehaviorCapability::SPEAK),
|
||||||
|
)
|
||||||
|
.with_trade_site(trade_for_site),
|
||||||
matches!(
|
matches!(
|
||||||
loadout_config,
|
loadout_config,
|
||||||
Some(comp::inventory::loadout_builder::LoadoutConfig::Guard)
|
Some(comp::inventory::loadout_builder::LoadoutConfig::Guard)
|
||||||
|
@ -4,9 +4,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
assets::{AssetExt, AssetHandle},
|
assets::{AssetExt, AssetHandle},
|
||||||
comp::Agent,
|
|
||||||
store::Store,
|
store::Store,
|
||||||
trade::SitePrices,
|
trade::{SiteId, SitePrices},
|
||||||
};
|
};
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use noise::{Seedable, SuperSimplex};
|
use noise::{Seedable, SuperSimplex};
|
||||||
@ -72,11 +71,9 @@ impl Index {
|
|||||||
|
|
||||||
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
|
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
|
||||||
|
|
||||||
pub fn get_site_prices(&self, agent: &Agent) -> Option<SitePrices> {
|
pub fn get_site_prices(&self, site_id: SiteId) -> Option<SitePrices> {
|
||||||
agent
|
self.sites
|
||||||
.trade_for_site
|
.recreate_id(site_id)
|
||||||
.map(|i| self.sites.recreate_id(i))
|
|
||||||
.flatten()
|
|
||||||
.map(|i| self.sites.get(i))
|
.map(|i| self.sites.get(i))
|
||||||
.map(|s| s.economy.get_site_prices())
|
.map(|s| s.economy.get_site_prices())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user