1use super::Rng;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum NameStyle {
14 Dark,
16 Elvish,
18 Orcish,
20 Arcane,
22 Draconic,
24 Void,
26 Place,
28 Human,
30}
31
32fn dark_prefixes() -> &'static [&'static str] { &["Mor","Mal","Kra","Vor","Zar","Dra","Sha","Gul","Neth","Bal","Tor","Khal","Vex","Crul","Dusk","Grim","Nox"] }
35fn dark_middles() -> &'static [&'static str] { &["ath","ul","ash","ak","ek","on","az","eth","ix","og","ur","an","ix","el","or"] }
36fn dark_suffixes() -> &'static [&'static str] { &["us","ak","on","ur","ax","ix","oth","eth","ar","as","en","ul","or","ath"] }
37
38fn elvish_prefixes() -> &'static [&'static str] { &["Aer","Cal","Syl","Elan","Mir","Tal","Ael","Ith","Sel","Ari","Fin","Gil","Lir","Mel","Nel","Ori"] }
39fn elvish_middles() -> &'static [&'static str] { &["an","aer","iel","ion","ias","iel","ian","ael","ial","uer","ien","ele","oth","ill","ir"] }
40fn elvish_suffixes() -> &'static [&'static str] { &["iel","ias","ion","ual","ael","ial","uen","iel","ean","ian","iel","ias","ier","ael"] }
41
42fn orcish_prefixes() -> &'static [&'static str] { &["Grak","Urg","Mag","Brul","Thog","Vrak","Krug","Gash","Ugreth","Marg","Bruk","Thrak"] }
43fn orcish_middles() -> &'static [&'static str] { &["ak","ug","ag","ok","ut","rag","ash","ul","grak","nak","krul","gur"] }
44fn orcish_suffixes() -> &'static [&'static str] { &["nak","gash","rug","ak","ug","og","ul","ash","rok","krul","gut","mak"] }
45
46fn arcane_prefixes() -> &'static [&'static str] { &["Aex","Zyr","Vael","Xan","Qaer","Zyth","Aex","Ixir","Myzt","Pyrr","Zel","Xyr"] }
47fn arcane_middles() -> &'static [&'static str] { &["ael","yth","yx","ire","ast","yrr","ael","ix","an","eth","yx","oth"] }
48fn arcane_suffixes() -> &'static [&'static str] { &["ix","aex","yth","ael","yx","ire","oth","ast","yrr","us","eth","ax"] }
49
50fn draconic_prefixes() -> &'static [&'static str] { &["Dra","Vrax","Thur","Kyr","Zur","Aur","Syr","Vor","Xar","Rha","Dur","Asr"] }
51fn draconic_middles() -> &'static [&'static str] { &["ak","ul","ath","ix","on","ur","eth","or","ax","an","ith","us"] }
52fn draconic_suffixes() -> &'static [&'static str] { &["ix","ax","us","ath","on","ur","ix","eth","or","an","ith","ax"] }
53
54fn void_prefixes() -> &'static [&'static str] { &["Zzz","Vhx","Yth","Xhl","Zhyr","Vlt","Kthx","Nyth","Pzr","Qxl","Zyx","Vlr"] }
55fn void_middles() -> &'static [&'static str] { &["yx","hz","zyr","xn","th","rx","yz","xr","zx","hz","yr","xz"] }
56fn void_suffixes() -> &'static [&'static str] { &["xyr","zth","vrx","yx","hz","xn","zyr","th","rx","yz","xr","vx"] }
57
58fn place_prefixes() -> &'static [&'static str] { &["Stone","Dark","Iron","Grim","Shadow","Blood","Frost","Ash","Black","Bone","Crypt","Death","Doom"] }
59fn place_middles() -> &'static [&'static str] { &["haven","hold","moor","gate","ridge","peak","forge","keep","vale","fell","fen","mere"] }
60fn place_suffixes() -> &'static [&'static str] { &["heim","moor","fell","fen","vale","keep","hold","croft","wick","ford","ton","burg"] }
61
62fn human_prefixes() -> &'static [&'static str] { &["Ald","Bar","Cal","Dan","Ed","Fred","Gar","Hal","Ing","Jon","Karl","Leo","Mar","Nor","Osw"] }
63fn human_middles() -> &'static [&'static str] { &["ric","win","ald","bert","ward","mund","gar","helm","ulf","frid","her","wig","rath","run"] }
64fn human_suffixes() -> &'static [&'static str] { &["son","sen","man","ard","ert","olf","ric","and","ley","ford","ton","ham","worth"] }
65
66const DARK_TITLES: &[&str] = &["the Cursed","the Defiler","Shadowbane","Deathmarch","the Undying","Soul Reaver","the Ancient"];
69const ORCISH_TITLES: &[&str] = &["Skull Crusher","Ironhide","Bonecleaver","Warlord","Bloodaxe","the Mighty","the Feared"];
70const ARCANE_TITLES: &[&str] = &["the Wise","Spellweaver","Runemaster","the Arcane","of the Inner Eye","the Eternal"];
71const PLACE_ADJECTIVES: &[&str] = &["Ancient","Cursed","Forsaken","Eternal","Ruined","Shadowed","Lost","Forgotten","Sunken"];
72
73#[derive(Clone, Debug)]
77pub struct NameGenerator {
78 pub style: NameStyle,
79}
80
81impl NameGenerator {
82 pub fn new(style: NameStyle) -> Self { Self { style } }
83
84 pub fn generate(&self, rng: &mut Rng) -> String {
86 self.generate_with_title(rng, false)
87 }
88
89 pub fn generate_with_title(&self, rng: &mut Rng, add_title: bool) -> String {
91 let name = match self.style {
92 NameStyle::Dark => self.build(rng, dark_prefixes(), dark_middles(), dark_suffixes()),
93 NameStyle::Elvish => self.build(rng, elvish_prefixes(), elvish_middles(), elvish_suffixes()),
94 NameStyle::Orcish => self.build(rng, orcish_prefixes(), orcish_middles(), orcish_suffixes()),
95 NameStyle::Arcane => self.build(rng, arcane_prefixes(), arcane_middles(), arcane_suffixes()),
96 NameStyle::Draconic => self.build(rng, draconic_prefixes(), draconic_middles(), draconic_suffixes()),
97 NameStyle::Void => self.build_void(rng),
98 NameStyle::Place => self.build_place(rng),
99 NameStyle::Human => self.build(rng, human_prefixes(), human_middles(), human_suffixes()),
100 };
101
102 if add_title {
103 let titles: &[&str] = match self.style {
104 NameStyle::Dark | NameStyle::Draconic => DARK_TITLES,
105 NameStyle::Orcish => ORCISH_TITLES,
106 NameStyle::Arcane | NameStyle::Elvish => ARCANE_TITLES,
107 _ => DARK_TITLES,
108 };
109 if let Some(&title) = rng.pick(titles) {
110 if rng.chance(0.35) {
111 return format!("{name} {title}");
112 }
113 }
114 }
115 name
116 }
117
118 fn build(&self, rng: &mut Rng, pre: &[&str], mid: &[&str], suf: &[&str]) -> String {
119 let p = rng.pick(pre).copied().unwrap_or("Ka");
120 let s = rng.pick(suf).copied().unwrap_or("ar");
121
122 if rng.chance(0.6) || mid.is_empty() {
123 capitalize(&format!("{p}{s}"))
125 } else {
126 let m = rng.pick(mid).copied().unwrap_or("an");
127 capitalize(&format!("{p}{m}{s}"))
128 }
129 }
130
131 fn build_void(&self, rng: &mut Rng) -> String {
132 let p = rng.pick(void_prefixes()).copied().unwrap_or("Zyx");
134 let s = rng.pick(void_suffixes()).copied().unwrap_or("xyr");
135 if rng.chance(0.4) {
136 let m = rng.pick(void_middles()).copied().unwrap_or("hz");
137 format!("{p}{m}{s}")
138 } else {
139 format!("{p}{s}")
140 }
141 }
142
143 fn build_place(&self, rng: &mut Rng) -> String {
144 let adj = if rng.chance(0.4) {
145 rng.pick(PLACE_ADJECTIVES).copied().unwrap_or("")
146 } else { "" };
147 let p = rng.pick(place_prefixes()).copied().unwrap_or("Dark");
148 let m = rng.pick(place_middles()).copied().unwrap_or("keep");
149 let s = if rng.chance(0.4) {
150 rng.pick(place_suffixes()).copied().unwrap_or("")
151 } else { "" };
152 let base = if s.is_empty() {
153 format!("{p}{m}")
154 } else {
155 format!("{p}{m}{s}")
156 };
157 if adj.is_empty() { base } else { format!("{adj} {base}") }
158 }
159
160 pub fn generate_n(&self, rng: &mut Rng, n: usize) -> Vec<String> {
162 let mut names = std::collections::HashSet::new();
163 let mut result = Vec::with_capacity(n);
164 let mut attempts = 0usize;
165 while result.len() < n && attempts < n * 10 {
166 let name = self.generate(rng);
167 if names.insert(name.clone()) {
168 result.push(name);
169 }
170 attempts += 1;
171 }
172 result
173 }
174}
175
176fn capitalize(s: &str) -> String {
177 let mut c = s.chars();
178 match c.next() {
179 None => String::new(),
180 Some(f) => f.to_uppercase().collect::<String>() + &c.as_str().to_lowercase(),
181 }
182}
183
184pub struct NameTable {
188 names: Vec<String>,
189 cursor: usize,
190}
191
192impl NameTable {
193 pub fn generate(style: NameStyle, count: usize, seed: u64) -> Self {
194 let mut rng = Rng::new(seed);
195 let gen = NameGenerator::new(style);
196 let names = gen.generate_n(&mut rng, count);
197 Self { names, cursor: 0 }
198 }
199
200 pub fn next(&mut self) -> &str {
202 if self.names.is_empty() { return "Unknown"; }
203 let name = &self.names[self.cursor % self.names.len()];
204 self.cursor += 1;
205 name
206 }
207
208 pub fn peek(&self, i: usize) -> Option<&str> {
209 self.names.get(i % self.names.len().max(1)).map(|s| s.as_str())
210 }
211
212 pub fn len(&self) -> usize { self.names.len() }
213}
214
215#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn dark_name_non_empty() {
223 let mut rng = Rng::new(42);
224 let gen = NameGenerator::new(NameStyle::Dark);
225 let name = gen.generate(&mut rng);
226 assert!(!name.is_empty());
227 assert!(name.chars().next().unwrap().is_uppercase());
228 }
229
230 #[test]
231 fn all_styles_produce_names() {
232 let mut rng = Rng::new(99);
233 for style in &[NameStyle::Dark, NameStyle::Elvish, NameStyle::Orcish,
234 NameStyle::Arcane, NameStyle::Draconic, NameStyle::Void,
235 NameStyle::Place, NameStyle::Human] {
236 let gen = NameGenerator::new(*style);
237 let name = gen.generate(&mut rng);
238 assert!(!name.is_empty(), "Empty name for style {:?}", style);
239 }
240 }
241
242 #[test]
243 fn generate_n_returns_n_unique() {
244 let mut rng = Rng::new(12345);
245 let gen = NameGenerator::new(NameStyle::Human);
246 let names = gen.generate_n(&mut rng, 20);
247 assert!(names.len() >= 10);
249 }
250
251 #[test]
252 fn name_table_wraps() {
253 let mut table = NameTable::generate(NameStyle::Dark, 5, 777);
254 let first = table.next().to_string();
255 for _ in 0..4 { table.next(); }
256 let again = table.next().to_string();
257 assert_eq!(first, again);
258 }
259}