mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Localised rtsim NPC speech
This commit is contained in:
parent
edcc2f1870
commit
cf701fb604
@ -223,3 +223,35 @@ npc-speech-prisoner =
|
|||||||
.a2 = That Cardinal can't be trusted.
|
.a2 = That Cardinal can't be trusted.
|
||||||
.a3 = These Clerics are up to no good.
|
.a3 = These Clerics are up to no good.
|
||||||
.a4 = I wish i still had my pick!
|
.a4 = I wish i still had my pick!
|
||||||
|
npc-speech-moving_on =
|
||||||
|
.a0 = I've spent enough time here, onward to { $site }!
|
||||||
|
npc-speech-night_time =
|
||||||
|
.a0 = It's dark, time to head home.
|
||||||
|
.a1 = I'm tired.
|
||||||
|
.a2 = My bed beckons!
|
||||||
|
npc-speech-day_time =
|
||||||
|
.a0 = A new day begins!
|
||||||
|
.a1 = I never liked waking up...
|
||||||
|
npc-speech-start_hunting =
|
||||||
|
.a0 = Time to go hunting!
|
||||||
|
npc-speech-guard_thought =
|
||||||
|
.a0 = My brother's out fighting ogres. What do I get? Guard duty...
|
||||||
|
.a1 = Just one more patrol, then I can head home.
|
||||||
|
.a2 = No bandits are going to get past me.
|
||||||
|
npc-speech-merchant_sell_undirected =
|
||||||
|
.a0 = All my goods are of the highest quality!
|
||||||
|
.a1 = Does anybody want to buy my wares?
|
||||||
|
.a2 = I've got the best offers in town.
|
||||||
|
.a3 = Looking for supplies? I've got you covered.
|
||||||
|
npc-speech-merchant_sell_directed =
|
||||||
|
.a0 = You there! Are you in need of a new thingamabob?
|
||||||
|
.a1 = Are you hungry? I'm sure I've got some cheese you can buy.
|
||||||
|
.a2 = You look like you could do with some new armour!
|
||||||
|
npc-speech-witness_murder =
|
||||||
|
.a0 = Murderer!
|
||||||
|
.a1 = How could you do this?
|
||||||
|
.a2 = Aaargh!
|
||||||
|
npc-speech-witness_death =
|
||||||
|
.a0 = No!
|
||||||
|
.a1 = This is terrible!
|
||||||
|
.a2 = Oh my goodness!
|
||||||
|
@ -200,10 +200,12 @@ pub enum Content {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Plain` explicit (to discourage it)
|
||||||
impl From<String> for Content {
|
impl From<String> for Content {
|
||||||
fn from(text: String) -> Self { Self::Plain(text) }
|
fn from(text: String) -> Self { Self::Plain(text) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Plain` explicit (to discourage it)
|
||||||
impl<'a> From<&'a str> for Content {
|
impl<'a> From<&'a str> for Content {
|
||||||
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||||
}
|
}
|
||||||
@ -212,7 +214,7 @@ impl Content {
|
|||||||
pub fn localized(key: impl ToString) -> Self {
|
pub fn localized(key: impl ToString) -> Self {
|
||||||
Self::Localized {
|
Self::Localized {
|
||||||
key: key.to_string(),
|
key: key.to_string(),
|
||||||
r: rand::random(),
|
seed: rand::random(),
|
||||||
args: HashMap::default(),
|
args: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +225,7 @@ impl Content {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self::Localized {
|
Self::Localized {
|
||||||
key: key.to_string(),
|
key: key.to_string(),
|
||||||
r: rand::random(),
|
seed: rand::random(),
|
||||||
args: args
|
args: args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
// `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;
|
use crate::{character::CharacterId, comp::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;
|
||||||
use std::{borrow::Cow, collections::VecDeque};
|
use std::collections::VecDeque;
|
||||||
use strum::{EnumIter, IntoEnumIterator};
|
use strum::{EnumIter, IntoEnumIterator};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -169,6 +169,33 @@ impl Personality {
|
|||||||
pub fn will_ambush(&self) -> bool {
|
pub fn will_ambush(&self) -> bool {
|
||||||
self.agreeableness < Self::LOW_THRESHOLD && self.conscientiousness < Self::LOW_THRESHOLD
|
self.agreeableness < Self::LOW_THRESHOLD && self.conscientiousness < Self::LOW_THRESHOLD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_generic_comment(&self, rng: &mut impl Rng) -> Content {
|
||||||
|
let i18n_key = if let Some(extreme_trait) = self.chat_trait(rng) {
|
||||||
|
match extreme_trait {
|
||||||
|
PersonalityTrait::Open => "npc-speech-villager_open",
|
||||||
|
PersonalityTrait::Adventurous => "npc-speech-villager_adventurous",
|
||||||
|
PersonalityTrait::Closed => "npc-speech-villager_closed",
|
||||||
|
PersonalityTrait::Conscientious => "npc-speech-villager_conscientious",
|
||||||
|
PersonalityTrait::Busybody => "npc-speech-villager_busybody",
|
||||||
|
PersonalityTrait::Unconscientious => "npc-speech-villager_unconscientious",
|
||||||
|
PersonalityTrait::Extroverted => "npc-speech-villager_extroverted",
|
||||||
|
PersonalityTrait::Introverted => "npc-speech-villager_introverted",
|
||||||
|
PersonalityTrait::Agreeable => "npc-speech-villager_agreeable",
|
||||||
|
PersonalityTrait::Sociable => "npc-speech-villager_sociable",
|
||||||
|
PersonalityTrait::Disagreeable => "npc-speech-villager_disagreeable",
|
||||||
|
PersonalityTrait::Neurotic => "npc-speech-villager_neurotic",
|
||||||
|
PersonalityTrait::Seeker => "npc-speech-villager_seeker",
|
||||||
|
PersonalityTrait::SadLoner => "npc-speech-villager_sad_loner",
|
||||||
|
PersonalityTrait::Worried => "npc-speech-villager_worried",
|
||||||
|
PersonalityTrait::Stable => "npc-speech-villager_stable",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"npc-speech-villager"
|
||||||
|
};
|
||||||
|
|
||||||
|
Content::localized(i18n_key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Personality {
|
impl Default for Personality {
|
||||||
@ -226,7 +253,7 @@ pub enum NpcAction {
|
|||||||
/// Speak the given message, with an optional target for that speech.
|
/// Speak the given message, with an optional target for that speech.
|
||||||
// TODO: Use some sort of structured, language-independent value that frontends can translate
|
// TODO: Use some sort of structured, language-independent value that frontends can translate
|
||||||
// instead
|
// instead
|
||||||
Say(Option<Actor>, Cow<'static, str>),
|
Say(Option<Actor>, Content),
|
||||||
/// Attack the given target
|
/// Attack the given target
|
||||||
Attack(Actor),
|
Attack(Actor),
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ use rand::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slotmap::HopSlotMap;
|
use slotmap::HopSlotMap;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
@ -74,8 +73,8 @@ impl Controller {
|
|||||||
|
|
||||||
pub fn do_dance(&mut self) { self.activity = Some(NpcActivity::Dance); }
|
pub fn do_dance(&mut self) { self.activity = Some(NpcActivity::Dance); }
|
||||||
|
|
||||||
pub fn say(&mut self, target: impl Into<Option<Actor>>, msg: impl Into<Cow<'static, str>>) {
|
pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
|
||||||
self.actions.push(NpcAction::Say(target.into(), msg.into()));
|
self.actions.push(NpcAction::Say(target.into(), content));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack(&mut self, target: impl Into<Actor>) {
|
pub fn attack(&mut self, target: impl Into<Actor>) {
|
||||||
|
@ -11,6 +11,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
astar::{Astar, PathResult},
|
astar::{Astar, PathResult},
|
||||||
|
comp::Content,
|
||||||
path::Path,
|
path::Path,
|
||||||
rtsim::{ChunkResource, Profession, SiteId},
|
rtsim::{ChunkResource, Profession, SiteId},
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
@ -470,15 +471,17 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
|||||||
|
|
||||||
fn socialize() -> impl Action {
|
fn socialize() -> impl Action {
|
||||||
now(|ctx| {
|
now(|ctx| {
|
||||||
// TODO: Bit odd, should wait for a while after greeting
|
// Skip most socialising actions if we're not loaded
|
||||||
if ctx.rng.gen_bool(0.002) {
|
if matches!(ctx.npc.mode, SimulationMode::Loaded) && ctx.rng.gen_bool(0.002) {
|
||||||
if ctx.rng.gen_bool(0.15) {
|
if ctx.rng.gen_bool(0.15) {
|
||||||
return just(|ctx| ctx.controller.do_dance())
|
return Either::Left(
|
||||||
.repeat()
|
just(|ctx| ctx.controller.do_dance())
|
||||||
.stop_if(timeout(6.0))
|
.repeat()
|
||||||
.debug(|| "dancing")
|
.stop_if(timeout(6.0))
|
||||||
.map(|_| ())
|
.debug(|| "dancing")
|
||||||
.boxed();
|
.map(|_| ())
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
} else if let Some(other) = ctx
|
} else if let Some(other) = ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
@ -486,12 +489,17 @@ 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)
|
||||||
{
|
{
|
||||||
return just(move |ctx| ctx.controller.say(other, "npc-speech-villager_open"))
|
return Either::Left(
|
||||||
.boxed();
|
just(move |ctx| ctx.controller.say(other, ctx.npc.personality.get_generic_comment(&mut ctx.rng)))
|
||||||
|
// After greeting the actor, wait for a while
|
||||||
|
.then(idle().repeat().stop_if(timeout(4.0)))
|
||||||
|
.map(|_| ())
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idle().boxed()
|
Either::Right(idle())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,7 +536,7 @@ fn adventure() -> impl Action {
|
|||||||
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
// Travel to the site
|
// Travel to the site
|
||||||
important(just(move |ctx| ctx.controller.say(None, format!("I've spent enough time here, onward to {}!", site_name)))
|
important(just(move |ctx| ctx.controller.say(None, Content::localized_with_args("npc-speech-moving_on", [("site", site_name.clone())])))
|
||||||
.then(travel_to_site(tgt_site, 0.6))
|
.then(travel_to_site(tgt_site, 0.6))
|
||||||
// Stop for a few minutes
|
// Stop for a few minutes
|
||||||
.then(villager(tgt_site).repeat().stop_if(timeout(wait_time)))
|
.then(villager(tgt_site).repeat().stop_if(timeout(wait_time)))
|
||||||
@ -634,14 +642,20 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
just(|ctx| ctx.controller.say(None, "It's dark, time to go home"))
|
just(|ctx| {
|
||||||
.then(travel_to_point(house_wpos, 0.65))
|
ctx.controller
|
||||||
.debug(|| "walk to house")
|
.say(None, Content::localized("npc-speech-night_time"))
|
||||||
.then(socialize().repeat().debug(|| "wait in house"))
|
})
|
||||||
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
.then(travel_to_point(house_wpos, 0.65))
|
||||||
.then(just(|ctx| ctx.controller.say(None, "A new day begins!")))
|
.debug(|| "walk to house")
|
||||||
.map(|_| ())
|
.then(socialize().repeat().debug(|| "wait in house"))
|
||||||
.boxed()
|
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||||
|
.then(just(|ctx| {
|
||||||
|
ctx.controller
|
||||||
|
.say(None, Content::localized("npc-speech-day_time"))
|
||||||
|
}))
|
||||||
|
.map(|_| ())
|
||||||
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
finish().boxed()
|
finish().boxed()
|
||||||
}
|
}
|
||||||
@ -665,14 +679,17 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
} else if matches!(ctx.npc.profession, Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
} else if matches!(ctx.npc.profession, Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
||||||
if let Some(forest_wpos) = find_forest(ctx) {
|
if let Some(forest_wpos) = find_forest(ctx) {
|
||||||
return casual(
|
return casual(
|
||||||
just(|ctx| ctx.controller.say(None, "Time to go hunting!"))
|
just(|ctx| {
|
||||||
.then(travel_to_point(forest_wpos, 0.75))
|
ctx.controller
|
||||||
.debug(|| "walk to forest")
|
.say(None, Content::localized("npc-speech-start_hunting"))
|
||||||
.then({
|
})
|
||||||
let wait_time = ctx.rng.gen_range(30.0..60.0);
|
.then(travel_to_point(forest_wpos, 0.75))
|
||||||
hunt_animals().repeat().stop_if(timeout(wait_time))
|
.debug(|| "walk to forest")
|
||||||
})
|
.then({
|
||||||
.map(|_| ()),
|
let wait_time = ctx.rng.gen_range(30.0..60.0);
|
||||||
|
hunt_animals().repeat().stop_if(timeout(wait_time))
|
||||||
|
})
|
||||||
|
.map(|_| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession, Some(Profession::Guard)) && ctx.rng.gen_bool(0.5) {
|
} else if matches!(ctx.npc.profession, Some(Profession::Guard)) && ctx.rng.gen_bool(0.5) {
|
||||||
@ -682,15 +699,10 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.debug(|| "patrol")
|
.debug(|| "patrol")
|
||||||
.interrupt_with(|ctx| {
|
.interrupt_with(|ctx| {
|
||||||
if ctx.rng.gen_bool(0.0003) {
|
if ctx.rng.gen_bool(0.0003) {
|
||||||
let phrase = *[
|
Some(just(move |ctx| {
|
||||||
"My brother's out fighting ogres. What do I get? Guard duty...",
|
ctx.controller
|
||||||
"Just one more patrol, then I can head home",
|
.say(None, Content::localized("npc-speech-guard_thought"))
|
||||||
"No bandits are going to get past me",
|
}))
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.choose(&mut ctx.rng)
|
|
||||||
.unwrap(); // Can't fail
|
|
||||||
Some(just(move |ctx| ctx.controller.say(None, phrase)))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -703,32 +715,20 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
return casual(
|
return casual(
|
||||||
just(|ctx| {
|
just(|ctx| {
|
||||||
// Try to direct our speech at nearby actors, if there are any
|
// Try to direct our speech at nearby actors, if there are any
|
||||||
let (target, phrases) = if ctx.rng.gen_bool(0.3) && let Some(other) = ctx
|
let (target, phrase) = if ctx.rng.gen_bool(0.3) && let Some(other) = ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
.npcs
|
.npcs
|
||||||
.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)
|
||||||
{
|
{
|
||||||
(Some(other), &[
|
(Some(other), "npc-speech-merchant_sell_directed")
|
||||||
"You there! Are you in need of a new thingamabob?",
|
|
||||||
"Are you hungry? I'm sure I've got some cheese you can buy",
|
|
||||||
"You look like you could do with some new armour!",
|
|
||||||
][..])
|
|
||||||
// Otherwise, resort to generic expressions
|
|
||||||
} else {
|
} else {
|
||||||
(None, &[
|
// Otherwise, resort to generic expressions
|
||||||
"All my goods are of the highest quality!",
|
(None, "npc-speech-merchant_sell_undirected")
|
||||||
"Does anybody want to buy my wares?",
|
|
||||||
"I've got the best offers in town",
|
|
||||||
"Looking for supplies? I've got you covered",
|
|
||||||
][..])
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.controller.say(
|
ctx.controller.say(target, Content::localized(phrase));
|
||||||
target,
|
|
||||||
*phrases.iter().choose(&mut ctx.rng).unwrap(), // Can't fail
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.then(idle().repeat().stop_if(timeout(8.0)))
|
.then(idle().repeat().stop_if(timeout(8.0)))
|
||||||
.repeat()
|
.repeat()
|
||||||
@ -898,16 +898,17 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
Some(ReportKind::Death { killer, .. }) => {
|
Some(ReportKind::Death { killer, .. }) => {
|
||||||
// TODO: Sentiment should be positive if we didn't like actor that died
|
// TODO: Sentiment should be positive if we didn't like actor that died
|
||||||
// TODO: Don't report self
|
// TODO: Don't report self
|
||||||
let phrases = if let Some(killer) = killer {
|
let phrase = if let Some(killer) = killer {
|
||||||
// TODO: Don't hard-code sentiment change
|
// TODO: Don't hard-code sentiment change
|
||||||
ctx.sentiments.change_by(killer, -0.7, Sentiment::VILLAIN);
|
ctx.sentiments.change_by(killer, -0.7, Sentiment::VILLAIN);
|
||||||
&["Murderer!", "How could you do this?", "Aaargh!"][..]
|
"npc-speech-witness_murder"
|
||||||
} else {
|
} else {
|
||||||
&["No!", "This is terrible!", "Oh my goodness!"][..]
|
"npc-speech-witness_death"
|
||||||
};
|
};
|
||||||
let phrase = *phrases.iter().choose(&mut ctx.rng).unwrap(); // Can't fail
|
|
||||||
ctx.known_reports.insert(report_id);
|
ctx.known_reports.insert(report_id);
|
||||||
break Some(just(move |ctx| ctx.controller.say(killer, phrase)));
|
break Some(just(move |ctx| {
|
||||||
|
ctx.controller.say(killer, Content::localized(phrase))
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
None => {}, // Stale report, ignore
|
None => {}, // Stale report, ignore
|
||||||
}
|
}
|
||||||
|
@ -731,7 +731,7 @@ impl<'a> AgentData<'a> {
|
|||||||
} else if can_ambush(entity, read_data) {
|
} else if can_ambush(entity, read_data) {
|
||||||
controller.clone().push_utterance(UtteranceKind::Ambush);
|
controller.clone().push_utterance(UtteranceKind::Ambush);
|
||||||
self.chat_npc_if_allowed_to_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-ambush".to_string(),
|
Content::localized("npc-speech-ambush"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -1517,7 +1517,7 @@ impl<'a> AgentData<'a> {
|
|||||||
|
|
||||||
pub fn chat_npc_if_allowed_to_speak(
|
pub fn chat_npc_if_allowed_to_speak(
|
||||||
&self,
|
&self,
|
||||||
msg: impl ToString,
|
msg: Content,
|
||||||
agent: &Agent,
|
agent: &Agent,
|
||||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@ -1529,10 +1529,9 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chat_npc(&self, key: impl ToString, event_emitter: &mut Emitter<'_, ServerEvent>) {
|
pub fn chat_npc(&self, content: Content, event_emitter: &mut Emitter<'_, ServerEvent>) {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||||
*self.uid,
|
*self.uid, content,
|
||||||
Content::localized(key),
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1561,13 +1560,13 @@ impl<'a> AgentData<'a> {
|
|||||||
// FIXME: If going to use "cultist + low health + fleeing" string, make sure
|
// FIXME: If going to use "cultist + low health + fleeing" string, make sure
|
||||||
// they are each true.
|
// they are each true.
|
||||||
self.chat_npc_if_allowed_to_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-cultist_low_health_fleeing",
|
Content::localized("npc-speech-cultist_low_health_fleeing"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
} else if is_villager(self.alignment) {
|
} else if is_villager(self.alignment) {
|
||||||
self.chat_npc_if_allowed_to_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_under_attack",
|
Content::localized("npc-speech-villager_under_attack"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -1582,7 +1581,7 @@ impl<'a> AgentData<'a> {
|
|||||||
) {
|
) {
|
||||||
if is_villager(self.alignment) {
|
if is_villager(self.alignment) {
|
||||||
self.chat_npc_if_allowed_to_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_enemy_killed",
|
Content::localized("npc-speech-villager_enemy_killed"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -1756,16 +1755,22 @@ impl<'a> AgentData<'a> {
|
|||||||
let move_dir = controller.inputs.move_dir;
|
let move_dir = controller.inputs.move_dir;
|
||||||
let move_dir_mag = move_dir.magnitude();
|
let move_dir_mag = move_dir.magnitude();
|
||||||
let small_chance = rng.gen::<f32>() < read_data.dt.0 * 0.25;
|
let small_chance = rng.gen::<f32>() < read_data.dt.0 * 0.25;
|
||||||
let mut chat = |msg: &str| {
|
let mut chat = |content: Content| {
|
||||||
self.chat_npc_if_allowed_to_speak(msg.to_string(), agent, event_emitter);
|
self.chat_npc_if_allowed_to_speak(content, agent, event_emitter);
|
||||||
};
|
};
|
||||||
let mut chat_villager_remembers_fighting = || {
|
let mut chat_villager_remembers_fighting = || {
|
||||||
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
|
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
|
||||||
|
|
||||||
|
// TODO: Localise
|
||||||
if let Some(tgt_name) = tgt_name {
|
if let Some(tgt_name) = tgt_name {
|
||||||
chat(format!("{}! How dare you cross me again!", &tgt_name).as_str());
|
chat(Content::Plain(format!(
|
||||||
|
"{}! How dare you cross me again!",
|
||||||
|
&tgt_name
|
||||||
|
)));
|
||||||
} else {
|
} else {
|
||||||
chat("You! How dare you cross me again!");
|
chat(Content::Plain(
|
||||||
|
"You! How dare you cross me again!".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1782,12 +1787,12 @@ impl<'a> AgentData<'a> {
|
|||||||
if remembers_fight_with_target {
|
if remembers_fight_with_target {
|
||||||
chat_villager_remembers_fighting();
|
chat_villager_remembers_fighting();
|
||||||
} else if is_dressed_as_cultist(target, read_data) {
|
} else if is_dressed_as_cultist(target, read_data) {
|
||||||
chat("npc-speech-villager_cultist_alarm");
|
chat(Content::localized("npc-speech-villager_cultist_alarm"));
|
||||||
} else {
|
} else {
|
||||||
chat("npc-speech-menacing");
|
chat(Content::localized("npc-speech-menacing"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chat("npc-speech-menacing");
|
chat(Content::localized("npc-speech-menacing"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,56 +155,17 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
} else {
|
} else {
|
||||||
standard_response_msg()
|
standard_response_msg()
|
||||||
};
|
};
|
||||||
agent_data.chat_npc(msg, event_emitter);
|
// TODO: Localise
|
||||||
|
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||||
} else {
|
} else {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if let Some(extreme_trait) =
|
agent_data.chat_npc(
|
||||||
agent.rtsim_controller.personality.chat_trait(&mut rng)
|
agent
|
||||||
{
|
.rtsim_controller
|
||||||
let msg = match extreme_trait {
|
.personality
|
||||||
PersonalityTrait::Open => "npc-speech-villager_open",
|
.get_generic_comment(&mut rng),
|
||||||
PersonalityTrait::Adventurous => {
|
event_emitter,
|
||||||
"npc-speech-villager_adventurous"
|
);
|
||||||
},
|
|
||||||
PersonalityTrait::Closed => "npc-speech-villager_closed",
|
|
||||||
PersonalityTrait::Conscientious => {
|
|
||||||
"npc-speech-villager_conscientious"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Busybody => {
|
|
||||||
"npc-speech-villager_busybody"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Unconscientious => {
|
|
||||||
"npc-speech-villager_unconscientious"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Extroverted => {
|
|
||||||
"npc-speech-villager_extroverted"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Introverted => {
|
|
||||||
"npc-speech-villager_introverted"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Agreeable => {
|
|
||||||
"npc-speech-villager_agreeable"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Sociable => {
|
|
||||||
"npc-speech-villager_sociable"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Disagreeable => {
|
|
||||||
"npc-speech-villager_disagreeable"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Neurotic => {
|
|
||||||
"npc-speech-villager_neurotic"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Seeker => "npc-speech-villager_seeker",
|
|
||||||
PersonalityTrait::SadLoner => {
|
|
||||||
"npc-speech-villager_sad_loner"
|
|
||||||
},
|
|
||||||
PersonalityTrait::Worried => "npc-speech-villager_worried",
|
|
||||||
PersonalityTrait::Stable => "npc-speech-villager_stable",
|
|
||||||
};
|
|
||||||
agent_data.chat_npc(msg, event_emitter);
|
|
||||||
} else {
|
|
||||||
agent_data.chat_npc("npc-speech-villager", event_emitter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -213,13 +174,13 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_advertisement",
|
Content::localized("npc-speech-merchant_advertisement"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_busy",
|
Content::localized("npc-speech-merchant_busy"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -228,7 +189,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
// TODO: maybe make some travellers willing to trade with
|
// TODO: maybe make some travellers willing to trade with
|
||||||
// simpler goods like potions
|
// simpler goods like potions
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_decline_trade",
|
Content::localized("npc-speech-villager_decline_trade"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -243,15 +204,17 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
let dist = Distance::from_dir(raw_dir).name();
|
let dist = Distance::from_dir(raw_dir).name();
|
||||||
let dir = Direction::from_dir(raw_dir).name();
|
let dir = Direction::from_dir(raw_dir).name();
|
||||||
|
|
||||||
|
// TODO: Localise
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} ? I think it's {} {} from here!",
|
"{} ? I think it's {} {} from here!",
|
||||||
location.name, dist, dir
|
location.name, dist, dir
|
||||||
);
|
);
|
||||||
agent_data.chat_npc(msg, event_emitter);
|
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Person(person) => {
|
Subject::Person(person) => {
|
||||||
if let Some(src_pos) = read_data.positions.get(target) {
|
if let Some(src_pos) = read_data.positions.get(target) {
|
||||||
|
// 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() - src_pos.0.xy());
|
||||||
match distance {
|
match distance {
|
||||||
@ -278,7 +241,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
person.name()
|
person.name()
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
agent_data.chat_npc(msg, event_emitter);
|
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Work => {},
|
Subject::Work => {},
|
||||||
@ -330,7 +293,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
|
|||||||
} else {
|
} else {
|
||||||
controller.push_invite_response(InviteResponse::Decline);
|
controller.push_invite_response(InviteResponse::Decline);
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_busy",
|
Content::localized("npc-speech-merchant_busy"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -339,7 +302,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
|
|||||||
// TODO: Provide a hint where to find the closest merchant?
|
// TODO: Provide a hint where to find the closest merchant?
|
||||||
controller.push_invite_response(InviteResponse::Decline);
|
controller.push_invite_response(InviteResponse::Decline);
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_decline_trade",
|
Content::localized("npc-speech-villager_decline_trade"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -396,14 +359,14 @@ pub fn handle_inbox_finished_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
match result {
|
match result {
|
||||||
TradeResult::Completed => {
|
TradeResult::Completed => {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_trade_successful",
|
Content::localized("npc-speech-merchant_trade_successful"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_trade_declined",
|
Content::localized("npc-speech-merchant_trade_declined"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -435,7 +398,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
let (tradeid, pending, prices, inventories) = *boxval;
|
let (tradeid, pending, prices, inventories) = *boxval;
|
||||||
if agent.behavior.is(BehaviorState::TRADING) {
|
if agent.behavior.is(BehaviorState::TRADING) {
|
||||||
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
||||||
let mut message = |text| {
|
let mut message = |content: Content| {
|
||||||
if let Some(with) = agent
|
if let Some(with) = agent
|
||||||
.target
|
.target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -444,14 +407,12 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell(
|
||||||
*agent_data.uid,
|
*agent_data.uid,
|
||||||
*with,
|
*with,
|
||||||
// TODO: localise this
|
content,
|
||||||
Content::Plain(text),
|
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
*agent_data.uid,
|
*agent_data.uid,
|
||||||
// TODO: localise this
|
content,
|
||||||
Content::Plain(text),
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -460,14 +421,14 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
let balance0 = prices.balance(&pending.offers, &inventories, 1 - who, true);
|
let balance0 = prices.balance(&pending.offers, &inventories, 1 - who, true);
|
||||||
let balance1 = prices.balance(&pending.offers, &inventories, who, false);
|
let balance1 = prices.balance(&pending.offers, &inventories, who, false);
|
||||||
match (balance0, balance1) {
|
match (balance0, balance1) {
|
||||||
(_, None) => {
|
// TODO: Localise
|
||||||
let msg = "I'm not willing to sell that item".to_string();
|
(_, None) => message(Content::Plain(
|
||||||
message(msg);
|
"I'm not willing to sell that item".to_string(),
|
||||||
},
|
)),
|
||||||
(None, _) => {
|
// TODO: Localise
|
||||||
let msg = "I'm not willing to buy that item".to_string();
|
(None, _) => message(Content::Plain(
|
||||||
message(msg);
|
"I'm not willing to buy that item".to_string(),
|
||||||
},
|
)),
|
||||||
(Some(balance0), Some(balance1)) => {
|
(Some(balance0), Some(balance1)) => {
|
||||||
if balance0 >= balance1 {
|
if balance0 >= balance1 {
|
||||||
// If the trade is favourable to us, only send an accept message if
|
// If the trade is favourable to us, only send an accept message if
|
||||||
@ -492,11 +453,11 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if balance1 > 0.0 {
|
if balance1 > 0.0 {
|
||||||
let msg = format!(
|
// TODO: Localise
|
||||||
|
message(Content::Plain(format!(
|
||||||
"That only covers {:.0}% of my costs!",
|
"That only covers {:.0}% of my costs!",
|
||||||
(balance0 / balance1 * 100.0).floor()
|
(balance0 / balance1 * 100.0).floor()
|
||||||
);
|
)));
|
||||||
message(msg);
|
|
||||||
}
|
}
|
||||||
if pending.phase != TradePhase::Mutate {
|
if pending.phase != TradePhase::Mutate {
|
||||||
// we got into the review phase but without balanced goods,
|
// we got into the review phase but without balanced goods,
|
||||||
@ -584,7 +545,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
|||||||
// in combat, speak to players that aren't the current target
|
// in combat, speak to players that aren't the current target
|
||||||
if !target.hostile || target.target != speaker {
|
if !target.hostile || target.target != speaker {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_busy",
|
Content::localized("npc-speech-villager_busy"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -602,13 +563,13 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
|||||||
if !target.hostile || target.target != speaker {
|
if !target.hostile || target.target != speaker {
|
||||||
if agent.behavior.can_trade(agent_data.alignment.copied(), *by) {
|
if agent.behavior.can_trade(agent_data.alignment.copied(), *by) {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_busy",
|
Content::localized("npc-speech-merchant_busy"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-villager_busy",
|
Content::localized("npc-speech-villager_busy"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -624,14 +585,14 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
|||||||
match result {
|
match result {
|
||||||
TradeResult::Completed => {
|
TradeResult::Completed => {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_trade_successful",
|
Content::localized("npc-speech-merchant_trade_successful"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_trade_declined",
|
Content::localized("npc-speech-merchant_trade_declined"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -653,7 +614,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
|||||||
TradeAction::Decline,
|
TradeAction::Decline,
|
||||||
));
|
));
|
||||||
agent_data.chat_npc_if_allowed_to_speak(
|
agent_data.chat_npc_if_allowed_to_speak(
|
||||||
"npc-speech-merchant_trade_cancelled_hostile",
|
Content::localized("npc-speech-merchant_trade_cancelled_hostile"),
|
||||||
agent,
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user