Implement planned pregnancy management in UserCharacterWorker and SQL: Added new queries for handling planned births and clearing character pregnancy records after birth. Refactored pregnancy processing logic to incorporate checks for planned pregnancy readiness and streamlined the insertion of child relations based on birth context. Enhanced documentation for clarity on the new planned pregnancy features and their integration into the existing system.
This commit is contained in:
1
migrations/010_falukant_marriage_started_at.sql
Normal file
1
migrations/010_falukant_marriage_started_at.sql
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
16
migrations/011_falukant_character_planned_pregnancy.sql
Normal file
16
migrations/011_falukant_character_planned_pregnancy.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Geplante Schwangerschaft auf dem Charakter (Weg A; vgl. backend/sql/add_character_pregnancy.sql).
|
||||
-- Kann bereits existieren — nur ergänzen, was fehlt.
|
||||
|
||||
ALTER TABLE falukant_data.character
|
||||
ADD COLUMN IF NOT EXISTS pregnancy_due_at timestamptz NULL,
|
||||
ADD COLUMN IF NOT EXISTS pregnancy_father_character_id integer NULL
|
||||
REFERENCES falukant_data.character (id) ON DELETE SET NULL;
|
||||
|
||||
COMMENT ON COLUMN falukant_data.character.pregnancy_due_at IS
|
||||
'Erwarteter Geburtstermin (Admin/Spiel); Daemon liefert Geburt wenn fällig.';
|
||||
COMMENT ON COLUMN falukant_data.character.pregnancy_father_character_id IS
|
||||
'Vater-Charakter für geplante Geburt; NULL = Daemon überspringt bis Policy geklärt.';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_character_pregnancy_due
|
||||
ON falukant_data.character (pregnancy_due_at)
|
||||
WHERE pregnancy_due_at IS NOT NULL;
|
||||
@@ -1675,6 +1675,7 @@ pub const QUERY_AUTOBATISM: &str = r#"
|
||||
// Migration: `008_falukant_marriage_pregnancy_due.sql`.
|
||||
|
||||
/// Fällige Geburten (Ehe): `marriage_pregnancy_due_at <= NOW()`.
|
||||
/// B2: keine Ehe-Geburt, solange die Mutter eine **geplante** Schwangerschaft (`character.pregnancy_due_at`) hat.
|
||||
pub const QUERY_GET_MARRIAGE_BIRTH_DELIVERIES: &str = r#"
|
||||
SELECT
|
||||
r.id AS relationship_id,
|
||||
@@ -1699,7 +1700,8 @@ pub const QUERY_GET_MARRIAGE_BIRTH_DELIVERIES: &str = r#"
|
||||
WHERE r.marriage_pregnancy_due_at IS NOT NULL
|
||||
AND r.marriage_pregnancy_due_at <= NOW()
|
||||
AND ((c1.gender = 'male' AND c2.gender = 'female')
|
||||
OR (c1.gender = 'female' AND c2.gender = 'male'));
|
||||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||||
AND c_female.pregnancy_due_at IS NULL;
|
||||
"#;
|
||||
|
||||
pub const QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE: &str = r#"
|
||||
@@ -1748,6 +1750,7 @@ pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
|
||||
WHERE r.marriage_pregnancy_due_at IS NULL
|
||||
AND ((c1.gender = 'male' AND c2.gender = 'female')
|
||||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||||
AND c_female.pregnancy_due_at IS NULL
|
||||
),
|
||||
mother_age AS (
|
||||
SELECT
|
||||
@@ -1858,8 +1861,9 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
||||
)
|
||||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||||
WHERE (c1.gender = 'male' AND c2.gender = 'female')
|
||||
OR (c1.gender = 'female' AND c2.gender = 'male')
|
||||
WHERE ((c1.gender = 'male' AND c2.gender = 'female')
|
||||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||||
AND c_female.pregnancy_due_at IS NULL
|
||||
),
|
||||
mother_age AS (
|
||||
SELECT
|
||||
@@ -1934,6 +1938,83 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
||||
AND random() < prob_year;
|
||||
"#;
|
||||
|
||||
/// Spalten `pregnancy_due_at` / `pregnancy_father_character_id` auf `character` (Migration `011_…`).
|
||||
pub const QUERY_CHARACTER_PLANNED_PREGNANCY_COLUMNS_READY: &str = r#"
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data'
|
||||
AND table_name = 'character'
|
||||
AND column_name = 'pregnancy_due_at'
|
||||
) AS ready;
|
||||
"#;
|
||||
|
||||
/// Fällige **geplante** Geburten (Weg A): Mutter trägt Termin + Vater; kein Ehe-`relationship`-Bezug.
|
||||
/// Vater NULL oder = Mutter: keine Zeile (Daemon überspringt; optional im Log).
|
||||
pub const QUERY_GET_PLANNED_CHARACTER_BIRTH_DELIVERIES: &str = r#"
|
||||
SELECT
|
||||
c_m.id AS mother_cid,
|
||||
c_f.id AS father_cid,
|
||||
CASE WHEN c_f.gender = 'male' THEN c_f.title_of_nobility ELSE c_m.title_of_nobility END AS title_of_nobility,
|
||||
CASE WHEN c_f.gender = 'male' THEN c_f.last_name ELSE c_m.last_name END AS last_name,
|
||||
CASE WHEN c_f.gender = 'male' THEN c_f.region_id ELSE c_m.region_id END AS region_id,
|
||||
fu_f.id AS father_uid,
|
||||
fu_m.id AS mother_uid,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||||
WHERE (r.character1_id = c_f.id AND r.character2_id = c_m.id)
|
||||
OR (r.character1_id = c_m.id AND r.character2_id = c_f.id)
|
||||
) THEN 'lover'
|
||||
ELSE 'marriage'
|
||||
END AS birth_context
|
||||
FROM falukant_data.character c_m
|
||||
JOIN falukant_data.character c_f ON c_f.id = c_m.pregnancy_father_character_id
|
||||
LEFT JOIN falukant_data.falukant_user fu_m ON fu_m.id = c_m.user_id
|
||||
LEFT JOIN falukant_data.falukant_user fu_f ON fu_f.id = c_f.user_id
|
||||
WHERE c_m.pregnancy_due_at IS NOT NULL
|
||||
AND c_m.pregnancy_due_at <= NOW()
|
||||
AND c_m.pregnancy_father_character_id IS NOT NULL
|
||||
AND c_m.pregnancy_father_character_id <> c_m.id
|
||||
AND c_m.health > 0;
|
||||
"#;
|
||||
|
||||
pub const QUERY_CLEAR_CHARACTER_PREGNANCY_AFTER_BIRTH: &str = r#"
|
||||
UPDATE falukant_data.character
|
||||
SET pregnancy_due_at = NULL,
|
||||
pregnancy_father_character_id = NULL,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1::int;
|
||||
"#;
|
||||
|
||||
/// Wie Node-Admin: `birth_context` / `legitimacy` / `public_known` je nach Liebschaft vs. Ehe.
|
||||
pub const QUERY_INSERT_CHILD_RELATION_PLANNED_BIRTH: &str = r#"
|
||||
INSERT INTO falukant_data.child_relation (
|
||||
father_character_id,
|
||||
mother_character_id,
|
||||
child_character_id,
|
||||
name_set,
|
||||
legitimacy,
|
||||
birth_context,
|
||||
public_known,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1::int,
|
||||
$2::int,
|
||||
$3::int,
|
||||
FALSE,
|
||||
CASE WHEN $4::text = 'lover' THEN 'hidden_bastard'::varchar ELSE 'legitimate'::varchar END,
|
||||
CASE WHEN $4::text = 'lover' THEN 'lover'::varchar ELSE 'marriage'::varchar END,
|
||||
CASE WHEN $4::text = 'lover' THEN FALSE ELSE TRUE END,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
"#;
|
||||
|
||||
pub const QUERY_INSERT_CHILD: &str = r#"
|
||||
INSERT INTO falukant_data.character (
|
||||
user_id,
|
||||
|
||||
@@ -38,8 +38,12 @@ use crate::worker::sql::{
|
||||
QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES,
|
||||
QUERY_GET_MARRIAGE_BIRTH_DELIVERIES, QUERY_MARRIAGE_PREGNANCY_COLUMN_READY,
|
||||
QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE,
|
||||
QUERY_CHARACTER_PLANNED_PREGNANCY_COLUMNS_READY,
|
||||
QUERY_CLEAR_CHARACTER_PREGNANCY_AFTER_BIRTH,
|
||||
QUERY_GET_PLANNED_CHARACTER_BIRTH_DELIVERIES,
|
||||
QUERY_INSERT_CHILD,
|
||||
QUERY_INSERT_CHILD_RELATION,
|
||||
QUERY_INSERT_CHILD_RELATION_PLANNED_BIRTH,
|
||||
QUERY_INSERT_NOTIFICATION,
|
||||
QUERY_DELETE_DIRECTOR,
|
||||
QUERY_DELETE_RELATIONSHIP,
|
||||
@@ -69,6 +73,8 @@ pub struct UserCharacterWorker {
|
||||
last_marriage_fertility_date: Option<NaiveDate>,
|
||||
/// `None` = noch nicht geprüft, ob Migration 008 (`marriage_pregnancy_due_at`) existiert.
|
||||
marriage_pregnancy_column_ready: Option<bool>,
|
||||
/// Migration `011` — `character.pregnancy_due_at` / geplante Geburt (Weg A).
|
||||
character_planned_pregnancy_columns_ready: Option<bool>,
|
||||
last_mood_run: Option<Instant>,
|
||||
last_death_check_run: Option<Instant>,
|
||||
}
|
||||
@@ -89,6 +95,7 @@ impl UserCharacterWorker {
|
||||
last_pregnancy_run: None,
|
||||
last_marriage_fertility_date: None,
|
||||
marriage_pregnancy_column_ready: None,
|
||||
character_planned_pregnancy_columns_ready: None,
|
||||
last_mood_run: None,
|
||||
last_death_check_run: None,
|
||||
}
|
||||
@@ -550,7 +557,7 @@ impl UserCharacterWorker {
|
||||
|
||||
// Schwangerschafts-Logik (portiert aus processPregnancies)
|
||||
fn process_pregnancies(&mut self, hourly: bool, daily_fertility: bool) -> Result<(), DbError> {
|
||||
self.ensure_marriage_pregnancy_schema()?;
|
||||
self.ensure_falukant_pregnancy_schema()?;
|
||||
|
||||
let mut conn = self
|
||||
.base
|
||||
@@ -560,13 +567,26 @@ impl UserCharacterWorker {
|
||||
|
||||
conn.prepare("insert_child", QUERY_INSERT_CHILD)?;
|
||||
conn.prepare("insert_child_relation", QUERY_INSERT_CHILD_RELATION)?;
|
||||
conn.prepare("insert_child_rel_planned", QUERY_INSERT_CHILD_RELATION_PLANNED_BIRTH)?;
|
||||
conn.prepare("clear_char_preg", QUERY_CLEAR_CHARACTER_PREGNANCY_AFTER_BIRTH)?;
|
||||
|
||||
let use_gestation = self.marriage_pregnancy_column_ready.unwrap_or(false);
|
||||
let planned_pregnancy_ready = self.character_planned_pregnancy_columns_ready.unwrap_or(false);
|
||||
|
||||
if hourly {
|
||||
conn.prepare("autobatism", QUERY_AUTOBATISM)?;
|
||||
conn.execute("autobatism", &[])?;
|
||||
|
||||
if planned_pregnancy_ready {
|
||||
conn.prepare("get_planned_births", QUERY_GET_PLANNED_CHARACTER_BIRTH_DELIVERIES)?;
|
||||
let planned_rows = conn.execute("get_planned_births", &[])?;
|
||||
for row in planned_rows {
|
||||
if let Err(e) = self.process_single_planned_character_birth(&mut conn, &row) {
|
||||
eprintln!("[UserCharacterWorker] geplante Geburt: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if use_gestation {
|
||||
conn.prepare("get_deliveries", QUERY_GET_MARRIAGE_BIRTH_DELIVERIES)?;
|
||||
conn.prepare("clear_preg", QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE)?;
|
||||
@@ -597,8 +617,10 @@ impl UserCharacterWorker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_marriage_pregnancy_schema(&mut self) -> Result<(), DbError> {
|
||||
if self.marriage_pregnancy_column_ready.is_some() {
|
||||
fn ensure_falukant_pregnancy_schema(&mut self) -> Result<(), DbError> {
|
||||
if self.marriage_pregnancy_column_ready.is_some()
|
||||
&& self.character_planned_pregnancy_columns_ready.is_some()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let mut conn = self
|
||||
@@ -606,6 +628,8 @@ impl UserCharacterWorker {
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
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(
|
||||
@@ -615,6 +639,78 @@ impl UserCharacterWorker {
|
||||
.map(|v| v == "true" || v == "t")
|
||||
.unwrap_or(false),
|
||||
);
|
||||
}
|
||||
|
||||
if self.character_planned_pregnancy_columns_ready.is_none() {
|
||||
conn.prepare("cpp_ready", QUERY_CHARACTER_PLANNED_PREGNANCY_COLUMNS_READY)?;
|
||||
let ready_rows = conn.execute("cpp_ready", &[])?;
|
||||
self.character_planned_pregnancy_columns_ready = Some(
|
||||
ready_rows
|
||||
.first()
|
||||
.and_then(|r| r.get("ready"))
|
||||
.map(|v| v == "true" || v == "t")
|
||||
.unwrap_or(false),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Weg A: `character.pregnancy_due_at` / `pregnancy_father_character_id` (Admin/Spiel).
|
||||
fn process_single_planned_character_birth(
|
||||
&mut self,
|
||||
conn: &mut crate::db::DbConnection,
|
||||
row: &crate::db::Row,
|
||||
) -> Result<(), DbError> {
|
||||
let mother_cid = parse_i32(row, "mother_cid", -1);
|
||||
let father_cid = parse_i32(row, "father_cid", -1);
|
||||
if mother_cid < 0 || father_cid < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let title_of_nobility = parse_i32(row, "title_of_nobility", 0);
|
||||
let last_name = parse_i32(row, "last_name", 0);
|
||||
let region_id = parse_i32(row, "region_id", 0);
|
||||
|
||||
let father_uid = parse_opt_i32(row, "father_uid");
|
||||
let mother_uid = parse_opt_i32(row, "mother_uid");
|
||||
|
||||
let birth_context = row
|
||||
.get("birth_context")
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or("marriage");
|
||||
|
||||
let gender = if self.dist.sample(&mut self.rng) < 0.5 {
|
||||
"male"
|
||||
} else {
|
||||
"female"
|
||||
};
|
||||
|
||||
let inserted =
|
||||
conn.execute("insert_child", &[®ion_id, &gender, &last_name, &title_of_nobility])?;
|
||||
let child_cid = inserted
|
||||
.first()
|
||||
.and_then(|r| r.get("child_cid"))
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(-1);
|
||||
if child_cid < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
"insert_child_rel_planned",
|
||||
&[&father_cid, &mother_cid, &child_cid, &birth_context],
|
||||
)?;
|
||||
conn.execute("clear_char_preg", &[&mother_cid])?;
|
||||
|
||||
if let Some(f_uid) = father_uid {
|
||||
self.send_children_update_and_status(f_uid);
|
||||
}
|
||||
if let Some(m_uid) = mother_uid {
|
||||
self.send_children_update_and_status(m_uid);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user