Localised rtsim NPC speech

This commit is contained in:
Joshua Barretto 2023-04-11 17:00:08 +01:00
parent edcc2f1870
commit cf701fb604
7 changed files with 188 additions and 161 deletions

View File

@ -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!

View File

@ -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()))

View File

@ -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),
}

View File

@ -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>) {

View File

@ -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
}

View File

@ -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"));
}
}
}

View File

@ -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,
);