1use once_cell::sync::Lazy;
4use rand::prelude::*;
5use serde::{Deserialize, Serialize};
6use std::{
7 fs, io,
8 path::{Path, PathBuf},
9};
10
11use crate::Displayable;
12use crate::entity::{EffectApplication, EffectBounds, EntityDuration};
13use crate::error::EpochError;
14use crate::stats::{EntityType, StatPair, Symbols};
15
16#[derive(Debug, Serialize, Deserialize, Default)]
18pub struct NamingData {
19 pub first_names: Vec<String>,
21 pub last_names: Vec<String>,
23 pub town_prefixes: Vec<String>,
25 pub town_suffixes: Vec<String>,
27
28 pub blocker_prefixes: Vec<String>,
30 pub blocker_suffixes: Vec<String>,
32 pub character_adjectives: Vec<String>,
34 pub character_prefixes: Vec<String>,
36
37 pub exotic_prefixes: Vec<String>,
40 pub exotic_infixes: Vec<String>,
42 pub exotic_suffixes: Vec<String>,
44 pub mystical_adjectives: Vec<String>,
46 pub event_ideas: Vec<EventDescription>,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51pub struct EventDescription {
53 pub name: String,
55
56 pub description: String,
58
59 pub stats: Vec<StatPair>,
62
63 pub duration: EntityDuration,
65
66 pub application: EffectApplication,
68
69 pub effect_bounds: EffectBounds,
71}
72
73impl Displayable for EventDescription {
74 fn oneline_display(&self) -> String {
75 let (description, _) = self.description.split_at(110.min(self.description.len()));
76 let line1 = format!(
77 "├─ {} {} ──────> {}{} \n\
78 │ ├─ {}",
79 self.name,
80 EntityType::Event.to_symbol(),
81 self.duration.to_symbol(),
82 self.effect_bounds.to_symbol(),
83 description,
84 );
85
86 let mut line2 = "│ └─ Stats: ".to_string();
87 for effect in &self.stats {
88 line2.push_str(&format!("{}:{} ", effect.stat.to_shortest(), effect.value,));
89 }
90
91 format!("{line1}\n{line2}")
92 }
93}
94
95impl NamingData {
96 pub fn load_or_create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
100 let path = path.as_ref();
101
102 if path.exists() {
103 let contents = fs::read_to_string(path)?;
105 let data: Self = serde_json::from_str(&contents)?;
106 Ok(data)
107 } else {
108 let default =
110 serde_json::from_str(DEFAULT_JSON_STR).expect("Default JSON is malformed");
111 if let Some(parent) = path.parent() {
113 fs::create_dir_all(parent)?;
114 }
115 let json = serde_json::to_string_pretty(&default)?;
116 fs::write(path, json)?;
117 Ok(default)
118 }
119 }
120
121 pub fn generate_character_name(&self) -> String {
123 let mut rng = rand::rng();
124
125 let binding = "Bobman".into();
126 let first = self.first_names.choose(&mut rng).unwrap_or(&binding);
127 let binding = "McBobintown".into();
128 let last = self.last_names.choose(&mut rng).unwrap_or(&binding);
129 format!("{} {}", first, last).trim().to_string()
130 }
131
132 pub fn generate_town_name(&self) -> String {
134 let mut rng = rand::rng();
135 let binding = "Bobtown".into();
136 let prefix = self.town_prefixes.choose(&mut rng).unwrap_or(&binding);
137 let suffix = self.town_suffixes.choose(&mut rng).unwrap_or(&binding);
138 format!("{}{}", prefix, suffix).trim().to_string()
139 }
140
141 #[allow(clippy::unwrap_used)]
143 pub fn random_event_idea(&self) -> Result<&EventDescription, EpochError> {
144 let mut rng = rand::rng();
145 let event_desc = self
146 .event_ideas
147 .choose(&mut rng)
148 .ok_or(EpochError::RNGPickError {
149 cause: "Picking a random event".into(),
150 help: None,
151 })?;
152 Ok(event_desc)
157 }
158
159 #[allow(clippy::unwrap_used)]
161 pub fn random_event_name(&self) -> String {
162 if let Ok(event) = self.random_event_idea() {
163 event.name.clone()
164 } else {
165 "The Great Bobbening".to_owned()
166 }
167 }
168
169 pub fn generate_blocker_name(&self) -> String {
175 let mut rng = rand::rng();
176
177 let binding = "Mystic ".to_string();
178 let prefix = self.blocker_prefixes.choose(&mut rng).unwrap_or(&binding);
179
180 let binding = "Barrier".to_string();
181 let suffix = self.blocker_suffixes.choose(&mut rng).unwrap_or(&binding);
182
183 format!("{} {}", prefix, suffix).trim().to_string()
184 }
185
186 pub fn generate_calamity_name(&self) -> String {
193 let mut rng = rand::rng();
194
195 let prefix = self
198 .exotic_prefixes
199 .choose(&mut rng)
200 .unwrap_or(&"Umbral".to_string())
201 .clone();
202
203 let infix_count = rng.random_range(2..=4);
205 let infixes: String = (0..infix_count)
206 .map(|_| {
207 self.exotic_infixes
208 .choose(&mut rng)
209 .expect("exotic_infixes must not be empty")
210 .clone()
211 })
212 .collect();
213
214 let suffix = self
216 .exotic_suffixes
217 .choose(&mut rng)
218 .cloned()
219 .unwrap_or_else(String::new);
220
221 let base = format!("{}{}{}", prefix, infixes, suffix);
223
224 let adjective = self
226 .mystical_adjectives
227 .choose(&mut rng)
228 .unwrap_or(&"Cataclysmic".to_string())
229 .clone();
230
231 format!("{} {}", base, adjective).trim().to_string()
232 }
233
234 pub fn generate_long_character_name(&self) -> String {
239 let mut rng = rand::rng();
240
241 let adjective = self.character_adjectives.choose(&mut rng).cloned();
243 let prefix = self.character_prefixes.choose(&mut rng).cloned();
244
245 let base = self.generate_character_name();
247
248 let mut parts = Vec::new();
250
251 if let Some(pre) = prefix
252 && rand::random_bool(0.3)
253 {
254 parts.push(pre);
255 }
256
257 parts.push(base);
258
259 if let Some(adj) = adjective
260 && rand::random_bool(0.3)
261 {
262 parts.push(adj);
263 }
264
265 parts.join(" ")
266 }
267
268 #[allow(clippy::unwrap_used)]
270 pub fn generate_exotic_character_name(&self) -> String {
271 let mut rng = rand::rng();
272
273 let first_len = rng.random_range(2..=4);
275 let pref: &String = self.exotic_prefixes.choose(&mut rng).unwrap();
276 let pref2: &String = self.exotic_prefixes.choose(&mut rng).unwrap();
277
278 let first: String = (0..first_len)
279 .map(|_| self.exotic_infixes.choose(&mut rng).unwrap())
280 .cloned()
281 .collect();
282
283 let last_len = rng.random_range(1..=3);
285 let last: String = (0..last_len)
286 .map(|_| self.exotic_infixes.choose(&mut rng).unwrap())
287 .cloned()
288 .collect();
289
290 format!("{pref}{} {pref2}{}", &first, &last)
292 }
293}
294
295const DEFAULT_JSON_STR: &str = include_str!("../naming.json");
307
308fn naming_file_path() -> PathBuf {
310 PathBuf::from("naming.json")
311}
312
313pub static NAMING: Lazy<NamingData> = Lazy::new(|| {
315 let path = naming_file_path();
316 NamingData::load_or_create(path).expect("Failed to load naming data")
317});
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use crate::stats::ListStats;
323 use std::collections::HashMap;
324
325 #[test]
326 fn try_load() {}
327 #[test]
328 fn basic_testing() {
329 let character = NAMING.generate_character_name();
331 println!("Character: {}", character);
332
333 let town = NAMING.generate_town_name();
335 println!("Town: {}", town);
336
337 let character = NAMING.generate_long_character_name();
339 println!("Event idea: {}", character);
340
341 let _ = NAMING.random_event_idea();
342 }
343
344 #[test]
345 fn test_all_events() {
347 let mut values: HashMap<ListStats, usize> = HashMap::new();
348 for event in &NAMING.event_ideas {
349 println!("Testing event: {:?}", event);
350 assert!(event.stats.len() > 1);
351
352 for stat in &event.stats {
353 if let s @ (ListStats::Demographics
354 | ListStats::Military
355 | ListStats::Economy
356 | ListStats::Social
357 | ListStats::Infrastructure) = stat.stat
358 {
359 panic!("Bad stat used {s:?}")
360 };
361 match stat.value.abs() {
362 0..5 => (),
363 5..10 => (),
364 10..20 => println!("Big stat"),
365 s => panic!("The event has a stat that is way too big {s}"),
366 }
367 let base_value = values.get(&stat.stat).unwrap_or(&0);
368 values.insert(stat.stat, base_value + 1);
369 }
370 }
371 println!("{values:#?}");
372 }
374 #[test]
375 fn test_most_naming() {
376 let _ = NAMING.generate_character_name();
377 let _ = NAMING.generate_town_name();
378 let _ = NAMING.random_event_idea().expect("Couldn't generate event");
379 let _ = NAMING.random_event_name();
380 let _ = NAMING.generate_blocker_name();
381 let _ = NAMING.generate_calamity_name();
382 let _ = NAMING.generate_long_character_name();
383 let _ = NAMING.generate_exotic_character_name();
384 }
385
386 #[test]
387 fn event_description() {
388 let event = NAMING.random_event_idea().expect("Generating event");
389 let _ = event.oneline_display();
390 }
391}