mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Make NPC interaction go via rtsim
This commit is contained in:
parent
2ff0118df0
commit
a5b1e41d8b
@ -23,6 +23,7 @@ use common::{
|
|||||||
self,
|
self,
|
||||||
chat::KillSource,
|
chat::KillSource,
|
||||||
controller::CraftEvent,
|
controller::CraftEvent,
|
||||||
|
dialogue::Subject,
|
||||||
group,
|
group,
|
||||||
inventory::item::{modular, tool, ItemKind},
|
inventory::item::{modular, tool, ItemKind},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
@ -1132,14 +1133,16 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn npc_interact(&mut self, npc_entity: EcsEntity) {
|
pub fn npc_interact(&mut self, npc_entity: EcsEntity, subject: Subject) {
|
||||||
// If we're dead, exit before sending message
|
// If we're dead, exit before sending message
|
||||||
if self.is_dead() {
|
if self.is_dead() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(uid) = self.state.read_component_copied(npc_entity) {
|
if let Some(uid) = self.state.read_component_copied(npc_entity) {
|
||||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Interact(uid)));
|
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Interact(
|
||||||
|
uid, subject,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
quadruped_small, ship, Body, UtteranceKind,
|
quadruped_small, ship, Body, UtteranceKind,
|
||||||
},
|
},
|
||||||
path::Chaser,
|
path::Chaser,
|
||||||
rtsim::RtSimController,
|
rtsim::{NpcInput, RtSimController},
|
||||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -569,6 +569,8 @@ pub struct Agent {
|
|||||||
/// required and reset each time the flee timer is reset.
|
/// required and reset each time the flee timer is reset.
|
||||||
pub flee_from_pos: Option<Pos>,
|
pub flee_from_pos: Option<Pos>,
|
||||||
pub awareness: Awareness,
|
pub awareness: Awareness,
|
||||||
|
/// Inputs sent up to rtsim
|
||||||
|
pub rtsim_outbox: Option<VecDeque<NpcInput>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -673,6 +675,7 @@ impl Agent {
|
|||||||
position_pid_controller: None,
|
position_pid_controller: None,
|
||||||
flee_from_pos: None,
|
flee_from_pos: None,
|
||||||
awareness: Awareness::new(0.0),
|
awareness: Awareness::new(0.0),
|
||||||
|
rtsim_outbox: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
ability,
|
ability,
|
||||||
|
dialogue::Subject,
|
||||||
inventory::{
|
inventory::{
|
||||||
item::tool::ToolKind,
|
item::tool::ToolKind,
|
||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
@ -137,7 +138,7 @@ pub enum ControlEvent {
|
|||||||
//ToggleLantern,
|
//ToggleLantern,
|
||||||
EnableLantern,
|
EnableLantern,
|
||||||
DisableLantern,
|
DisableLantern,
|
||||||
Interact(Uid),
|
Interact(Uid, Subject),
|
||||||
InitiateInvite(Uid, InviteKind),
|
InitiateInvite(Uid, InviteKind),
|
||||||
InviteResponse(InviteResponse),
|
InviteResponse(InviteResponse),
|
||||||
PerformTradeAction(TradeId, TradeAction),
|
PerformTradeAction(TradeId, TradeAction),
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use vek::{Vec2, Vec3};
|
use vek::{Vec2, Vec3};
|
||||||
|
|
||||||
use super::Item;
|
use super::Item;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct AskedLocation {
|
pub struct AskedLocation {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub origin: Vec2<i32>,
|
pub origin: Vec2<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PersonType {
|
pub enum PersonType {
|
||||||
Merchant,
|
Merchant,
|
||||||
Villager { name: String },
|
Villager { name: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct AskedPerson {
|
pub struct AskedPerson {
|
||||||
pub person_type: PersonType,
|
pub person_type: PersonType,
|
||||||
pub origin: Option<Vec3<f32>>,
|
pub origin: Option<Vec3<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AskedPerson {
|
impl AskedPerson {
|
||||||
@ -30,7 +31,7 @@ impl AskedPerson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Conversation subject
|
/// Conversation subject
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Subject {
|
pub enum Subject {
|
||||||
/// Using simple interaction with NPC
|
/// Using simple interaction with NPC
|
||||||
/// This is meant to be the default behavior of talking
|
/// This is meant to be the default behavior of talking
|
||||||
|
@ -40,6 +40,14 @@ impl PresenceKind {
|
|||||||
/// certain in-game messages from the client such as control inputs
|
/// certain in-game messages from the client such as control inputs
|
||||||
/// should be handled.
|
/// should be handled.
|
||||||
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
|
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
|
||||||
|
|
||||||
|
pub fn character_id(&self) -> Option<CharacterId> {
|
||||||
|
if let Self::Character(character_id) = self {
|
||||||
|
Some(*character_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::Sound,
|
agent::Sound,
|
||||||
|
dialogue::Subject,
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
DisconnectReason, Ori, Pos,
|
DisconnectReason, Ori, Pos,
|
||||||
},
|
},
|
||||||
@ -189,7 +190,7 @@ pub enum ServerEvent {
|
|||||||
},
|
},
|
||||||
EnableLantern(EcsEntity),
|
EnableLantern(EcsEntity),
|
||||||
DisableLantern(EcsEntity),
|
DisableLantern(EcsEntity),
|
||||||
NpcInteract(EcsEntity, EcsEntity),
|
NpcInteract(EcsEntity, EcsEntity, Subject),
|
||||||
InviteResponse(EcsEntity, InviteResponse),
|
InviteResponse(EcsEntity, InviteResponse),
|
||||||
InitiateInvite(EcsEntity, Uid, InviteKind),
|
InitiateInvite(EcsEntity, Uid, InviteKind),
|
||||||
ProcessTradeAction(EcsEntity, TradeId, TradeAction),
|
ProcessTradeAction(EcsEntity, TradeId, TradeAction),
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||||
// module in `server`.
|
// module in `server`.
|
||||||
|
|
||||||
use crate::{character::CharacterId, comp::Content};
|
use crate::{
|
||||||
|
character::CharacterId,
|
||||||
|
comp::{dialogue::Subject, Content},
|
||||||
|
};
|
||||||
use rand::{seq::IteratorRandom, Rng};
|
use rand::{seq::IteratorRandom, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::Component;
|
use specs::Component;
|
||||||
@ -19,6 +22,8 @@ slotmap::new_key_type! { pub struct SiteId; }
|
|||||||
|
|
||||||
slotmap::new_key_type! { pub struct FactionId; }
|
slotmap::new_key_type! { pub struct FactionId; }
|
||||||
|
|
||||||
|
slotmap::new_key_type! { pub struct ReportId; }
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct RtSimEntity(pub NpcId);
|
pub struct RtSimEntity(pub NpcId);
|
||||||
|
|
||||||
@ -258,6 +263,13 @@ pub enum NpcAction {
|
|||||||
Attack(Actor),
|
Attack(Actor),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Represents a message passed back to rtsim from an agent's brain
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum NpcInput {
|
||||||
|
Report(ReportId),
|
||||||
|
Interaction(Actor, Subject),
|
||||||
|
}
|
||||||
|
|
||||||
// Note: the `serde(name = "...")` is to minimise the length of field
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
// identifiers for the sake of rtsim persistence
|
// identifiers for the sake of rtsim persistence
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, enum_map::Enum)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, enum_map::Enum)]
|
||||||
|
@ -66,12 +66,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
ControlEvent::DisableLantern => {
|
ControlEvent::DisableLantern => {
|
||||||
server_emitter.emit(ServerEvent::DisableLantern(entity))
|
server_emitter.emit(ServerEvent::DisableLantern(entity))
|
||||||
},
|
},
|
||||||
ControlEvent::Interact(npc_uid) => {
|
ControlEvent::Interact(npc_uid, subject) => {
|
||||||
if let Some(npc_entity) = read_data
|
if let Some(npc_entity) = read_data
|
||||||
.uid_allocator
|
.uid_allocator
|
||||||
.retrieve_entity_internal(npc_uid.id())
|
.retrieve_entity_internal(npc_uid.id())
|
||||||
{
|
{
|
||||||
server_emitter.emit(ServerEvent::NpcInteract(entity, npc_entity));
|
server_emitter
|
||||||
|
.emit(ServerEvent::NpcInteract(entity, npc_entity, subject));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ControlEvent::InitiateInvite(inviter_uid, kind) => {
|
ControlEvent::InitiateInvite(inviter_uid, kind) => {
|
||||||
|
@ -5,7 +5,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
RtState,
|
RtState,
|
||||||
};
|
};
|
||||||
use common::resources::{Time, TimeOfDay};
|
use common::{
|
||||||
|
resources::{Time, TimeOfDay},
|
||||||
|
rtsim::NpcInput,
|
||||||
|
};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
@ -26,7 +29,7 @@ pub struct NpcCtx<'a> {
|
|||||||
pub npc_id: NpcId,
|
pub npc_id: NpcId,
|
||||||
pub npc: &'a Npc,
|
pub npc: &'a Npc,
|
||||||
pub controller: &'a mut Controller,
|
pub controller: &'a mut Controller,
|
||||||
pub inbox: &'a mut VecDeque<ReportId>, // TODO: Allow more inbox items
|
pub inbox: &'a mut VecDeque<NpcInput>, // TODO: Allow more inbox items
|
||||||
pub sentiments: &'a mut Sentiments,
|
pub sentiments: &'a mut Sentiments,
|
||||||
pub known_reports: &'a mut HashSet<ReportId>,
|
pub known_reports: &'a mut HashSet<ReportId>,
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ai::Action,
|
ai::Action,
|
||||||
data::{ReportId, Reports, Sentiments},
|
data::{Reports, Sentiments},
|
||||||
gen::name,
|
gen::name,
|
||||||
};
|
};
|
||||||
pub use common::rtsim::{NpcId, Profession};
|
pub use common::rtsim::{NpcId, Profession};
|
||||||
@ -9,8 +9,8 @@ use common::{
|
|||||||
comp,
|
comp,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{
|
rtsim::{
|
||||||
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, Role, SiteId,
|
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, NpcInput, Personality, ReportId,
|
||||||
VehicleId,
|
Role, SiteId, VehicleId,
|
||||||
},
|
},
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
@ -121,7 +121,7 @@ pub struct Npc {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub controller: Controller,
|
pub controller: Controller,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub inbox: VecDeque<ReportId>,
|
pub inbox: VecDeque<NpcInput>,
|
||||||
|
|
||||||
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
|
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
|
||||||
/// server, loaded corresponds to being within a loaded chunk). When in
|
/// server, loaded corresponds to being within a loaded chunk). When in
|
||||||
|
@ -4,7 +4,7 @@ use slotmap::HopSlotMap;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
slotmap::new_key_type! { pub struct ReportId; }
|
pub use common::rtsim::ReportId;
|
||||||
|
|
||||||
/// Represents a single piece of information known by an rtsim entity.
|
/// Represents a single piece of information known by an rtsim entity.
|
||||||
///
|
///
|
||||||
|
@ -13,10 +13,11 @@ use common::{
|
|||||||
astar::{Astar, PathResult},
|
astar::{Astar, PathResult},
|
||||||
comp::{
|
comp::{
|
||||||
compass::{Direction, Distance},
|
compass::{Direction, Distance},
|
||||||
|
dialogue::Subject,
|
||||||
Content,
|
Content,
|
||||||
},
|
},
|
||||||
path::Path,
|
path::Path,
|
||||||
rtsim::{Actor, ChunkResource, Profession, Role, SiteId},
|
rtsim::{Actor, ChunkResource, NpcInput, Profession, Role, SiteId},
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize},
|
terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize},
|
||||||
@ -542,6 +543,41 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
|||||||
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn talk_to(tgt: Actor, subject: Option<Subject>) -> impl Action {
|
||||||
|
now(move |ctx| {
|
||||||
|
// Mention nearby sites
|
||||||
|
let comment = if ctx.rng.gen_bool(0.3)
|
||||||
|
&& let Some(current_site) = ctx.npc.current_site
|
||||||
|
&& let Some(current_site) = ctx.state.data().sites.get(current_site)
|
||||||
|
&& let Some(mention_site) = current_site.nearby_sites_by_size.choose(&mut ctx.rng)
|
||||||
|
&& let Some(mention_site) = ctx.state.data().sites.get(*mention_site)
|
||||||
|
&& let Some(mention_site_name) = mention_site.world_site
|
||||||
|
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
||||||
|
{
|
||||||
|
Content::localized_with_args("npc-speech-tell_site", [
|
||||||
|
("site", Content::Plain(mention_site_name)),
|
||||||
|
("dir", Direction::from_dir(mention_site.wpos.as_() - ctx.npc.wpos.xy()).localize_npc()),
|
||||||
|
("dist", Distance::from_length(mention_site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
||||||
|
])
|
||||||
|
// Mention nearby monsters
|
||||||
|
} else if ctx.rng.gen_bool(0.3)
|
||||||
|
&& let Some(monster) = ctx.state.data().npcs
|
||||||
|
.values()
|
||||||
|
.filter(|other| matches!(&other.role, Role::Monster))
|
||||||
|
.min_by_key(|other| other.wpos.xy().distance(ctx.npc.wpos.xy()) as i32)
|
||||||
|
{
|
||||||
|
Content::localized_with_args("npc-speech-tell_monster", [
|
||||||
|
("body", monster.body.localize()),
|
||||||
|
("dir", Direction::from_dir(monster.wpos.xy() - ctx.npc.wpos.xy()).localize_npc()),
|
||||||
|
("dist", Distance::from_length(monster.wpos.xy().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
ctx.npc.personality.get_generic_comment(&mut ctx.rng)
|
||||||
|
};
|
||||||
|
just(move |ctx| ctx.controller.say(tgt, comment.clone()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn socialize() -> impl Action {
|
fn socialize() -> impl Action {
|
||||||
now(|ctx| {
|
now(|ctx| {
|
||||||
// Skip most socialising actions if we're not loaded
|
// Skip most socialising actions if we're not loaded
|
||||||
@ -563,36 +599,7 @@ fn socialize() -> impl Action {
|
|||||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
||||||
.choose(&mut ctx.rng)
|
.choose(&mut ctx.rng)
|
||||||
{
|
{
|
||||||
// Mention nearby sites
|
return talk_to(other, None)
|
||||||
let comment = if ctx.rng.gen_bool(0.3)
|
|
||||||
&& let Some(current_site) = ctx.npc.current_site
|
|
||||||
&& let Some(current_site) = ctx.state.data().sites.get(current_site)
|
|
||||||
&& let Some(mention_site) = current_site.nearby_sites_by_size.choose(&mut ctx.rng)
|
|
||||||
&& let Some(mention_site) = ctx.state.data().sites.get(*mention_site)
|
|
||||||
&& let Some(mention_site_name) = mention_site.world_site
|
|
||||||
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
|
||||||
{
|
|
||||||
Content::localized_with_args("npc-speech-tell_site", [
|
|
||||||
("site", Content::Plain(mention_site_name)),
|
|
||||||
("dir", Direction::from_dir(mention_site.wpos.as_() - ctx.npc.wpos.xy()).localize_npc()),
|
|
||||||
("dist", Distance::from_length(mention_site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
|
||||||
])
|
|
||||||
// Mention nearby monsters
|
|
||||||
} else if ctx.rng.gen_bool(0.3)
|
|
||||||
&& let Some(monster) = ctx.state.data().npcs
|
|
||||||
.values()
|
|
||||||
.filter(|other| matches!(&other.role, Role::Monster))
|
|
||||||
.min_by_key(|other| other.wpos.xy().distance(ctx.npc.wpos.xy()) as i32)
|
|
||||||
{
|
|
||||||
Content::localized_with_args("npc-speech-tell_monster", [
|
|
||||||
("body", monster.body.localize()),
|
|
||||||
("dir", Direction::from_dir(monster.wpos.xy() - ctx.npc.wpos.xy()).localize_npc()),
|
|
||||||
("dist", Distance::from_length(monster.wpos.xy().distance(ctx.npc.wpos.xy()) as i32).localize_npc()),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
ctx.npc.personality.get_generic_comment(&mut ctx.rng)
|
|
||||||
};
|
|
||||||
return just(move |ctx| ctx.controller.say(other, comment.clone()))
|
|
||||||
// After talking, wait for a while
|
// After talking, wait for a while
|
||||||
.then(idle().repeat().stop_if(timeout(4.0)))
|
.then(idle().repeat().stop_if(timeout(4.0)))
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@ -972,7 +979,7 @@ fn captain() -> impl Action {
|
|||||||
fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
||||||
loop {
|
loop {
|
||||||
match ctx.inbox.pop_front() {
|
match ctx.inbox.pop_front() {
|
||||||
Some(report_id) if !ctx.known_reports.contains(&report_id) => {
|
Some(NpcInput::Report(report_id)) if !ctx.known_reports.contains(&report_id) => {
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
match ctx.state.data().reports.get(report_id).map(|r| r.kind) {
|
match ctx.state.data().reports.get(report_id).map(|r| r.kind) {
|
||||||
Some(ReportKind::Death { killer, actor, .. })
|
Some(ReportKind::Death { killer, actor, .. })
|
||||||
@ -1013,15 +1020,17 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
"npc-speech-witness_death"
|
"npc-speech-witness_death"
|
||||||
};
|
};
|
||||||
ctx.known_reports.insert(report_id);
|
ctx.known_reports.insert(report_id);
|
||||||
break Some(just(move |ctx| {
|
break Some(
|
||||||
ctx.controller.say(killer, Content::localized(phrase))
|
just(move |ctx| ctx.controller.say(killer, Content::localized(phrase)))
|
||||||
}));
|
.l(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
Some(ReportKind::Death { .. }) => {}, // We don't care about death
|
Some(ReportKind::Death { .. }) => {}, // We don't care about death
|
||||||
None => {}, // Stale report, ignore
|
None => {}, // Stale report, ignore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(_) => {}, // Reports we already know of are ignored
|
Some(NpcInput::Report(_)) => {}, // Reports we already know of are ignored
|
||||||
|
Some(NpcInput::Interaction(by, subject)) => break Some(talk_to(by, Some(subject)).r()),
|
||||||
None => break None,
|
None => break None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
event::{EventCtx, OnDeath},
|
event::{EventCtx, OnDeath},
|
||||||
RtState, Rule, RuleError,
|
RtState, Rule, RuleError,
|
||||||
};
|
};
|
||||||
|
use common::rtsim::NpcInput;
|
||||||
|
|
||||||
pub struct ReportEvents;
|
pub struct ReportEvents;
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ fn on_death(ctx: EventCtx<ReportEvents, OnDeath>) {
|
|||||||
// data structure in their own time.
|
// data structure in their own time.
|
||||||
for npc_id in nearby {
|
for npc_id in nearby {
|
||||||
if let Some(npc) = data.npcs.get_mut(npc_id) {
|
if let Some(npc) = data.npcs.get_mut(npc_id) {
|
||||||
npc.inbox.push_back(report);
|
npc.inbox.push_back(NpcInput::Report(report));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,11 @@ use crate::{
|
|||||||
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
||||||
RtState, Rule, RuleError,
|
RtState, Rule, RuleError,
|
||||||
};
|
};
|
||||||
use common::{grid::Grid, rtsim::Actor, terrain::CoordinateConversions};
|
use common::{
|
||||||
|
grid::Grid,
|
||||||
|
rtsim::{Actor, NpcInput},
|
||||||
|
terrain::CoordinateConversions,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SyncNpcs;
|
pub struct SyncNpcs;
|
||||||
|
|
||||||
@ -124,7 +128,8 @@ fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
|||||||
npc.inbox.extend(site.known_reports
|
npc.inbox.extend(site.known_reports
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|report| !npc.known_reports.contains(report)));
|
.filter(|report| !npc.known_reports.contains(report))
|
||||||
|
.map(NpcInput::Report));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,12 @@ pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_entity: EcsEntity) {
|
pub fn handle_npc_interaction(
|
||||||
|
server: &mut Server,
|
||||||
|
interactor: EcsEntity,
|
||||||
|
npc_entity: EcsEntity,
|
||||||
|
subject: Subject,
|
||||||
|
) {
|
||||||
let state = server.state_mut();
|
let state = server.state_mut();
|
||||||
if let Some(agent) = state
|
if let Some(agent) = state
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -89,7 +94,7 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en
|
|||||||
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
|
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
|
||||||
agent
|
agent
|
||||||
.inbox
|
.inbox
|
||||||
.push_back(AgentEvent::Talk(interactor_uid, Subject::Regular));
|
.push_back(AgentEvent::Talk(interactor_uid, subject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,8 +123,8 @@ impl Server {
|
|||||||
},
|
},
|
||||||
ServerEvent::EnableLantern(entity) => handle_lantern(self, entity, true),
|
ServerEvent::EnableLantern(entity) => handle_lantern(self, entity, true),
|
||||||
ServerEvent::DisableLantern(entity) => handle_lantern(self, entity, false),
|
ServerEvent::DisableLantern(entity) => handle_lantern(self, entity, false),
|
||||||
ServerEvent::NpcInteract(interactor, target) => {
|
ServerEvent::NpcInteract(interactor, target, subject) => {
|
||||||
handle_npc_interaction(self, interactor, target)
|
handle_npc_interaction(self, interactor, target, subject)
|
||||||
},
|
},
|
||||||
ServerEvent::InitiateInvite(interactor, target, kind) => {
|
ServerEvent::InitiateInvite(interactor, target, kind) => {
|
||||||
handle_invite(self, interactor, target, kind)
|
handle_invite(self, interactor, target, kind)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::sys::terrain::NpcData;
|
use crate::sys::terrain::NpcData;
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body, Presence, PresenceKind},
|
comp::{self, Agent, Body, Presence, PresenceKind},
|
||||||
event::{EventBus, NpcBuilder, ServerEvent},
|
event::{EventBus, NpcBuilder, ServerEvent},
|
||||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
||||||
resources::{DeltaTime, Time, TimeOfDay},
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
@ -314,7 +314,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
.with_health(health)
|
.with_health(health)
|
||||||
.with_poise(poise)
|
.with_poise(poise)
|
||||||
.with_inventory(inventory)
|
.with_inventory(inventory)
|
||||||
.with_agent(agent)
|
.with_agent(agent.map(|agent| Agent {
|
||||||
|
rtsim_outbox: Some(Default::default()),
|
||||||
|
..agent
|
||||||
|
}))
|
||||||
.with_scale(scale)
|
.with_scale(scale)
|
||||||
.with_loot(loot)
|
.with_loot(loot)
|
||||||
.with_rtsim(RtSimEntity(npc_id)),
|
.with_rtsim(RtSimEntity(npc_id)),
|
||||||
@ -373,7 +376,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
.with_health(health)
|
.with_health(health)
|
||||||
.with_poise(poise)
|
.with_poise(poise)
|
||||||
.with_inventory(inventory)
|
.with_inventory(inventory)
|
||||||
.with_agent(agent)
|
.with_agent(agent.map(|agent| Agent {
|
||||||
|
rtsim_outbox: Some(Default::default()),
|
||||||
|
..agent
|
||||||
|
}))
|
||||||
.with_scale(scale)
|
.with_scale(scale)
|
||||||
.with_loot(loot)
|
.with_loot(loot)
|
||||||
.with_rtsim(RtSimEntity(npc_id)),
|
.with_rtsim(RtSimEntity(npc_id)),
|
||||||
@ -417,6 +423,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
.rtsim_controller
|
.rtsim_controller
|
||||||
.actions
|
.actions
|
||||||
.extend(std::mem::take(&mut npc.controller.actions));
|
.extend(std::mem::take(&mut npc.controller.actions));
|
||||||
|
if let Some(rtsim_outbox) = &mut agent.rtsim_outbox {
|
||||||
|
npc.inbox.append(rtsim_outbox);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use common::{
|
|||||||
UtteranceKind,
|
UtteranceKind,
|
||||||
},
|
},
|
||||||
event::ServerEvent,
|
event::ServerEvent,
|
||||||
rtsim::PersonalityTrait,
|
rtsim::{Actor, NpcInput, PersonalityTrait},
|
||||||
trade::{TradeAction, TradePhase, TradeResult},
|
trade::{TradeAction, TradePhase, TradeResult},
|
||||||
};
|
};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
@ -87,8 +87,26 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(AgentEvent::Talk(by, subject)) = agent.inbox.pop_front() {
|
if let Some(AgentEvent::Talk(by, subject)) = agent.inbox.pop_front() {
|
||||||
|
let by_entity = get_entity_by_id(by.id(), read_data);
|
||||||
|
|
||||||
|
if let Some(rtsim_outbox) = &mut agent.rtsim_outbox {
|
||||||
|
if let Subject::Regular
|
||||||
|
| Subject::Mood
|
||||||
|
| Subject::Work = subject
|
||||||
|
&& let Some(by_entity) = by_entity
|
||||||
|
&& let Some(actor) = read_data.presences
|
||||||
|
.get(by_entity)
|
||||||
|
.and_then(|p| p.kind.character_id().map(Actor::Character))
|
||||||
|
.or_else(|| Some(Actor::Npc(read_data.rtsim_entities
|
||||||
|
.get(by_entity)?.0)))
|
||||||
|
{
|
||||||
|
rtsim_outbox.push_back(NpcInput::Interaction(actor, subject));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if agent.allowed_to_speak() {
|
if agent.allowed_to_speak() {
|
||||||
if let Some(target) = get_entity_by_id(by.id(), read_data) {
|
if let Some(target) = by_entity {
|
||||||
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
|
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
|
||||||
|
|
||||||
agent.target = Some(Target::new(
|
agent.target = Some(Target::new(
|
||||||
@ -216,15 +234,18 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
if let Some(src_pos) = read_data.positions.get(target) {
|
if let Some(src_pos) = read_data.positions.get(target) {
|
||||||
// TODO: Localise
|
// TODO: Localise
|
||||||
let msg = if let Some(person_pos) = person.origin {
|
let msg = if let Some(person_pos) = person.origin {
|
||||||
let distance = Distance::from_dir(person_pos.xy() - src_pos.0.xy());
|
let distance =
|
||||||
|
Distance::from_dir(person_pos.xy().as_() - src_pos.0.xy());
|
||||||
match distance {
|
match distance {
|
||||||
Distance::NextTo | Distance::Near => {
|
Distance::NextTo | Distance::Near => {
|
||||||
format!(
|
format!(
|
||||||
"{} ? I think he's {} {} from here!",
|
"{} ? I think he's {} {} from here!",
|
||||||
person.name(),
|
person.name(),
|
||||||
distance.name(),
|
distance.name(),
|
||||||
Direction::from_dir(person_pos.xy() - src_pos.0.xy(),)
|
Direction::from_dir(
|
||||||
.name()
|
person_pos.xy().as_() - src_pos.0.xy()
|
||||||
|
)
|
||||||
|
.name()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -15,6 +15,7 @@ use client::{self, Client};
|
|||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
comp::{
|
comp::{
|
||||||
|
dialogue::Subject,
|
||||||
inventory::slot::{EquipSlot, Slot},
|
inventory::slot::{EquipSlot, Slot},
|
||||||
invite::InviteKind,
|
invite::InviteKind,
|
||||||
item::{tool::ToolKind, ItemDesc},
|
item::{tool::ToolKind, ItemDesc},
|
||||||
@ -959,7 +960,7 @@ impl PlayState for SessionState {
|
|||||||
// TODO: maybe start crafting instead?
|
// TODO: maybe start crafting instead?
|
||||||
client.toggle_sit();
|
client.toggle_sit();
|
||||||
} else {
|
} else {
|
||||||
client.npc_interact(*entity);
|
client.npc_interact(*entity, Subject::Regular);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user