diff --git a/common/src/generation.rs b/common/src/generation.rs index 00ac1f7b29..091421aa1f 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -256,7 +256,7 @@ impl EntityInfo { self = self.with_name(name); }, NameKind::Automatic => { - self = self.with_automatic_name(); + self = self.with_automatic_name(None); }, NameKind::Uninit => {}, } @@ -408,7 +408,7 @@ impl EntityInfo { } #[must_use] - pub fn with_automatic_name(mut self) -> Self { + pub fn with_automatic_name(mut self, alias: Option) -> Self { let npc_names = NPC_NAMES.read(); let name = match &self.body { Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)), @@ -430,7 +430,23 @@ impl EntityInfo { Body::Arthropod(body) => Some(get_npc_name(&npc_names.arthropod, body.species)), _ => None, }; - self.name = name.map(str::to_owned); + self.name = name.map(|name| { + if let Some(alias) = alias { + format!("{alias} ({name})") + } else { + name.to_string() + } + }); + self + } + + #[must_use] + pub fn with_alias(mut self, alias: String) -> Self { + self.name = Some(if let Some(name) = self.name { + format!("{alias} ({name})") + } else { + alias + }); self } diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index d3cecf3624..28655536b1 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -287,25 +287,6 @@ pub enum Profession { Captain, } -impl Profession { - pub fn to_name(&self) -> String { - match self { - Self::Farmer => "Farmer".to_string(), - Self::Hunter => "Hunter".to_string(), - Self::Merchant => "Merchant".to_string(), - Self::Guard => "Guard".to_string(), - Self::Adventurer(_) => "Adventurer".to_string(), - Self::Blacksmith => "Blacksmith".to_string(), - Self::Chef => "Chef".to_string(), - Self::Alchemist => "Alchemist".to_string(), - Self::Pirate => "Pirate".to_string(), - Self::Cultist => "Cultist".to_string(), - Self::Herbalist => "Herbalist".to_string(), - Self::Captain => "Captain".to_string(), - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WorldSettings { pub start_time: f64, diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index af79c2ecce..c71d1c06f0 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -1,11 +1,10 @@ -use crate::ai::Action; +use crate::{ai::Action, gen::name}; pub use common::rtsim::{NpcId, Profession}; use common::{ comp, grid::Grid, rtsim::{ - Actor, ChunkResource, FactionId, NpcAction, NpcActivity, NpcMsg, Personality, SiteId, - VehicleId, + Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId, }, store::Id, terrain::TerrainChunkSize, @@ -136,6 +135,9 @@ impl Clone for Npc { } impl Npc { + pub const PERM_ENTITY_CONFIG: u32 = 1; + const PERM_NAME: u32 = 0; + pub fn new(seed: u32, wpos: Vec3, body: comp::Body) -> Self { Self { seed, @@ -191,6 +193,8 @@ impl Npc { } pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) } + + pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) } } #[derive(Clone, Serialize, Deserialize)] @@ -297,10 +301,11 @@ impl Npcs { pub fn nearby( &self, this_npc: Option, - wpos: Vec2, + wpos: Vec3, radius: f32, ) -> impl Iterator + '_ { let chunk_pos = wpos + .xy() .as_::() .map2(TerrainChunkSize::RECT_SIZE.as_::(), |e, sz| { e.div_euclid(sz) @@ -316,7 +321,7 @@ impl Npcs { .filter(move |npc| { self.npcs .get(*npc) - .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr) + .map_or(false, |npc| npc.wpos.distance_squared(wpos) < r_sqr) && Some(*npc) != this_npc }) .map(Actor::Npc) @@ -328,7 +333,7 @@ impl Npcs { .get(&chunk_pos) .map(|characters| { characters.iter().filter_map(move |(character, c_wpos)| { - if c_wpos.xy().distance_squared(wpos) < r_sqr { + if c_wpos.distance_squared(wpos) < r_sqr { Some(Actor::Character(*character)) } else { None diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index 2dd8e727ca..975e347152 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -1,4 +1,5 @@ pub mod faction; +pub mod name; pub mod site; use crate::data::{ diff --git a/rtsim/src/gen/name.rs b/rtsim/src/gen/name.rs new file mode 100644 index 0000000000..736bb34dd5 --- /dev/null +++ b/rtsim/src/gen/name.rs @@ -0,0 +1,22 @@ +use rand::prelude::*; + +pub fn generate(rng: &mut impl Rng) -> String { + let starts = ["ad", "tr", "b", "l", "p", "d", "r", "w", "t", "fr", "s"]; + let vowels = ["o", "e", "a", "i"]; + let cons = ["m", "d", "st", "n", "y", "gh", "s"]; + + let mut name = String::new(); + + name += starts.choose(rng).unwrap(); + + for _ in 0..thread_rng().gen_range(1..=3) { + name += vowels.choose(rng).unwrap(); + name += cons.choose(rng).unwrap(); + } + + // Make the first letter uppercase (hacky) + name.chars() + .enumerate() + .map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c }) + .collect() +} diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 37435a497d..885a97a663 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -464,7 +464,7 @@ fn socialize() -> impl Action { .state .data() .npcs - .nearby(Some(ctx.npc_id), ctx.npc.wpos.xy(), 8.0) + .nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0) .choose(&mut ctx.rng) { just(move |ctx| ctx.controller.say(other, "npc-speech-villager_open")).boxed() @@ -654,7 +654,7 @@ fn villager(visiting_site: SiteId) -> impl Action { .state .data() .npcs - .nearby(Some(ctx.npc_id), ctx.npc.wpos.xy(), 8.0) + .nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0) .choose(&mut ctx.rng) { (Some(other), &[ diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 9cec5e0fa8..2706c7cdb2 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -130,7 +130,7 @@ fn profession_agent_mark(profession: Option<&Profession>) -> Option EntityInfo { let pos = comp::Pos(npc.wpos); - let mut rng = npc.rng(3); + let mut rng = npc.rng(Npc::PERM_ENTITY_CONFIG); if let Some(ref profession) = npc.profession { let economy = npc.home.and_then(|home| { let site = sites.get(home)?.world_site?; @@ -150,6 +150,7 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo }) .with_economy(economy.as_ref()) .with_lazy_loadout(profession_extra_loadout(npc.profession.as_ref())) + .with_alias(npc.get_name()) .with_agent_mark(profession_agent_mark(npc.profession.as_ref())) } else { let config_asset = match npc.body { diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index c7d394f4d8..fad1f2c5fd 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -973,7 +973,7 @@ fn barnyard(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { quadruped_small::Body::random_with(dynamic_rng, &species), )) .with_alignment(comp::Alignment::Tame) - .with_automatic_name() + .with_automatic_name(None) } fn bird(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { @@ -990,7 +990,7 @@ fn bird(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { &species, ))) .with_alignment(comp::Alignment::Tame) - .with_automatic_name() + .with_automatic_name(None) } fn humanoid(pos: Vec3, economy: &SiteInformation, dynamic_rng: &mut impl Rng) -> EntityInfo {