Added /rtsim_purge command

This commit is contained in:
Joshua Barretto 2023-03-31 23:20:52 +01:00
parent 66710d5bc2
commit 64324262c7
12 changed files with 171 additions and 85 deletions

View File

@ -310,8 +310,9 @@ pub enum ServerChatCommand {
Tell, Tell,
Time, Time,
Tp, Tp,
TpNpc, RtsimTp,
NpcInfo, RtsimInfo,
RtsimPurge,
RtsimChunk, RtsimChunk,
Unban, Unban,
Version, Version,
@ -682,16 +683,25 @@ impl ServerChatCommand {
"Teleport to another player", "Teleport to another player",
Some(Moderator), Some(Moderator),
), ),
ServerChatCommand::TpNpc => cmd( ServerChatCommand::RtsimTp => cmd(
vec![Integer("npc index", 0, Required)], vec![Integer("npc index", 0, Required)],
"Teleport to an rtsim npc", "Teleport to an rtsim npc",
Some(Moderator), Some(Moderator),
), ),
ServerChatCommand::NpcInfo => cmd( ServerChatCommand::RtsimInfo => cmd(
vec![Integer("npc index", 0, Required)], vec![Integer("npc index", 0, Required)],
"Display information about an rtsim NPC", "Display information about an rtsim NPC",
Some(Moderator), Some(Moderator),
), ),
ServerChatCommand::RtsimPurge => cmd(
vec![Boolean(
"whether purging of rtsim data should occur on next startup",
true.to_string(),
Required,
)],
"Purge rtsim data on next startup",
Some(Admin),
),
ServerChatCommand::RtsimChunk => cmd( ServerChatCommand::RtsimChunk => cmd(
vec![], vec![],
"Display information about the current chunk from rtsim", "Display information about the current chunk from rtsim",
@ -824,8 +834,9 @@ impl ServerChatCommand {
ServerChatCommand::Tell => "tell", ServerChatCommand::Tell => "tell",
ServerChatCommand::Time => "time", ServerChatCommand::Time => "time",
ServerChatCommand::Tp => "tp", ServerChatCommand::Tp => "tp",
ServerChatCommand::TpNpc => "tp_npc", ServerChatCommand::RtsimTp => "rtsim_tp",
ServerChatCommand::NpcInfo => "npc_info", ServerChatCommand::RtsimInfo => "rtsim_info",
ServerChatCommand::RtsimPurge => "rtsim_purge",
ServerChatCommand::RtsimChunk => "rtsim_chunk", ServerChatCommand::RtsimChunk => "rtsim_chunk",
ServerChatCommand::Unban => "unban", ServerChatCommand::Unban => "unban",
ServerChatCommand::Version => "version", ServerChatCommand::Version => "version",

View File

@ -3,7 +3,7 @@
// `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 rand::{Rng, seq::IteratorRandom}; use rand::{seq::IteratorRandom, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::Component; use specs::Component;
use strum::{EnumIter, IntoEnumIterator}; use strum::{EnumIter, IntoEnumIterator};
@ -56,7 +56,6 @@ pub enum MemoryItem {
Mood { state: MoodState }, Mood { state: MoodState },
} }
#[derive(EnumIter, Clone, Copy)] #[derive(EnumIter, Clone, Copy)]
pub enum PersonalityTrait { pub enum PersonalityTrait {
Open, Open,
@ -88,7 +87,9 @@ pub struct Personality {
fn distributed(min: u8, max: u8, rng: &mut impl Rng) -> u8 { fn distributed(min: u8, max: u8, rng: &mut impl Rng) -> u8 {
let l = max - min; let l = max - min;
min + rng.gen_range(0..=l / 3) + rng.gen_range(0..=l / 3 + l % 3 % 2) + rng.gen_range(0..=l / 3 + l % 3 / 2) min + rng.gen_range(0..=l / 3)
+ rng.gen_range(0..=l / 3 + l % 3 % 2)
+ rng.gen_range(0..=l / 3 + l % 3 / 2)
} }
impl Personality { impl Personality {
@ -96,13 +97,11 @@ impl Personality {
pub const LITTLE_HIGH: u8 = Self::MID + (Self::MAX - Self::MIN) / 20; pub const LITTLE_HIGH: u8 = Self::MID + (Self::MAX - Self::MIN) / 20;
pub const LITTLE_LOW: u8 = Self::MID - (Self::MAX - Self::MIN) / 20; pub const LITTLE_LOW: u8 = Self::MID - (Self::MAX - Self::MIN) / 20;
pub const LOW_THRESHOLD: u8 = (Self::MAX - Self::MIN) / 5 * 2 + Self::MIN; pub const LOW_THRESHOLD: u8 = (Self::MAX - Self::MIN) / 5 * 2 + Self::MIN;
const MIN: u8 = 0;
pub const MID: u8 = (Self::MAX - Self::MIN) / 2;
const MAX: u8 = 255; const MAX: u8 = 255;
pub const MID: u8 = (Self::MAX - Self::MIN) / 2;
const MIN: u8 = 0;
fn distributed_value(rng: &mut impl Rng) -> u8 { fn distributed_value(rng: &mut impl Rng) -> u8 { distributed(Self::MIN, Self::MAX, rng) }
distributed(Self::MIN, Self::MAX, rng)
}
pub fn random(rng: &mut impl Rng) -> Self { pub fn random(rng: &mut impl Rng) -> Self {
Self { Self {
@ -137,20 +136,36 @@ impl Personality {
pub fn is(&self, trait_: PersonalityTrait) -> bool { pub fn is(&self, trait_: PersonalityTrait) -> bool {
match trait_ { match trait_ {
PersonalityTrait::Open => self.openness > Personality::HIGH_THRESHOLD, PersonalityTrait::Open => self.openness > Personality::HIGH_THRESHOLD,
PersonalityTrait::Adventurous => self.openness > Personality::HIGH_THRESHOLD && self.neuroticism < Personality::MID, PersonalityTrait::Adventurous => {
self.openness > Personality::HIGH_THRESHOLD && self.neuroticism < Personality::MID
},
PersonalityTrait::Closed => self.openness < Personality::LOW_THRESHOLD, PersonalityTrait::Closed => self.openness < Personality::LOW_THRESHOLD,
PersonalityTrait::Conscientious => self.conscientiousness > Personality::HIGH_THRESHOLD, PersonalityTrait::Conscientious => self.conscientiousness > Personality::HIGH_THRESHOLD,
PersonalityTrait::Busybody => self.agreeableness < Personality::LOW_THRESHOLD, PersonalityTrait::Busybody => self.agreeableness < Personality::LOW_THRESHOLD,
PersonalityTrait::Unconscientious => self.conscientiousness < Personality::LOW_THRESHOLD, PersonalityTrait::Unconscientious => {
self.conscientiousness < Personality::LOW_THRESHOLD
},
PersonalityTrait::Extroverted => self.extraversion > Personality::HIGH_THRESHOLD, PersonalityTrait::Extroverted => self.extraversion > Personality::HIGH_THRESHOLD,
PersonalityTrait::Introverted => self.extraversion < Personality::LOW_THRESHOLD, PersonalityTrait::Introverted => self.extraversion < Personality::LOW_THRESHOLD,
PersonalityTrait::Agreeable => self.agreeableness > Personality::HIGH_THRESHOLD, PersonalityTrait::Agreeable => self.agreeableness > Personality::HIGH_THRESHOLD,
PersonalityTrait::Sociable => self.agreeableness > Personality::HIGH_THRESHOLD && self.extraversion > Personality::MID, PersonalityTrait::Sociable => {
self.agreeableness > Personality::HIGH_THRESHOLD
&& self.extraversion > Personality::MID
},
PersonalityTrait::Disagreeable => self.agreeableness < Personality::LOW_THRESHOLD, PersonalityTrait::Disagreeable => self.agreeableness < Personality::LOW_THRESHOLD,
PersonalityTrait::Neurotic => self.neuroticism > Personality::HIGH_THRESHOLD, PersonalityTrait::Neurotic => self.neuroticism > Personality::HIGH_THRESHOLD,
PersonalityTrait::Seeker => self.neuroticism > Personality::HIGH_THRESHOLD && self.openness > Personality::LITTLE_HIGH, PersonalityTrait::Seeker => {
PersonalityTrait::Worried => self.neuroticism > Personality::HIGH_THRESHOLD && self.agreeableness > Personality::LITTLE_HIGH, self.neuroticism > Personality::HIGH_THRESHOLD
PersonalityTrait::SadLoner => self.neuroticism > Personality::HIGH_THRESHOLD && self.extraversion < Personality::LITTLE_LOW, && self.openness > Personality::LITTLE_HIGH
},
PersonalityTrait::Worried => {
self.neuroticism > Personality::HIGH_THRESHOLD
&& self.agreeableness > Personality::LITTLE_HIGH
},
PersonalityTrait::SadLoner => {
self.neuroticism > Personality::HIGH_THRESHOLD
&& self.extraversion < Personality::LITTLE_LOW
},
PersonalityTrait::Stable => self.neuroticism < Personality::LOW_THRESHOLD, PersonalityTrait::Stable => self.neuroticism < Personality::LOW_THRESHOLD,
} }
} }
@ -160,14 +175,19 @@ impl Personality {
} }
pub fn will_ambush(&self) -> bool { pub fn will_ambush(&self) -> bool {
self.agreeableness < Self::LOW_THRESHOLD self.agreeableness < Self::LOW_THRESHOLD && self.conscientiousness < Self::LOW_THRESHOLD
&& self.conscientiousness < Self::LOW_THRESHOLD
} }
} }
impl Default for Personality { impl Default for Personality {
fn default() -> Self { fn default() -> Self {
Self { openness: Personality::MID, conscientiousness: Personality::MID, extraversion: Personality::MID, agreeableness: Personality::MID, neuroticism: Personality::MID } Self {
openness: Personality::MID,
conscientiousness: Personality::MID,
extraversion: Personality::MID,
agreeableness: Personality::MID,
neuroticism: Personality::MID,
}
} }
} }
@ -198,7 +218,7 @@ impl Default for RtSimController {
fn default() -> Self { fn default() -> Self {
Self { Self {
travel_to: None, travel_to: None,
personality:Personality::default(), personality: Personality::default(),
heading_to: None, heading_to: None,
speed_factor: 1.0, speed_factor: 1.0,
events: Vec::new(), events: Vec::new(),
@ -210,7 +230,7 @@ impl RtSimController {
pub fn with_destination(pos: Vec3<f32>) -> Self { pub fn with_destination(pos: Vec3<f32>) -> Self {
Self { Self {
travel_to: Some(pos), travel_to: Some(pos),
personality:Personality::default(), personality: Personality::default(),
heading_to: None, heading_to: None,
speed_factor: 0.5, speed_factor: 0.5,
events: Vec::new(), events: Vec::new(),

View File

@ -5,8 +5,8 @@ use crate::{
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller, ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate, Stats, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate,
Vel, Stats, Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::Rider,

View File

@ -9,8 +9,8 @@ use common::{
character_state::OutputEvents, character_state::OutputEvents,
inventory::item::{tool::AbilityMap, MaterialStatManifest}, inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet,
StateUpdate, Stats, Vel, Stance, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
link::Is, link::Is,

View File

@ -43,6 +43,8 @@ pub struct Data {
pub factions: Factions, pub factions: Factions,
pub time_of_day: TimeOfDay, pub time_of_day: TimeOfDay,
// If true, rtsim data will be ignored (and, hence, overwritten on next save) on load.
pub should_purge: bool,
} }
pub type ReadError = rmp_serde::decode::Error; pub type ReadError = rmp_serde::decode::Error;

View File

@ -44,6 +44,7 @@ impl Data {
}, },
time_of_day: TimeOfDay(settings.start_time), time_of_day: TimeOfDay(settings.start_time),
should_purge: false,
}; };
let initial_factions = (0..16) let initial_factions = (0..16)

View File

@ -184,8 +184,9 @@ fn do_command(
ServerChatCommand::Tell => handle_tell, ServerChatCommand::Tell => handle_tell,
ServerChatCommand::Time => handle_time, ServerChatCommand::Time => handle_time,
ServerChatCommand::Tp => handle_tp, ServerChatCommand::Tp => handle_tp,
ServerChatCommand::TpNpc => handle_tp_npc, ServerChatCommand::RtsimTp => handle_rtsim_tp,
ServerChatCommand::NpcInfo => handle_npc_info, ServerChatCommand::RtsimInfo => handle_rtsim_info,
ServerChatCommand::RtsimPurge => handle_rtsim_purge,
ServerChatCommand::RtsimChunk => handle_rtsim_chunk, ServerChatCommand::RtsimChunk => handle_rtsim_chunk,
ServerChatCommand::Unban => handle_unban, ServerChatCommand::Unban => handle_unban,
ServerChatCommand::Version => handle_version, ServerChatCommand::Version => handle_version,
@ -1185,7 +1186,7 @@ fn handle_tp(
}) })
} }
fn handle_tp_npc( fn handle_rtsim_tp(
server: &mut Server, server: &mut Server,
_client: EcsEntity, _client: EcsEntity,
target: EcsEntity, target: EcsEntity,
@ -1214,7 +1215,7 @@ fn handle_tp_npc(
}) })
} }
fn handle_npc_info( fn handle_rtsim_info(
server: &mut Server, server: &mut Server,
client: EcsEntity, client: EcsEntity,
target: EcsEntity, target: EcsEntity,
@ -1263,6 +1264,36 @@ fn handle_npc_info(
} }
} }
fn handle_rtsim_purge(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
use crate::rtsim2::RtSim;
if let Some(should_purge) = parse_cmd_args!(args, bool) {
server
.state
.ecs()
.write_resource::<RtSim>()
.set_should_purge(should_purge);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!(
"Rtsim data {} be purged on next startup",
if should_purge { "WILL" } else { "will NOT" },
),
),
);
Ok(())
} else {
return Err(action.help_string());
}
}
fn handle_rtsim_chunk( fn handle_rtsim_chunk(
server: &mut Server, server: &mut Server,
client: EcsEntity, client: EcsEntity,

View File

@ -80,8 +80,8 @@ use common::{
comp, comp,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::{BattleMode, GameMode, Time, TimeOfDay}, resources::{BattleMode, GameMode, Time, TimeOfDay},
shared_server_config::ServerConstants,
rtsim::{RtSimEntity, RtSimVehicle}, rtsim::{RtSimEntity, RtSimVehicle},
shared_server_config::ServerConstants,
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::{Block, TerrainChunk, TerrainChunkSize}, terrain::{Block, TerrainChunk, TerrainChunkSize},
vol::RectRasterableVol, vol::RectRasterableVol,
@ -1463,6 +1463,15 @@ impl Drop for Server {
info!("Unloading terrain persistence..."); info!("Unloading terrain persistence...");
terrain_persistence.unload_all() terrain_persistence.unload_all()
}); });
#[cfg(feature = "worldgen")]
{
info!("Saving rtsim state...");
self.state
.ecs()
.write_resource::<rtsim2::RtSim>()
.save(true);
}
} }
} }

View File

@ -54,7 +54,14 @@ impl RtSim {
match Data::from_reader(io::BufReader::new(file)) { match Data::from_reader(io::BufReader::new(file)) {
Ok(data) => { Ok(data) => {
info!("Rtsim data loaded."); info!("Rtsim data loaded.");
if data.should_purge {
warn!(
"The should_purge flag was set on the rtsim data, \
generating afresh"
);
} else {
break 'load data; break 'load data;
}
}, },
Err(e) => { Err(e) => {
error!("Rtsim data failed to load: {}", e); error!("Rtsim data failed to load: {}", e);
@ -171,14 +178,14 @@ impl RtSim {
self.state.data_mut().npcs.remove(entity.0); self.state.data_mut().npcs.remove(entity.0);
} }
pub fn save(&mut self, slowjob_pool: &SlowJobPool) { pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) {
info!("Saving rtsim data..."); info!("Saving rtsim data...");
let file_path = self.file_path.clone(); let file_path = self.file_path.clone();
let data = self.state.data().clone(); let data = self.state.data().clone();
debug!("Starting rtsim data save job..."); debug!("Starting rtsim data save job...");
// TODO: Use slow job // TODO: Use slow job
// slowjob_pool.spawn("RTSIM_SAVE", move || { // slowjob_pool.spawn("RTSIM_SAVE", move || {
std::thread::spawn(move || { let handle = std::thread::spawn(move || {
let tmp_file_name = "data_tmp.dat"; let tmp_file_name = "data_tmp.dat";
if let Err(e) = file_path if let Err(e) = file_path
.parent() .parent()
@ -203,6 +210,11 @@ impl RtSim {
error!("Saving rtsim data failed: {}", e); error!("Saving rtsim data failed: {}", e);
} }
}); });
if wait_until_finished {
handle.join();
}
self.last_saved = Some(Instant::now()); self.last_saved = Some(Instant::now());
} }
@ -212,6 +224,10 @@ impl RtSim {
} }
pub fn state(&self) -> &RtState { &self.state } pub fn state(&self) -> &RtState { &self.state }
pub fn set_should_purge(&mut self, should_purge: bool) {
self.state.data_mut().should_purge = should_purge;
}
} }
pub struct ChunkStates(pub Grid<Option<LoadedChunkState>>); pub struct ChunkStates(pub Grid<Option<LoadedChunkState>>);

View File

@ -223,7 +223,9 @@ impl<'a> System<'a> for Sys {
.last_saved .last_saved
.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) .map_or(true, |ls| ls.elapsed() > Duration::from_secs(60))
{ {
rtsim.save(&slow_jobs); // TODO: Use slow jobs
let _ = slow_jobs;
rtsim.save(/* &slow_jobs, */ false);
} }
let chunk_states = rtsim.state.resource::<ChunkStates>(); let chunk_states = rtsim.state.resource::<ChunkStates>();

View File

@ -741,12 +741,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS; read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
if !in_aggro_range && is_time_to_retarget { if !in_aggro_range && is_time_to_retarget {
agent_data.choose_target( agent_data.choose_target(agent, controller, read_data, event_emitter);
agent,
controller,
read_data,
event_emitter,
);
} }
if aggro_on { if aggro_on {

View File

@ -9,7 +9,7 @@ use common::{
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind, BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
}, },
event::ServerEvent, event::ServerEvent,
rtsim::{Memory, MemoryItem, RtSimEvent, PersonalityTrait}, rtsim::{Memory, MemoryItem, PersonalityTrait, RtSimEvent},
trade::{TradeAction, TradePhase, TradeResult}, trade::{TradeAction, TradePhase, TradeResult},
}; };
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -106,47 +106,46 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
match subject { match subject {
Subject::Regular => { Subject::Regular => {
if let Some(tgt_stats) = read_data.stats.get(target) { if let Some(tgt_stats) = read_data.stats.get(target) {
agent.rtsim_controller.events.push(RtSimEvent::AddMemory( agent
Memory { .rtsim_controller
.events
.push(RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterInteraction { item: MemoryItem::CharacterInteraction {
name: tgt_stats.name.clone(), name: tgt_stats.name.clone(),
}, },
time_to_forget: read_data.time.0 + 600.0, time_to_forget: read_data.time.0 + 600.0,
}, }));
));
if let Some(destination_name) = &agent.rtsim_controller.heading_to { if let Some(destination_name) = &agent.rtsim_controller.heading_to {
let personality = &agent.rtsim_controller.personality; let personality = &agent.rtsim_controller.personality;
let standard_response_msg = || -> String { let standard_response_msg = || -> String {
if personality.will_ambush() { if personality.will_ambush() {
format!( format!(
"I'm heading to {}! Want to come along? We'll make \ "I'm heading to {}! Want to come along? We'll \
great travel buddies, hehe.", make great travel buddies, hehe.",
destination_name destination_name
) )
} else if personality.is(PersonalityTrait::Extroverted) } else if personality.is(PersonalityTrait::Extroverted) {
{
format!( format!(
"I'm heading to {}! Want to come along?", "I'm heading to {}! Want to come along?",
destination_name destination_name
) )
} else if personality.is(PersonalityTrait::Disagreeable) } else if personality.is(PersonalityTrait::Disagreeable) {
{
"Hrm.".to_string() "Hrm.".to_string()
} else { } else {
"Hello!".to_string() "Hello!".to_string()
} }
}; };
let msg = if false /* TODO: Remembers character */ { let msg = if false
/* TODO: Remembers character */
{
if personality.will_ambush() { if personality.will_ambush() {
"Just follow me a bit more, hehe.".to_string() "Just follow me a bit more, hehe.".to_string()
} else if personality.is(PersonalityTrait::Extroverted) } else if personality.is(PersonalityTrait::Extroverted) {
{ if personality.is(PersonalityTrait::Extroverted) {
if personality.is(PersonalityTrait::Extroverted)
{
format!( format!(
"Greetings fair {}! It has been far \ "Greetings fair {}! It has been far too long \
too long since last I saw you. I'm \ since last I saw you. I'm going to {} right \
going to {} right now.", now.",
&tgt_stats.name, destination_name &tgt_stats.name, destination_name
) )
} else if personality.is(PersonalityTrait::Disagreeable) } else if personality.is(PersonalityTrait::Disagreeable)
@ -154,8 +153,8 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
"Oh. It's you again.".to_string() "Oh. It's you again.".to_string()
} else { } else {
format!( format!(
"Hi again {}! Unfortunately I'm in a \ "Hi again {}! Unfortunately I'm in a hurry \
hurry right now. See you!", right now. See you!",
&tgt_stats.name &tgt_stats.name
) )
} }
@ -183,14 +182,14 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
}; };
agent_data.chat_npc(msg, event_emitter); agent_data.chat_npc(msg, event_emitter);
} }
}*/ else { }*/
else {
let mut rng = thread_rng(); let mut rng = thread_rng();
if let Some(extreme_trait) = agent.rtsim_controller.personality.chat_trait(&mut rng) if let Some(extreme_trait) =
agent.rtsim_controller.personality.chat_trait(&mut rng)
{ {
let msg = match extreme_trait { let msg = match extreme_trait {
PersonalityTrait::Open => { PersonalityTrait::Open => "npc-speech-villager_open",
"npc-speech-villager_open"
},
PersonalityTrait::Adventurous => { PersonalityTrait::Adventurous => {
"npc-speech-villager_adventurous" "npc-speech-villager_adventurous"
}, },