Refactor personal event handling in EventsWorker: Introduce a new CharacterInfo struct to encapsulate character details extracted from event effects. Streamline the application of personal effects by consolidating logic into a single method, improving clarity and maintainability. Enhance notification creation by directly incorporating character information, ensuring accurate event communication.

This commit is contained in:
Torsten Schulz (local)
2026-01-15 13:33:39 +01:00
parent 63a89588ac
commit fc1b0c2259

View File

@@ -101,6 +101,13 @@ pub enum EventEffect {
HouseQualityChange { probability: f64, min_change: i32, max_change: i32 }, HouseQualityChange { probability: f64, min_change: i32, max_change: i32 },
} }
/// Hilfsstruktur für Character-Informationen aus Effekten
struct CharacterInfo {
character_id: Option<i32>,
first_name: Option<String>,
last_name: Option<String>,
}
/// Definition eines zufälligen Ereignisses /// Definition eines zufälligen Ereignisses
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RandomEvent { pub struct RandomEvent {
@@ -530,24 +537,11 @@ impl EventsWorker {
event: &RandomEvent, event: &RandomEvent,
rng: &mut impl Rng, rng: &mut impl Rng,
) -> Result<(), DbError> { ) -> 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" { if event.id == "sudden_infant_death" {
return Self::trigger_sudden_infant_death(pool, broker, event, rng); return Self::trigger_sudden_infant_death(pool, broker, event, rng);
} }
conn.prepare("get_random_user", QUERY_GET_RANDOM_USER)?; let user_id = match Self::get_random_user_id(pool)? {
let rows = conn.execute("get_random_user", &[])?;
let user_id: Option<i32> = rows
.first()
.and_then(|r| r.get("id"))
.and_then(|v| v.parse::<i32>().ok());
let user_id = match user_id {
Some(id) => id, Some(id) => id,
None => { None => {
eprintln!("[EventsWorker] Kein Spieler gefunden für persönliches Ereignis"); 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<Option<i32>, 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::<i32>().ok()))
}
fn apply_personal_effects(
conn: &mut DbConnection,
pool: &ConnectionPool,
broker: &MessageBroker,
user_id: i32,
event: &RandomEvent,
rng: &mut impl Rng,
) -> Result<Vec<serde_json::Value>, DbError> {
let mut effect_results = Vec::new(); let mut effect_results = Vec::new();
for effect in &event.effects { for effect in &event.effects {
let effect_roll = rng.gen_range(0.0..=1.0); let effect_roll = rng.gen_range(0.0..=1.0);
match effect { if let Some(result) = Self::apply_single_personal_effect(conn, pool, broker, user_id, effect, effect_roll, rng)? {
EventEffect::MoneyChange { effect_results.extend(result);
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
);
}
} }
} }
// Schreibe Benachrichtigung in die Datenbank mit Event-Details Ok(effect_results)
// 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| {
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<Option<Vec<serde_json::Value>>, 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<Option<Vec<serde_json::Value>>, 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<Option<Vec<serde_json::Value>>, 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<Option<Vec<serde_json::Value>>, 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<Option<Vec<serde_json::Value>>, 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<Option<Vec<serde_json::Value>>, 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) 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 (first_name, last_name) = if let Some(cid) = character_id {
let (top_first_name, top_last_name) = if let Some(cid) = top_character_id {
let eff = effect_results.iter().find(|e| { let eff = effect_results.iter().find(|e| {
e.get("character_id") e.get("character_id")
.and_then(|v| v.as_i64()) .and_then(|v| v.as_i64())
@@ -704,16 +810,18 @@ impl EventsWorker {
(None, None) (None, None)
}; };
// Keine Benachrichtigung erstellen, wenn kein Effekt angewendet wurde CharacterInfo { character_id, first_name, last_name }
if effect_results.is_empty() { }
eprintln!(
"[EventsWorker] Persönliches Ereignis '{}' für Spieler {} übersprungen (keine Effekte)",
event.id, user_id
);
return Ok(());
}
// 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!({ let mut notification_json = serde_json::json!({
"tr": format!("random_event.{}", event.id), "tr": format!("random_event.{}", event.id),
"event_id": event.id, "event_id": event.id,
@@ -721,27 +829,20 @@ impl EventsWorker {
"effects": effect_results "effects": effect_results
}); });
// Character-Informationen (falls vorhanden) if let Some(cid) = char_info.character_id {
if let Some(cid) = top_character_id {
notification_json["character_id"] = serde_json::json!(cid); 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_); 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_); notification_json["character_last_name"] = serde_json::json!(ln_);
} }
Self::notify_user( Self::notify_user(pool, broker, user_id, &notification_json.to_string(), char_info.character_id)?;
pool,
broker,
user_id,
&notification_json.to_string(),
top_character_id,
)?;
// Sende Benachrichtigung über WebSocket // WebSocket-Benachrichtigung
let mut notification = json!({ let mut ws_notification = json!({
"event": "random_event", "event": "random_event",
"event_id": event.id, "event_id": event.id,
"event_type": "personal", "event_type": "personal",
@@ -751,16 +852,11 @@ impl EventsWorker {
"effects": effect_results "effects": effect_results
}); });
if let Some(cid) = top_character_id { if let Some(cid) = char_info.character_id {
notification["character_id"] = json!(cid); ws_notification["character_id"] = json!(cid);
} }
broker.publish(notification.to_string()); broker.publish(ws_notification.to_string());
eprintln!(
"[EventsWorker] Persönliches Ereignis '{}' für Spieler {} verarbeitet",
event.id, user_id
);
Ok(()) Ok(())
} }