diff --git a/src/worker/events.rs b/src/worker/events.rs index c96cfd6..fe2e05b 100644 --- a/src/worker/events.rs +++ b/src/worker/events.rs @@ -101,6 +101,13 @@ pub enum EventEffect { HouseQualityChange { probability: f64, min_change: i32, max_change: i32 }, } +/// Hilfsstruktur für Character-Informationen aus Effekten +struct CharacterInfo { + character_id: Option, + first_name: Option, + last_name: Option, +} + /// Definition eines zufälligen Ereignisses #[derive(Debug, Clone)] pub struct RandomEvent { @@ -530,24 +537,11 @@ impl EventsWorker { event: &RandomEvent, rng: &mut impl Rng, ) -> Result<(), DbError> { - let mut conn = pool - .get() - .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - - // Spezielle Behandlung für plötzlichen Kindstod: Finde ein zufälliges Kind unter 2 Jahren if event.id == "sudden_infant_death" { return Self::trigger_sudden_infant_death(pool, broker, event, rng); } - conn.prepare("get_random_user", QUERY_GET_RANDOM_USER)?; - let rows = conn.execute("get_random_user", &[])?; - - let user_id: Option = rows - .first() - .and_then(|r| r.get("id")) - .and_then(|v| v.parse::().ok()); - - let user_id = match user_id { + let user_id = match Self::get_random_user_id(pool)? { Some(id) => id, None => { eprintln!("[EventsWorker] Kein Spieler gefunden für persönliches Ereignis"); @@ -555,136 +549,248 @@ impl EventsWorker { } }; - // Wende Effekte an + let mut conn = pool + .get() + .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + + let effect_results = Self::apply_personal_effects(&mut conn, pool, broker, user_id, event, rng)?; + + if effect_results.is_empty() { + eprintln!( + "[EventsWorker] Persönliches Ereignis '{}' für Spieler {} übersprungen (keine Effekte)", + event.id, user_id + ); + return Ok(()); + } + + let char_info = Self::extract_character_info_from_effects(&effect_results); + Self::send_personal_event_notifications(pool, broker, user_id, event, &effect_results, &char_info)?; + + eprintln!( + "[EventsWorker] Persönliches Ereignis '{}' für Spieler {} verarbeitet", + event.id, user_id + ); + + Ok(()) + } + + fn get_random_user_id(pool: &ConnectionPool) -> Result, DbError> { + let mut conn = pool + .get() + .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; + + conn.prepare("get_random_user", QUERY_GET_RANDOM_USER)?; + let rows = conn.execute("get_random_user", &[])?; + + Ok(rows + .first() + .and_then(|r| r.get("id")) + .and_then(|v| v.parse::().ok())) + } + + fn apply_personal_effects( + conn: &mut DbConnection, + pool: &ConnectionPool, + broker: &MessageBroker, + user_id: i32, + event: &RandomEvent, + rng: &mut impl Rng, + ) -> Result, DbError> { let mut effect_results = Vec::new(); + for effect in &event.effects { let effect_roll = rng.gen_range(0.0..=1.0); - match effect { - EventEffect::MoneyChange { - probability, - min_percent, - max_percent, - } => { - if effect_roll < *probability { - let percent_change = rng.gen_range(*min_percent..=*max_percent); - if let Ok(absolute_change) = Self::apply_money_change(&mut conn, user_id, percent_change) { - effect_results.push(json!({ - "type": "money_change", - "percent": percent_change, - "absolute": absolute_change - })); - } - } - } - EventEffect::StorageCapacityChange { - probability, - min_percent, - max_percent, - } => { - if effect_roll < *probability { - let percent_change = rng.gen_range(*min_percent..=*max_percent); - Self::apply_storage_capacity_change(&mut conn, user_id, percent_change)?; - effect_results.push(json!({ - "type": "storage_capacity_change", - "percent": percent_change - })); - } - } - EventEffect::CharacterHealthChange { - probability, - min_change, - max_change, - } => { - if effect_roll < *probability - && let Ok((character_id, health_change, died, first_name, last_name)) = Self::apply_character_health_change( - &mut conn, - pool, - broker, - user_id, - *min_change, - *max_change, - rng, - ) - { - effect_results.push(json!({ - "type": "character_health_change", - "character_id": character_id, - "change": health_change, - "character_first_name": first_name, - "character_last_name": last_name - })); - if died { - effect_results.push(json!({ - "type": "character_death", - "character_id": character_id, - "character_first_name": first_name, - "character_last_name": last_name - })); - } - } - } - EventEffect::CharacterDeath { probability } => { - if effect_roll < *probability - && let Ok((character_id, first_name, last_name)) = - Self::apply_character_death(&mut conn, user_id, pool, broker) - { - effect_results.push(json!({ - "type": "character_death", - "character_id": character_id, - "character_first_name": first_name, - "character_last_name": last_name - })); - } - } - EventEffect::StorageDamage { - probability, - stock_type_label, - inventory_damage_min_percent, - inventory_damage_max_percent, - storage_destruction_min_percent, - storage_destruction_max_percent, - } => { - if effect_roll < *probability - && let Ok(damage_info) = Self::apply_personal_storage_damage( - &mut conn, - PersonalStorageDamageParams { - user_id, - stock_type_label, - inventory_damage_min_percent: *inventory_damage_min_percent, - inventory_damage_max_percent: *inventory_damage_max_percent, - storage_destruction_min_percent: *storage_destruction_min_percent, - storage_destruction_max_percent: *storage_destruction_max_percent, - }, - rng, - ) - { - effect_results.push(json!({ - "type": "storage_damage", - "stock_type": stock_type_label, - "inventory_damage_percent": damage_info.inventory_damage_percent, - "storage_destruction_percent": damage_info.storage_destruction_percent, - "affected_stocks": damage_info.affected_stocks, - "destroyed_stocks": damage_info.destroyed_stocks, - })); - } - } - _ => { - eprintln!( - "[EventsWorker] Effekt {:?} wird für persönliche Ereignisse noch nicht unterstützt", - effect - ); - } + if let Some(result) = Self::apply_single_personal_effect(conn, pool, broker, user_id, effect, effect_roll, rng)? { + effect_results.extend(result); } } - // Schreibe Benachrichtigung in die Datenbank mit Event-Details - // If any effect contains a character_id, include it at top-level for the notification - let top_character_id = effect_results.iter().find_map(|eff| { + Ok(effect_results) + } + + fn apply_single_personal_effect( + conn: &mut DbConnection, + pool: &ConnectionPool, + broker: &MessageBroker, + user_id: i32, + effect: &EventEffect, + effect_roll: f64, + rng: &mut impl Rng, + ) -> Result>, DbError> { + match effect { + EventEffect::MoneyChange { probability, min_percent, max_percent } => { + Self::handle_money_change_effect(conn, user_id, effect_roll, *probability, *min_percent, *max_percent, rng) + } + EventEffect::StorageCapacityChange { probability, min_percent, max_percent } => { + Self::handle_storage_capacity_effect(conn, user_id, effect_roll, *probability, *min_percent, *max_percent, rng) + } + EventEffect::CharacterHealthChange { probability, min_change, max_change } => { + Self::handle_character_health_effect(conn, pool, broker, user_id, effect_roll, *probability, *min_change, *max_change, rng) + } + EventEffect::CharacterDeath { probability } => { + Self::handle_character_death_effect(conn, pool, broker, user_id, effect_roll, *probability) + } + EventEffect::StorageDamage { probability, stock_type_label, inventory_damage_min_percent, inventory_damage_max_percent, storage_destruction_min_percent, storage_destruction_max_percent } => { + Self::handle_storage_damage_effect(conn, user_id, effect_roll, *probability, stock_type_label, *inventory_damage_min_percent, *inventory_damage_max_percent, *storage_destruction_min_percent, *storage_destruction_max_percent, rng) + } + _ => { + eprintln!("[EventsWorker] Effekt {:?} wird für persönliche Ereignisse noch nicht unterstützt", effect); + Ok(None) + } + } + } + + fn handle_money_change_effect( + conn: &mut DbConnection, + user_id: i32, + effect_roll: f64, + probability: f64, + min_percent: f64, + max_percent: f64, + rng: &mut impl Rng, + ) -> Result>, DbError> { + if effect_roll >= probability { + return Ok(None); + } + let percent_change = rng.gen_range(min_percent..=max_percent); + if let Ok(absolute_change) = Self::apply_money_change(conn, user_id, percent_change) { + Ok(Some(vec![json!({ + "type": "money_change", + "percent": percent_change, + "absolute": absolute_change + })])) + } else { + Ok(None) + } + } + + fn handle_storage_capacity_effect( + conn: &mut DbConnection, + user_id: i32, + effect_roll: f64, + probability: f64, + min_percent: f64, + max_percent: f64, + rng: &mut impl Rng, + ) -> Result>, DbError> { + if effect_roll >= probability { + return Ok(None); + } + let percent_change = rng.gen_range(min_percent..=max_percent); + Self::apply_storage_capacity_change(conn, user_id, percent_change)?; + Ok(Some(vec![json!({ + "type": "storage_capacity_change", + "percent": percent_change + })])) + } + + fn handle_character_health_effect( + conn: &mut DbConnection, + pool: &ConnectionPool, + broker: &MessageBroker, + user_id: i32, + effect_roll: f64, + probability: f64, + min_change: i32, + max_change: i32, + rng: &mut impl Rng, + ) -> Result>, DbError> { + if effect_roll >= probability { + return Ok(None); + } + let Ok((character_id, health_change, died, first_name, last_name)) = + Self::apply_character_health_change(conn, pool, broker, user_id, min_change, max_change, rng) + else { + return Ok(None); + }; + + let mut results = vec![json!({ + "type": "character_health_change", + "character_id": character_id, + "change": health_change, + "character_first_name": first_name, + "character_last_name": last_name + })]; + + if died { + results.push(json!({ + "type": "character_death", + "character_id": character_id, + "character_first_name": first_name, + "character_last_name": last_name + })); + } + + Ok(Some(results)) + } + + fn handle_character_death_effect( + conn: &mut DbConnection, + pool: &ConnectionPool, + broker: &MessageBroker, + user_id: i32, + effect_roll: f64, + probability: f64, + ) -> Result>, DbError> { + if effect_roll >= probability { + return Ok(None); + } + let Ok((character_id, first_name, last_name)) = Self::apply_character_death(conn, user_id, pool, broker) else { + return Ok(None); + }; + Ok(Some(vec![json!({ + "type": "character_death", + "character_id": character_id, + "character_first_name": first_name, + "character_last_name": last_name + })])) + } + + fn handle_storage_damage_effect( + conn: &mut DbConnection, + user_id: i32, + effect_roll: f64, + probability: f64, + stock_type_label: &str, + inventory_damage_min_percent: f64, + inventory_damage_max_percent: f64, + storage_destruction_min_percent: f64, + storage_destruction_max_percent: f64, + rng: &mut impl Rng, + ) -> Result>, DbError> { + if effect_roll >= probability { + return Ok(None); + } + let Ok(damage_info) = Self::apply_personal_storage_damage( + conn, + PersonalStorageDamageParams { + user_id, + stock_type_label, + inventory_damage_min_percent, + inventory_damage_max_percent, + storage_destruction_min_percent, + storage_destruction_max_percent, + }, + rng, + ) else { + return Ok(None); + }; + Ok(Some(vec![json!({ + "type": "storage_damage", + "stock_type": stock_type_label, + "inventory_damage_percent": damage_info.inventory_damage_percent, + "storage_destruction_percent": damage_info.storage_destruction_percent, + "affected_stocks": damage_info.affected_stocks, + "destroyed_stocks": damage_info.destroyed_stocks, + })])) + } + + fn extract_character_info_from_effects(effect_results: &[serde_json::Value]) -> CharacterInfo { + let character_id = effect_results.iter().find_map(|eff| { eff.get("character_id").and_then(|v| v.as_i64()).map(|n| n as i32) }); - // Optional: Namen-Snapshot aus Effekten extrahieren (auch wenn der Charakter danach stirbt/gelöscht wird) - let (top_first_name, top_last_name) = if let Some(cid) = top_character_id { + let (first_name, last_name) = if let Some(cid) = character_id { let eff = effect_results.iter().find(|e| { e.get("character_id") .and_then(|v| v.as_i64()) @@ -704,16 +810,18 @@ impl EventsWorker { (None, None) }; - // Keine Benachrichtigung erstellen, wenn kein Effekt angewendet wurde - if effect_results.is_empty() { - eprintln!( - "[EventsWorker] Persönliches Ereignis '{}' für Spieler {} übersprungen (keine Effekte)", - event.id, user_id - ); - return Ok(()); - } + CharacterInfo { character_id, first_name, last_name } + } - // Baue die Benachrichtigung gemäß EVENT_JSON_MESSAGES.md Struktur + fn send_personal_event_notifications( + pool: &ConnectionPool, + broker: &MessageBroker, + user_id: i32, + event: &RandomEvent, + effect_results: &[serde_json::Value], + char_info: &CharacterInfo, + ) -> Result<(), DbError> { + // DB-Benachrichtigung let mut notification_json = serde_json::json!({ "tr": format!("random_event.{}", event.id), "event_id": event.id, @@ -721,27 +829,20 @@ impl EventsWorker { "effects": effect_results }); - // Character-Informationen (falls vorhanden) - if let Some(cid) = top_character_id { + if let Some(cid) = char_info.character_id { notification_json["character_id"] = serde_json::json!(cid); } - if let Some(fn_) = top_first_name { + if let Some(ref fn_) = char_info.first_name { notification_json["character_first_name"] = serde_json::json!(fn_); } - if let Some(ln_) = top_last_name { + if let Some(ref ln_) = char_info.last_name { notification_json["character_last_name"] = serde_json::json!(ln_); } - Self::notify_user( - pool, - broker, - user_id, - ¬ification_json.to_string(), - top_character_id, - )?; + Self::notify_user(pool, broker, user_id, ¬ification_json.to_string(), char_info.character_id)?; - // Sende Benachrichtigung über WebSocket - let mut notification = json!({ + // WebSocket-Benachrichtigung + let mut ws_notification = json!({ "event": "random_event", "event_id": event.id, "event_type": "personal", @@ -751,16 +852,11 @@ impl EventsWorker { "effects": effect_results }); - if let Some(cid) = top_character_id { - notification["character_id"] = json!(cid); + if let Some(cid) = char_info.character_id { + ws_notification["character_id"] = json!(cid); } - broker.publish(notification.to_string()); - eprintln!( - "[EventsWorker] Persönliches Ereignis '{}' für Spieler {} verarbeitet", - event.id, user_id - ); - + broker.publish(ws_notification.to_string()); Ok(()) }