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.
|
||||
.a3 = These Clerics are up to no good.
|
||||
.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 {
|
||||
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 {
|
||||
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||
}
|
||||
@ -212,7 +214,7 @@ impl Content {
|
||||
pub fn localized(key: impl ToString) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
r: rand::random(),
|
||||
seed: rand::random(),
|
||||
args: HashMap::default(),
|
||||
}
|
||||
}
|
||||
@ -223,7 +225,7 @@ impl Content {
|
||||
) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
r: rand::random(),
|
||||
seed: rand::random(),
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
|
@ -3,11 +3,11 @@
|
||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||
// module in `server`.
|
||||
|
||||
use crate::character::CharacterId;
|
||||
use crate::{character::CharacterId, comp::Content};
|
||||
use rand::{seq::IteratorRandom, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Component;
|
||||
use std::{borrow::Cow, collections::VecDeque};
|
||||
use std::collections::VecDeque;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use vek::*;
|
||||
|
||||
@ -169,6 +169,33 @@ impl Personality {
|
||||
pub fn will_ambush(&self) -> bool {
|
||||
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 {
|
||||
@ -226,7 +253,7 @@ pub enum NpcAction {
|
||||
/// Speak the given message, with an optional target for that speech.
|
||||
// TODO: Use some sort of structured, language-independent value that frontends can translate
|
||||
// instead
|
||||
Say(Option<Actor>, Cow<'static, str>),
|
||||
Say(Option<Actor>, Content),
|
||||
/// Attack the given target
|
||||
Attack(Actor),
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slotmap::HopSlotMap;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
@ -74,8 +73,8 @@ impl Controller {
|
||||
|
||||
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>>) {
|
||||
self.actions.push(NpcAction::Say(target.into(), msg.into()));
|
||||
pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
|
||||
self.actions.push(NpcAction::Say(target.into(), content));
|
||||
}
|
||||
|
||||
pub fn attack(&mut self, target: impl Into<Actor>) {
|
||||
|
@ -11,6 +11,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
astar::{Astar, PathResult},
|
||||
comp::Content,
|
||||
path::Path,
|
||||
rtsim::{ChunkResource, Profession, SiteId},
|
||||
spiral::Spiral2d,
|
||||
@ -470,15 +471,17 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
||||
|
||||
fn socialize() -> impl Action {
|
||||
now(|ctx| {
|
||||
// TODO: Bit odd, should wait for a while after greeting
|
||||
if ctx.rng.gen_bool(0.002) {
|
||||
// Skip most socialising actions if we're not loaded
|
||||
if matches!(ctx.npc.mode, SimulationMode::Loaded) && ctx.rng.gen_bool(0.002) {
|
||||
if ctx.rng.gen_bool(0.15) {
|
||||
return just(|ctx| ctx.controller.do_dance())
|
||||
.repeat()
|
||||
.stop_if(timeout(6.0))
|
||||
.debug(|| "dancing")
|
||||
.map(|_| ())
|
||||
.boxed();
|
||||
return Either::Left(
|
||||
just(|ctx| ctx.controller.do_dance())
|
||||
.repeat()
|
||||
.stop_if(timeout(6.0))
|
||||
.debug(|| "dancing")
|
||||
.map(|_| ())
|
||||
.boxed(),
|
||||
);
|
||||
} else if let Some(other) = ctx
|
||||
.state
|
||||
.data()
|
||||
@ -486,12 +489,17 @@ fn socialize() -> impl Action {
|
||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
||||
.choose(&mut ctx.rng)
|
||||
{
|
||||
return just(move |ctx| ctx.controller.say(other, "npc-speech-villager_open"))
|
||||
.boxed();
|
||||
return Either::Left(
|
||||
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())
|
||||
.unwrap_or_default();
|
||||
// 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))
|
||||
// Stop for a few minutes
|
||||
.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_())
|
||||
})
|
||||
{
|
||||
just(|ctx| ctx.controller.say(None, "It's dark, time to go home"))
|
||||
.then(travel_to_point(house_wpos, 0.65))
|
||||
.debug(|| "walk to house")
|
||||
.then(socialize().repeat().debug(|| "wait in house"))
|
||||
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||
.then(just(|ctx| ctx.controller.say(None, "A new day begins!")))
|
||||
.map(|_| ())
|
||||
.boxed()
|
||||
just(|ctx| {
|
||||
ctx.controller
|
||||
.say(None, Content::localized("npc-speech-night_time"))
|
||||
})
|
||||
.then(travel_to_point(house_wpos, 0.65))
|
||||
.debug(|| "walk to house")
|
||||
.then(socialize().repeat().debug(|| "wait in house"))
|
||||
.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 {
|
||||
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) {
|
||||
if let Some(forest_wpos) = find_forest(ctx) {
|
||||
return casual(
|
||||
just(|ctx| ctx.controller.say(None, "Time to go hunting!"))
|
||||
.then(travel_to_point(forest_wpos, 0.75))
|
||||
.debug(|| "walk to forest")
|
||||
.then({
|
||||
let wait_time = ctx.rng.gen_range(30.0..60.0);
|
||||
hunt_animals().repeat().stop_if(timeout(wait_time))
|
||||
})
|
||||
.map(|_| ()),
|
||||
just(|ctx| {
|
||||
ctx.controller
|
||||
.say(None, Content::localized("npc-speech-start_hunting"))
|
||||
})
|
||||
.then(travel_to_point(forest_wpos, 0.75))
|
||||
.debug(|| "walk to forest")
|
||||
.then({
|
||||
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) {
|
||||
@ -682,15 +699,10 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
||||
.debug(|| "patrol")
|
||||
.interrupt_with(|ctx| {
|
||||
if ctx.rng.gen_bool(0.0003) {
|
||||
let phrase = *[
|
||||
"My brother's out fighting ogres. What do I get? Guard duty...",
|
||||
"Just one more patrol, then I can head home",
|
||||
"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)))
|
||||
Some(just(move |ctx| {
|
||||
ctx.controller
|
||||
.say(None, Content::localized("npc-speech-guard_thought"))
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -703,32 +715,20 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
||||
return casual(
|
||||
just(|ctx| {
|
||||
// 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
|
||||
.data()
|
||||
.npcs
|
||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
||||
.choose(&mut ctx.rng)
|
||||
{
|
||||
(Some(other), &[
|
||||
"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
|
||||
(Some(other), "npc-speech-merchant_sell_directed")
|
||||
} else {
|
||||
(None, &[
|
||||
"All my goods are of the highest quality!",
|
||||
"Does anybody want to buy my wares?",
|
||||
"I've got the best offers in town",
|
||||
"Looking for supplies? I've got you covered",
|
||||
][..])
|
||||
// Otherwise, resort to generic expressions
|
||||
(None, "npc-speech-merchant_sell_undirected")
|
||||
};
|
||||
|
||||
ctx.controller.say(
|
||||
target,
|
||||
*phrases.iter().choose(&mut ctx.rng).unwrap(), // Can't fail
|
||||
);
|
||||
ctx.controller.say(target, Content::localized(phrase));
|
||||
})
|
||||
.then(idle().repeat().stop_if(timeout(8.0)))
|
||||
.repeat()
|
||||
@ -898,16 +898,17 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
||||
Some(ReportKind::Death { killer, .. }) => {
|
||||
// TODO: Sentiment should be positive if we didn't like actor that died
|
||||
// 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
|
||||
ctx.sentiments.change_by(killer, -0.7, Sentiment::VILLAIN);
|
||||
&["Murderer!", "How could you do this?", "Aaargh!"][..]
|
||||
"npc-speech-witness_murder"
|
||||
} 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);
|
||||
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
|
||||
}
|
||||
|
@ -731,7 +731,7 @@ impl<'a> AgentData<'a> {
|
||||
} else if can_ambush(entity, read_data) {
|
||||
controller.clone().push_utterance(UtteranceKind::Ambush);
|
||||
self.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-ambush".to_string(),
|
||||
Content::localized("npc-speech-ambush"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -1517,7 +1517,7 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
pub fn chat_npc_if_allowed_to_speak(
|
||||
&self,
|
||||
msg: impl ToString,
|
||||
msg: Content,
|
||||
agent: &Agent,
|
||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||
) -> 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(
|
||||
*self.uid,
|
||||
Content::localized(key),
|
||||
*self.uid, content,
|
||||
)));
|
||||
}
|
||||
|
||||
@ -1561,13 +1560,13 @@ impl<'a> AgentData<'a> {
|
||||
// FIXME: If going to use "cultist + low health + fleeing" string, make sure
|
||||
// they are each true.
|
||||
self.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-cultist_low_health_fleeing",
|
||||
Content::localized("npc-speech-cultist_low_health_fleeing"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else if is_villager(self.alignment) {
|
||||
self.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_under_attack",
|
||||
Content::localized("npc-speech-villager_under_attack"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -1582,7 +1581,7 @@ impl<'a> AgentData<'a> {
|
||||
) {
|
||||
if is_villager(self.alignment) {
|
||||
self.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_enemy_killed",
|
||||
Content::localized("npc-speech-villager_enemy_killed"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -1756,16 +1755,22 @@ impl<'a> AgentData<'a> {
|
||||
let move_dir = controller.inputs.move_dir;
|
||||
let move_dir_mag = move_dir.magnitude();
|
||||
let small_chance = rng.gen::<f32>() < read_data.dt.0 * 0.25;
|
||||
let mut chat = |msg: &str| {
|
||||
self.chat_npc_if_allowed_to_speak(msg.to_string(), agent, event_emitter);
|
||||
let mut chat = |content: Content| {
|
||||
self.chat_npc_if_allowed_to_speak(content, agent, event_emitter);
|
||||
};
|
||||
let mut chat_villager_remembers_fighting = || {
|
||||
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
|
||||
|
||||
// TODO: Localise
|
||||
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 {
|
||||
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 {
|
||||
chat_villager_remembers_fighting();
|
||||
} else if is_dressed_as_cultist(target, read_data) {
|
||||
chat("npc-speech-villager_cultist_alarm");
|
||||
chat(Content::localized("npc-speech-villager_cultist_alarm"));
|
||||
} else {
|
||||
chat("npc-speech-menacing");
|
||||
chat(Content::localized("npc-speech-menacing"));
|
||||
}
|
||||
} 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 {
|
||||
standard_response_msg()
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
// TODO: Localise
|
||||
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
if let Some(extreme_trait) =
|
||||
agent.rtsim_controller.personality.chat_trait(&mut rng)
|
||||
{
|
||||
let msg = 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",
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else {
|
||||
agent_data.chat_npc("npc-speech-villager", event_emitter);
|
||||
}
|
||||
agent_data.chat_npc(
|
||||
agent
|
||||
.rtsim_controller
|
||||
.personality
|
||||
.get_generic_comment(&mut rng),
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -213,13 +174,13 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_advertisement",
|
||||
Content::localized("npc-speech-merchant_advertisement"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
Content::localized("npc-speech-merchant_busy"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -228,7 +189,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
// TODO: maybe make some travellers willing to trade with
|
||||
// simpler goods like potions
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_decline_trade",
|
||||
Content::localized("npc-speech-villager_decline_trade"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -243,15 +204,17 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
let dist = Distance::from_dir(raw_dir).name();
|
||||
let dir = Direction::from_dir(raw_dir).name();
|
||||
|
||||
// TODO: Localise
|
||||
let msg = format!(
|
||||
"{} ? I think it's {} {} from here!",
|
||||
location.name, dist, dir
|
||||
);
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Person(person) => {
|
||||
if let Some(src_pos) = read_data.positions.get(target) {
|
||||
// TODO: Localise
|
||||
let msg = if let Some(person_pos) = person.origin {
|
||||
let distance = Distance::from_dir(person_pos.xy() - src_pos.0.xy());
|
||||
match distance {
|
||||
@ -278,7 +241,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
person.name()
|
||||
)
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
agent_data.chat_npc(Content::Plain(msg), event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Work => {},
|
||||
@ -330,7 +293,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
|
||||
} else {
|
||||
controller.push_invite_response(InviteResponse::Decline);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
Content::localized("npc-speech-merchant_busy"),
|
||||
agent,
|
||||
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?
|
||||
controller.push_invite_response(InviteResponse::Decline);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_decline_trade",
|
||||
Content::localized("npc-speech-villager_decline_trade"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -396,14 +359,14 @@ pub fn handle_inbox_finished_trade(bdata: &mut BehaviorData) -> bool {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_successful",
|
||||
Content::localized("npc-speech-merchant_trade_successful"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_declined",
|
||||
Content::localized("npc-speech-merchant_trade_declined"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -435,7 +398,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
||||
let (tradeid, pending, prices, inventories) = *boxval;
|
||||
if agent.behavior.is(BehaviorState::TRADING) {
|
||||
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
||||
let mut message = |text| {
|
||||
let mut message = |content: Content| {
|
||||
if let Some(with) = agent
|
||||
.target
|
||||
.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(
|
||||
*agent_data.uid,
|
||||
*with,
|
||||
// TODO: localise this
|
||||
Content::Plain(text),
|
||||
content,
|
||||
)));
|
||||
} else {
|
||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||
*agent_data.uid,
|
||||
// TODO: localise this
|
||||
Content::Plain(text),
|
||||
content,
|
||||
)));
|
||||
}
|
||||
};
|
||||
@ -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 balance1 = prices.balance(&pending.offers, &inventories, who, false);
|
||||
match (balance0, balance1) {
|
||||
(_, None) => {
|
||||
let msg = "I'm not willing to sell that item".to_string();
|
||||
message(msg);
|
||||
},
|
||||
(None, _) => {
|
||||
let msg = "I'm not willing to buy that item".to_string();
|
||||
message(msg);
|
||||
},
|
||||
// TODO: Localise
|
||||
(_, None) => message(Content::Plain(
|
||||
"I'm not willing to sell that item".to_string(),
|
||||
)),
|
||||
// TODO: Localise
|
||||
(None, _) => message(Content::Plain(
|
||||
"I'm not willing to buy that item".to_string(),
|
||||
)),
|
||||
(Some(balance0), Some(balance1)) => {
|
||||
if balance0 >= balance1 {
|
||||
// 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 {
|
||||
if balance1 > 0.0 {
|
||||
let msg = format!(
|
||||
// TODO: Localise
|
||||
message(Content::Plain(format!(
|
||||
"That only covers {:.0}% of my costs!",
|
||||
(balance0 / balance1 * 100.0).floor()
|
||||
);
|
||||
message(msg);
|
||||
)));
|
||||
}
|
||||
if pending.phase != TradePhase::Mutate {
|
||||
// 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
|
||||
if !target.hostile || target.target != speaker {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_busy",
|
||||
Content::localized("npc-speech-villager_busy"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -602,13 +563,13 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
if !target.hostile || target.target != speaker {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), *by) {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
Content::localized("npc-speech-merchant_busy"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_busy",
|
||||
Content::localized("npc-speech-villager_busy"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -624,14 +585,14 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
match result {
|
||||
TradeResult::Completed => {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_successful",
|
||||
Content::localized("npc-speech-merchant_trade_successful"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_declined",
|
||||
Content::localized("npc-speech-merchant_trade_declined"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
@ -653,7 +614,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
TradeAction::Decline,
|
||||
));
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_trade_cancelled_hostile",
|
||||
Content::localized("npc-speech-merchant_trade_cancelled_hostile"),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user