From 2cca0da750df460899636ed484a7bc049310daa9 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 13 Apr 2026 12:21:35 +0200 Subject: [PATCH] Update version and edition in Cargo.toml; enhance breakup risk management in FalukantFamilyWorker: Added logic to automatically deactivate low-affection relationships and notify users of high breakup risks. Updated SQL queries to support these changes. --- Cargo.toml | 4 +-- src/worker/falukant_family.rs | 55 +++++++++++++++++++++++++++++++++++ src/worker/sql.rs | 14 +++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee7cc3a..c53ffee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "YpDaemon" -version = "0.1.0" -edition = "2024" +version = "0.5.0" +edition = "2026" [dependencies] rand = "0.8" diff --git a/src/worker/falukant_family.rs b/src/worker/falukant_family.rs index 3632e3a..2be6868 100644 --- a/src/worker/falukant_family.rs +++ b/src/worker/falukant_family.rs @@ -18,6 +18,7 @@ use rand::SeedableRng; use super::base::{BaseWorker, Worker, WorkerState}; use super::sql::{ + QUERY_DEACTIVATE_LOVER_RELATIONSHIP, QUERY_COUNT_LOVER_CHILDREN_FOR_USER, QUERY_FAMILY_SCHEMA_READY, QUERY_GET_ACTIVE_LOVER_ROWS_FOR_DAILY, QUERY_GET_ACTIVE_LOVER_ROWS_FOR_MONTHLY, QUERY_GET_ACTIVE_LOVER_ROWS_FOR_INSTALLMENT, QUERY_GET_LOVER_PREGNANCY_CANDIDATES, @@ -32,6 +33,7 @@ use super::sql::{ }; use crate::db::{ConnectionPool, DbError}; use crate::message_broker::MessageBroker; +use serde_json::json; const DAILY_INTERVAL: Duration = Duration::from_secs(24 * 3600); /// Wie `DAILY_INTERVAL`: 1 Spieljahr = 1 Kalendertag — Monats-Stempel/„monthly“-Batch pro Spieltag, nicht alle 30 echten Tage. @@ -258,6 +260,10 @@ impl FalukantFamilyWorker { } conn.prepare("upd_vd", QUERY_UPDATE_LOVER_VISIBILITY_DISCRETION)?; + conn.prepare("deactivate_lover", QUERY_DEACTIVATE_LOVER_RELATIONSHIP)?; + let mut ended_rel_ids: HashSet = HashSet::new(); + let mut breakup_socket_users: HashSet = HashSet::new(); + let mut breakup_risk_notified: HashSet<(i32, i32)> = HashSet::new(); for l in &mut lovers { let ehe_conflict = marriages.iter().any(|m| { (m.m1 == l.c1 || m.m2 == l.c1 || m.m1 == l.c2 || m.m2 == l.c2) && m.satisfaction < 40 @@ -304,7 +310,42 @@ impl FalukantFamilyWorker { )?; l.visibility = new_vis; l.discretion = new_disc; + + // Je niedriger die Zuneigung, desto höher das tägliche Trennungsrisiko. + let breakup_risk_pct = breakup_risk_percent(l); + if breakup_risk_pct > 50.0 { + for uid in [l.user1_id, l.user2_id].into_iter().flatten() { + if uid <= 0 || !breakup_risk_notified.insert((l.rel_id, uid)) { + continue; + } + let payload = json!({ + "tr": "random_event.lover_breakup_risk_high", + "event_id": "lover_breakup_risk_high", + "event_type": "family", + "relationship_id": l.rel_id, + "risk_percent": breakup_risk_pct, + "affection": l.affection, + "visibility": l.visibility, + "discretion": l.discretion, + "months_underfunded": l.months_underfunded + }) + .to_string(); + if let Err(e) = super::notify::insert_notification_conn(&mut conn, uid, &payload, None) { + eprintln!( + "[FalukantFamilyWorker] high breakup risk notification failed rel_id={} uid={}: {}", + l.rel_id, uid, e + ); + } + } + } + if breakup_risk_pct > 0.0 && self.dist.sample(&mut self.rng) * 100.0 < breakup_risk_pct { + conn.execute("deactivate_lover", &[&l.rel_id])?; + ended_rel_ids.insert(l.rel_id); + push_user_id(&mut breakup_socket_users, l.user1_id); + push_user_id(&mut breakup_socket_users, l.user2_id); + } } + lovers.retain(|l| !ended_rel_ids.contains(&l.rel_id)); conn.prepare("mark_daily", QUERY_MARK_LOVER_DAILY_DONE)?; conn.prepare("mark_monthly", QUERY_MARK_LOVER_MONTHLY_DONE)?; @@ -673,6 +714,7 @@ impl FalukantFamilyWorker { } notify.extend(marriage_socket_users); notify.extend(tension_socket_users); + notify.extend(breakup_socket_users); self.publish_falukant_update_family_batch(¬ify, "daily"); drop(conn); @@ -1279,6 +1321,19 @@ fn visibility_young_penalty(min_age_years: i32, visibility: i32) -> f64 { } } +fn breakup_risk_percent(l: &LoverData) -> f64 { + if l.affection >= 45 { + return 0.0; + } + + let affection_risk = (45 - l.affection).max(0) as f64 * 0.5; + let underfund_risk = (l.months_underfunded.max(0) as f64 * 1.5).min(10.0); + let visibility_risk = if l.visibility >= 70 { 2.0 } else { 0.0 }; + let discretion_risk = if l.discretion <= 30 { 3.0 } else { 0.0 }; + + (affection_risk + underfund_risk + visibility_risk + discretion_risk).clamp(0.0, 30.0) +} + /// Spec Skandalrisiko: stufenweise Zusatz (exklusiv). fn scandal_age_extra_pct(min_age_years: i32) -> f64 { if min_age_years <= 13 { diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 1b6ca08..9c1c0b2 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -444,6 +444,11 @@ SELECT tpw.region_id, tpw.product_id, tpw.worth_percent FROM falukant_data.town_ "#; // Political offices and cumulative tax +// +// `falukant_type.political_office_type.term_length`: überall als **Kalendertage** (Integer), +// nie als Jahre. Ablauf: `po.created_at + term_length * INTERVAL '1 day'`. +// Steht in den Stammdaten z. B. `4` für „4 Jahre“, wären das faktisch **4 Tage** — dann +// Werte auf Tage umrechnen (z. B. 4 Jahre → 1460) oder die Spalte/Seed-Daten korrigieren. pub const QUERY_GET_USER_OFFICES: &str = r#" SELECT po.id AS office_id, pot.name AS office_name, po.region_id, rt.label_tr AS region_type FROM falukant_data.political_office po @@ -3574,6 +3579,15 @@ pub const QUERY_MARK_LOVER_MONTHLY_DONE: &str = r#" WHERE relationship_id = $1::int; "#; +/// Daily: automatisches Beenden einer Liebschaft bei anhaltend niedriger Zuneigung. +pub const QUERY_DEACTIVATE_LOVER_RELATIONSHIP: &str = r#" + UPDATE falukant_data.relationship_state + SET active = false, + updated_at = NOW() + WHERE relationship_id = $1::int + AND active = true; +"#; + /// Liebschaft: fällige Teilzahlung (alle 2 h), Migration `006_falukant_lover_installments.sql`. pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_INSTALLMENT: &str = r#" SELECT