Refactor marriage pregnancy logic in UserCharacterWorker and SQL: Updated the conception process to occur daily instead of hourly, simplifying the fertility mechanics. Enhanced documentation for clarity on the new daily conception approach and adjusted related SQL queries to reflect these changes. Introduced a mechanism to track the last fertility date to prevent multiple conceptions in a single day.
This commit is contained in:
@@ -1662,9 +1662,10 @@ pub const QUERY_AUTOBATISM: &str = r#"
|
|||||||
);
|
);
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
// Biologische Fruchtbarkeit nach Alter der Frau (Jahres-Wahrscheinlichkeit).
|
// Biologische Fruchtbarkeit nach Alter der Frau (Jahres-Wahrscheinlichkeit prob_year).
|
||||||
// Worker würfelt stündlich: P_stunde = 1 - (1 - P_jahr)^(1/8760), damit 24×/Tag zusammen
|
// Konzeption: genau ein Wurf pro Ehe und Kalendertag (`UserCharacterWorker`), mit random() < prob_year.
|
||||||
// dieselbe kumulative Jahresaufteilung wie früher 1×/Tag mit P_tag = 1 - (1 - P_jahr)^(1/365).
|
// Entspricht „ein Spieljahr pro Kalendertag“: keine 24× stündlichen Versuche mehr, keine „Hochzeitsnacht“
|
||||||
|
// als Extra-Event — erste mögliche Konzeption am nächsten täglichen Fertilitätslauf nach der Hochzeit.
|
||||||
// Grenzen in Tagen: 1 Jahr ≈ 365 Tage (mother_age_days).
|
// Grenzen in Tagen: 1 Jahr ≈ 365 Tage (mother_age_days).
|
||||||
// Erstes gemeinsames Kind (0 Zeilen in child_relation für dieses Paar): prob_year mindestens 1.0
|
// Erstes gemeinsames Kind (0 Zeilen in child_relation für dieses Paar): prob_year mindestens 1.0
|
||||||
// im Alter 18–~44 (6570–16000 Tage), damit kinderlose Ehen nicht über viele Jahre ohne Nachwuchs bleiben.
|
// im Alter 18–~44 (6570–16000 Tage), damit kinderlose Ehen nicht über viele Jahre ohne Nachwuchs bleiben.
|
||||||
@@ -1715,7 +1716,7 @@ pub const QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE: &str = r#"
|
|||||||
AND marriage_pregnancy_due_at < NOW() - INTERVAL '30 days';
|
AND marriage_pregnancy_due_at < NOW() - INTERVAL '30 days';
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// Stündlicher Konzeptionswurf (Ehe): bei Treffer wird `marriage_pregnancy_due_at` auf +5 Tage gesetzt.
|
/// Täglicher Konzeptionswurf (Ehe): bei Treffer wird `marriage_pregnancy_due_at` auf +5 Tage gesetzt.
|
||||||
pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
|
pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
|
||||||
WITH paired AS (
|
WITH paired AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -1811,7 +1812,7 @@ pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
|
|||||||
FROM mother_age_final ma
|
FROM mother_age_final ma
|
||||||
WHERE ma.mother_age_days >= 4380
|
WHERE ma.mother_age_days >= 4380
|
||||||
AND ma.mother_age_days < 18993
|
AND ma.mother_age_days < 18993
|
||||||
AND random() < (1 - POWER(1 - ma.prob_year, 1.0/8760.0))
|
AND random() < ma.prob_year
|
||||||
)
|
)
|
||||||
UPDATE falukant_data.relationship r
|
UPDATE falukant_data.relationship r
|
||||||
SET marriage_pregnancy_due_at = NOW() + INTERVAL '5 days'
|
SET marriage_pregnancy_due_at = NOW() + INTERVAL '5 days'
|
||||||
@@ -1829,7 +1830,7 @@ pub const QUERY_MARRIAGE_PREGNANCY_COLUMN_READY: &str = r#"
|
|||||||
) AS ready;
|
) AS ready;
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// Ehe: ohne Migration 008 — stündlicher Wurf legt sofort ein Kind an (wie früher).
|
/// Ehe: ohne Migration 008 — täglicher Wurf legt sofort ein Kind an (Daemon ruft 1×/Kalendertag auf).
|
||||||
pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
||||||
WITH paired AS (
|
WITH paired AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -1926,11 +1927,11 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
|||||||
father_uid,
|
father_uid,
|
||||||
mother_uid,
|
mother_uid,
|
||||||
mother_age_days,
|
mother_age_days,
|
||||||
(1 - POWER(1 - prob_year, 1.0/8760.0)) * 100 AS prob_pct
|
prob_year * 100.0 AS prob_pct
|
||||||
FROM mother_age_final
|
FROM mother_age_final
|
||||||
WHERE mother_age_days >= 4380
|
WHERE mother_age_days >= 4380
|
||||||
AND mother_age_days < 18993
|
AND mother_age_days < 18993
|
||||||
AND random() < (1 - POWER(1 - prob_year, 1.0/8760.0));
|
AND random() < prob_year;
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
pub const QUERY_INSERT_CHILD: &str = r#"
|
pub const QUERY_INSERT_CHILD: &str = r#"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::db::{ConnectionPool, DbError, Rows};
|
use crate::db::{ConnectionPool, DbError, Rows};
|
||||||
use crate::message_broker::MessageBroker;
|
use crate::message_broker::MessageBroker;
|
||||||
|
use chrono::{Local, NaiveDate};
|
||||||
use rand::distributions::{Distribution, Uniform};
|
use rand::distributions::{Distribution, Uniform};
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
@@ -64,6 +65,8 @@ pub struct UserCharacterWorker {
|
|||||||
dist: Uniform<f64>,
|
dist: Uniform<f64>,
|
||||||
last_hourly_run: Option<Instant>,
|
last_hourly_run: Option<Instant>,
|
||||||
last_pregnancy_run: Option<Instant>,
|
last_pregnancy_run: Option<Instant>,
|
||||||
|
/// Letzter Kalendertag, an dem Ehe-Konzeption (oder Legacy-Instant) gelaufen ist — höchstens 1×/Tag.
|
||||||
|
last_marriage_fertility_date: Option<NaiveDate>,
|
||||||
/// `None` = noch nicht geprüft, ob Migration 008 (`marriage_pregnancy_due_at`) existiert.
|
/// `None` = noch nicht geprüft, ob Migration 008 (`marriage_pregnancy_due_at`) existiert.
|
||||||
marriage_pregnancy_column_ready: Option<bool>,
|
marriage_pregnancy_column_ready: Option<bool>,
|
||||||
last_mood_run: Option<Instant>,
|
last_mood_run: Option<Instant>,
|
||||||
@@ -84,6 +87,7 @@ impl UserCharacterWorker {
|
|||||||
dist,
|
dist,
|
||||||
last_hourly_run: None,
|
last_hourly_run: None,
|
||||||
last_pregnancy_run: None,
|
last_pregnancy_run: None,
|
||||||
|
last_marriage_fertility_date: None,
|
||||||
marriage_pregnancy_column_ready: None,
|
marriage_pregnancy_column_ready: None,
|
||||||
last_mood_run: None,
|
last_mood_run: None,
|
||||||
last_death_check_run: None,
|
last_death_check_run: None,
|
||||||
@@ -179,26 +183,35 @@ impl UserCharacterWorker {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ehe-Schwangerschaft: höchstens einmal pro Stunde (Daemon-Schleife ~1 s).
|
/// Ehe: Geburten/stale stündlich; Konzeption höchstens einmal pro Kalendertag (ein Jahreswurf, `prob_year`).
|
||||||
/// Konzeption setzt `marriage_pregnancy_due_at` (+5 Tage); Geburt über `QUERY_GET_MARRIAGE_BIRTH_DELIVERIES`.
|
/// Konzeption setzt `marriage_pregnancy_due_at` (+5 Tage); Geburt über `QUERY_GET_MARRIAGE_BIRTH_DELIVERIES`.
|
||||||
/// Stündliche Zerlegung der Jahres-Wahrscheinlichkeit (`1/8760`), siehe `QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE`.
|
/// Kein separates „Hochzeitsnacht“-Event — erste Konzeption am nächsten Tageslauf nach der Hochzeit.
|
||||||
/// Erstes gemeinsames Kind: Jahreswahrscheinlichkeit mindestens 1.0 (18–~44 J.), siehe `sql.rs`.
|
|
||||||
fn maybe_run_hourly_pregnancies(&mut self) {
|
fn maybe_run_hourly_pregnancies(&mut self) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let should_run = match self.last_pregnancy_run {
|
let today = Local::now().date_naive();
|
||||||
|
|
||||||
|
let run_hourly = match self.last_pregnancy_run {
|
||||||
None => true,
|
None => true,
|
||||||
Some(last) => now.saturating_duration_since(last) >= Duration::from_secs(3600),
|
Some(last) => now.saturating_duration_since(last) >= Duration::from_secs(3600),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !should_run {
|
let run_daily_fertility = self.last_marriage_fertility_date != Some(today);
|
||||||
|
|
||||||
|
if !run_hourly && !run_daily_fertility {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = self.process_pregnancies() {
|
if let Err(err) = self.process_pregnancies(run_hourly, run_daily_fertility) {
|
||||||
eprintln!("[UserCharacterWorker] Fehler in processPregnancies: {err}");
|
eprintln!("[UserCharacterWorker] Fehler in processPregnancies: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if run_hourly {
|
||||||
self.last_pregnancy_run = Some(now);
|
self.last_pregnancy_run = Some(now);
|
||||||
}
|
}
|
||||||
|
if run_daily_fertility {
|
||||||
|
self.last_marriage_fertility_date = Some(today);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_character_events(&mut self) -> Result<(), DbError> {
|
fn process_character_events(&mut self) -> Result<(), DbError> {
|
||||||
self.base.set_current_step("Get character data");
|
self.base.set_current_step("Get character data");
|
||||||
@@ -536,32 +549,24 @@ impl UserCharacterWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schwangerschafts-Logik (portiert aus processPregnancies)
|
// Schwangerschafts-Logik (portiert aus processPregnancies)
|
||||||
fn process_pregnancies(&mut self) -> Result<(), DbError> {
|
fn process_pregnancies(&mut self, hourly: bool, daily_fertility: bool) -> Result<(), DbError> {
|
||||||
|
self.ensure_marriage_pregnancy_schema()?;
|
||||||
|
|
||||||
let mut conn = self
|
let mut conn = self
|
||||||
.base
|
.base
|
||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||||
|
|
||||||
conn.prepare("autobatism", QUERY_AUTOBATISM)?;
|
|
||||||
conn.execute("autobatism", &[])?;
|
|
||||||
|
|
||||||
if self.marriage_pregnancy_column_ready.is_none() {
|
|
||||||
conn.prepare("mp_ready", QUERY_MARRIAGE_PREGNANCY_COLUMN_READY)?;
|
|
||||||
let ready_rows = conn.execute("mp_ready", &[])?;
|
|
||||||
self.marriage_pregnancy_column_ready = Some(
|
|
||||||
ready_rows
|
|
||||||
.first()
|
|
||||||
.and_then(|r| r.get("ready"))
|
|
||||||
.map(|v| v == "true" || v == "t")
|
|
||||||
.unwrap_or(false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let use_gestation = self.marriage_pregnancy_column_ready.unwrap_or(false);
|
|
||||||
|
|
||||||
conn.prepare("insert_child", QUERY_INSERT_CHILD)?;
|
conn.prepare("insert_child", QUERY_INSERT_CHILD)?;
|
||||||
conn.prepare("insert_child_relation", QUERY_INSERT_CHILD_RELATION)?;
|
conn.prepare("insert_child_relation", QUERY_INSERT_CHILD_RELATION)?;
|
||||||
|
|
||||||
|
let use_gestation = self.marriage_pregnancy_column_ready.unwrap_or(false);
|
||||||
|
|
||||||
|
if hourly {
|
||||||
|
conn.prepare("autobatism", QUERY_AUTOBATISM)?;
|
||||||
|
conn.execute("autobatism", &[])?;
|
||||||
|
|
||||||
if use_gestation {
|
if use_gestation {
|
||||||
conn.prepare("get_deliveries", QUERY_GET_MARRIAGE_BIRTH_DELIVERIES)?;
|
conn.prepare("get_deliveries", QUERY_GET_MARRIAGE_BIRTH_DELIVERIES)?;
|
||||||
conn.prepare("clear_preg", QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE)?;
|
conn.prepare("clear_preg", QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE)?;
|
||||||
@@ -573,7 +578,11 @@ impl UserCharacterWorker {
|
|||||||
|
|
||||||
conn.prepare("clear_stale_preg", QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE)?;
|
conn.prepare("clear_stale_preg", QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE)?;
|
||||||
conn.execute("clear_stale_preg", &[])?;
|
conn.execute("clear_stale_preg", &[])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if daily_fertility {
|
||||||
|
if use_gestation {
|
||||||
conn.prepare("try_conception", QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE)?;
|
conn.prepare("try_conception", QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE)?;
|
||||||
conn.execute("try_conception", &[])?;
|
conn.execute("try_conception", &[])?;
|
||||||
} else {
|
} else {
|
||||||
@@ -583,10 +592,32 @@ impl UserCharacterWorker {
|
|||||||
self.process_single_marriage_delivery(&mut conn, &row)?;
|
self.process_single_marriage_delivery(&mut conn, &row)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_marriage_pregnancy_schema(&mut self) -> Result<(), DbError> {
|
||||||
|
if self.marriage_pregnancy_column_ready.is_some() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut conn = self
|
||||||
|
.base
|
||||||
|
.pool
|
||||||
|
.get()
|
||||||
|
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||||
|
conn.prepare("mp_ready", QUERY_MARRIAGE_PREGNANCY_COLUMN_READY)?;
|
||||||
|
let ready_rows = conn.execute("mp_ready", &[])?;
|
||||||
|
self.marriage_pregnancy_column_ready = Some(
|
||||||
|
ready_rows
|
||||||
|
.first()
|
||||||
|
.and_then(|r| r.get("ready"))
|
||||||
|
.map(|v| v == "true" || v == "t")
|
||||||
|
.unwrap_or(false),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn process_single_marriage_delivery(
|
fn process_single_marriage_delivery(
|
||||||
&mut self,
|
&mut self,
|
||||||
conn: &mut crate::db::DbConnection,
|
conn: &mut crate::db::DbConnection,
|
||||||
|
|||||||
Reference in New Issue
Block a user