use crate::db::{ConnectionPool, DbError, Rows}; use crate::message_broker::MessageBroker; use rand::distributions::{Distribution, Uniform}; use rand::rngs::StdRng; use rand::{thread_rng, Rng, SeedableRng}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use std::time::Duration; use super::base::{BaseWorker, Worker, WorkerState}; use crate::worker::sql::{ QUERY_IS_PREVIOUS_DAY_CHARACTER_CREATED, QUERY_GET_TOWN_REGION_IDS, QUERY_LOAD_FIRST_NAMES, QUERY_LOAD_LAST_NAMES, QUERY_INSERT_CHARACTER, QUERY_GET_ELIGIBLE_NPC_FOR_DEATH, QUERY_DELETE_DIRECTOR, QUERY_DELETE_RELATIONSHIP, QUERY_DELETE_CHILD_RELATION, QUERY_INSERT_NOTIFICATION, QUERY_MARK_CHARACTER_DECEASED, }; pub struct CharacterCreationWorker { pub(crate) base: BaseWorker, rng: StdRng, dist: Uniform, first_name_cache: HashMap>, last_name_cache: HashSet, death_check_running: Arc, death_thread: Option>, } impl CharacterCreationWorker { pub fn new(pool: ConnectionPool, broker: MessageBroker) -> Self { Self::new_internal(pool, broker, true) } /// Interner Konstruktor, der optional den NPC-Todes-Monitor startet. fn new_internal(pool: ConnectionPool, broker: MessageBroker, start_death_thread: bool) -> Self { let base = BaseWorker::new("CharacterCreationWorker", pool.clone(), broker.clone()); let rng = StdRng::from_entropy(); let dist = Uniform::from(2..=3); let death_check_running = Arc::new(AtomicBool::new(start_death_thread)); let death_thread = if start_death_thread { let death_flag = Arc::clone(&death_check_running); let pool_clone = pool; let broker_clone = broker; Some(thread::spawn(move || { while death_flag.load(Ordering::Relaxed) { if let Err(err) = CharacterCreationWorker::monitor_character_deaths(&pool_clone, &broker_clone) { eprintln!( "[CharacterCreationWorker] Fehler beim Überprüfen von NPC-Todesfällen: {err}" ); } // Warte 1 Stunde, aber mit frühem Abbruch, wenn death_flag false wird for _ in 0..3600 { if !death_flag.load(Ordering::Relaxed) { break; } thread::sleep(Duration::from_secs(1)); } } })) } else { None }; Self { base, rng, dist, first_name_cache: HashMap::new(), last_name_cache: HashSet::new(), death_check_running, death_thread, } } /// Variante ohne separaten Todes-Monitor-Thread – wird nur in der Worker-Loop benutzt. fn new_for_loop(pool: ConnectionPool, broker: MessageBroker) -> Self { Self::new_internal(pool, broker, false) } fn is_today_character_created(&self) -> bool { match self.fetch_today_characters() { Ok(rows) => !rows.is_empty(), Err(err) => { eprintln!( "[CharacterCreationWorker] Fehler in is_today_character_created: {err}" ); false } } } fn fetch_today_characters(&self) -> Result { const STMT_NAME: &str = "is_previous_day_character_created"; let mut conn = self .base .pool .get() .map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare(STMT_NAME, QUERY_IS_PREVIOUS_DAY_CHARACTER_CREATED)?; conn.execute(STMT_NAME, &[]) } fn create_characters_for_today(&mut self) { self.load_names(); if self.first_name_cache.is_empty() || self.last_name_cache.is_empty() { eprintln!( "[CharacterCreationWorker] Fehler: Namen konnten nicht geladen werden (Stub-Implementierung)." ); return; } let town_ids = self.get_town_region_ids(); for region_id in town_ids { self.create_characters_for_region(region_id); } } fn create_characters_for_region(&mut self, region_id: i32) { let nobility_stands = [1, 2, 3]; let genders = ["male", "female"]; for &nobility in &nobility_stands { for &gender in &genders { let num_chars = self.rng.sample(self.dist); for _ in 0..num_chars { self.create_character(region_id, gender, nobility); } } } } fn create_character(&mut self, region_id: i32, gender: &str, title_of_nobility: i32) { let first_set = self .first_name_cache .get(gender) .cloned() .unwrap_or_default(); let first_name_id = Self::get_random_from_set(&first_set); if first_name_id == -1 { eprintln!("[CharacterCreationWorker] Fehler: Kein passender Vorname gefunden."); return; } let last_name_id = Self::get_random_from_set(&self.last_name_cache); if last_name_id == -1 { eprintln!("[CharacterCreationWorker] Fehler: Kein passender Nachname gefunden."); return; } if let Err(err) = Self::insert_character( &self.base.pool, region_id, first_name_id, last_name_id, gender, title_of_nobility, ) { eprintln!("[CharacterCreationWorker] Fehler in createCharacter: {err}"); } } fn get_town_region_ids(&self) -> Vec { match self.load_town_region_ids() { Ok(rows) => rows .into_iter() .filter_map(|row| row.get("id")?.parse::().ok()) .collect(), Err(err) => { eprintln!( "[CharacterCreationWorker] Fehler in getTownRegionIds: {err}" ); Vec::new() } } } fn load_town_region_ids(&self) -> Result { const STMT_NAME: &str = "get_town_region_ids"; let mut conn = self .base .pool .get() .map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare(STMT_NAME, QUERY_GET_TOWN_REGION_IDS)?; conn.execute(STMT_NAME, &[]) } fn load_names(&mut self) { if (self.first_name_cache.is_empty() || self.last_name_cache.is_empty()) && let Err(err) = self.load_first_and_last_names() { eprintln!("[CharacterCreationWorker] Fehler in loadNames: {err}"); } } fn load_first_and_last_names(&mut self) -> Result<(), crate::db::DbError> { let mut conn = self .base .pool .get() .map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; // Vornamen conn.prepare("load_first_names", QUERY_LOAD_FIRST_NAMES)?; let first_rows = conn.execute("load_first_names", &[])?; for row in first_rows { let id = match row.get("id").and_then(|v| v.parse::().ok()) { Some(id) => id, None => continue, }; let gender = row.get("gender").cloned().unwrap_or_default(); self.first_name_cache.entry(gender).or_default().insert(id); } // Nachnamen conn.prepare("load_last_names", QUERY_LOAD_LAST_NAMES)?; let last_rows = conn.execute("load_last_names", &[])?; for row in last_rows { if let Some(id) = row.get("id").and_then(|v| v.parse::().ok()) { self.last_name_cache.insert(id); } } Ok(()) } fn get_random_from_set(set: &HashSet) -> i32 { if set.is_empty() { return -1; } let mut rng = thread_rng(); let idx = rng.gen_range(0..set.len()); *set.iter().nth(idx).unwrap_or(&-1) } fn run_iteration(&mut self, state: &WorkerState) { self.base .set_current_step("Check if previous day character was created"); if !self.is_today_character_created() { self.base .set_current_step("Create characters for today"); self.create_characters_for_today(); } self.sleep_one_minute(state); } fn sleep_one_minute(&self, state: &WorkerState) { self.base .set_current_step("Sleep for 60 seconds"); for _ in 0..60 { if !state.running_worker.load(Ordering::Relaxed) { break; } thread::sleep(Duration::from_secs(1)); } self.base.set_current_step("Loop done"); } } impl Worker for CharacterCreationWorker { fn start_worker_thread(&mut self) { let pool = self.base.pool.clone(); let broker = self.base.broker.clone(); self.base .start_worker_with_loop(move |state: Arc| { let mut worker = CharacterCreationWorker::new_for_loop(pool.clone(), broker.clone()); while state.running_worker.load(Ordering::Relaxed) { worker.run_iteration(&state); } }); } fn stop_worker_thread(&mut self) { self.base.stop_worker(); } fn enable_watchdog(&mut self) { self.base.start_watchdog(); } } impl Drop for CharacterCreationWorker { fn drop(&mut self) { self.death_check_running .store(false, Ordering::Relaxed); if let Some(handle) = self.death_thread.take() { let _ = handle.join(); } } } // Zusätzliche Logik: NPC-Todesfälle überwachen und verarbeiten impl CharacterCreationWorker { fn insert_character( pool: &ConnectionPool, region_id: i32, first_name_id: i32, last_name_id: i32, gender: &str, title_of_nobility: i32, ) -> Result<(), DbError> { let mut conn = pool .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare("insert_character", QUERY_INSERT_CHARACTER)?; conn.execute( "insert_character", &[ ®ion_id, &first_name_id, &last_name_id, &gender, &title_of_nobility, ], )?; Ok(()) } fn monitor_character_deaths( pool: &ConnectionPool, broker: &MessageBroker, ) -> Result<(), DbError> { let mut conn = pool .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare( "get_eligible_npc_for_death", QUERY_GET_ELIGIBLE_NPC_FOR_DEATH, )?; let rows = conn.execute("get_eligible_npc_for_death", &[])?; for row in rows { let character_id = row .get("id") .and_then(|v| v.parse::().ok()) .unwrap_or(-1); let age = row .get("age") .and_then(|v| v.parse::().ok()) .unwrap_or(0); if character_id > 0 && Self::calculate_death_probability(age) && let Err(err) = Self::handle_character_death(pool, broker, character_id) { eprintln!( "[CharacterCreationWorker] Fehler beim Bearbeiten des NPC-Todes (id={character_id}): {err}" ); } } Ok(()) } fn calculate_death_probability(age: i32) -> bool { if age < 60 { return false; } let base_probability = 0.01_f64; let increase_per_year = 0.01_f64; let death_probability = base_probability + increase_per_year * (age.saturating_sub(60) as f64); let mut rng = thread_rng(); let dist = Uniform::from(0.0..1.0); let roll: f64 = dist.sample(&mut rng); roll < death_probability } fn handle_character_death( pool: &ConnectionPool, broker: &MessageBroker, character_id: i32, ) -> Result<(), DbError> { let mut conn = pool .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; // 1) Director löschen und User benachrichtigen conn.prepare("delete_director", QUERY_DELETE_DIRECTOR)?; let dir_result = conn.execute("delete_director", &[&character_id])?; if let Some(row) = dir_result.first() && let Some(user_id) = row .get("employer_user_id") .and_then(|v| v.parse::().ok()) { Self::notify_user(pool, broker, user_id, "director_death")?; } // 2) Relationships löschen und betroffene User benachrichtigen conn.prepare("delete_relationship", QUERY_DELETE_RELATIONSHIP)?; let rel_result = conn.execute("delete_relationship", &[&character_id])?; for row in rel_result { if let Some(related_user_id) = row .get("related_user_id") .and_then(|v| v.parse::().ok()) { Self::notify_user(pool, broker, related_user_id, "relationship_death")?; } } // 3) Child-Relations löschen und Eltern benachrichtigen conn.prepare("delete_child_relation", QUERY_DELETE_CHILD_RELATION)?; let child_result = conn.execute("delete_child_relation", &[&character_id])?; for row in child_result { if let Some(father_user_id) = row .get("father_user_id") .and_then(|v| v.parse::().ok()) { Self::notify_user(pool, broker, father_user_id, "child_death")?; } if let Some(mother_user_id) = row .get("mother_user_id") .and_then(|v| v.parse::().ok()) { Self::notify_user(pool, broker, mother_user_id, "child_death")?; } } // 4) Charakter als verstorben markieren Self::mark_character_as_deceased(pool, character_id)?; Ok(()) } fn notify_user( pool: &ConnectionPool, broker: &MessageBroker, user_id: i32, event_type: &str, ) -> Result<(), DbError> { let mut conn = pool .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare("insert_notification", QUERY_INSERT_NOTIFICATION)?; conn.execute("insert_notification", &[&user_id])?; // falukantUpdateStatus let update_message = format!(r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, user_id); broker.publish(update_message); // ursprüngliche Benachrichtigung let message = format!(r#"{{"event":"{event_type}","user_id":{}}}"#, user_id); broker.publish(message); Ok(()) } fn mark_character_as_deceased( pool: &ConnectionPool, character_id: i32, ) -> Result<(), DbError> { let mut conn = pool .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; conn.prepare("mark_character_deceased", QUERY_MARK_CHARACTER_DECEASED)?; conn.execute("mark_character_deceased", &[&character_id])?; Ok(()) } }