Add FalukantFamilyWorker and related SQL queries: Introduced the FalukantFamilyWorker to manage family-related logic, including marriage satisfaction and relationship states. Added new SQL queries for handling lover relationships and marriage updates, enhancing the overall functionality of family dynamics in the application.
This commit is contained in:
23
docs/FALUKANT_DAEMON_HANDOFF.md
Normal file
23
docs/FALUKANT_DAEMON_HANDOFF.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Falukant: Daemon-Handoff (YpDaemon)
|
||||
|
||||
Technische Abstimmung mit dem Übergabedokument im Backend-Projekt (`FALUKANT_LOVERS_DAEMON_SPEC.md` / `FALUKANT_LOVERS_TECHNICAL_CONCEPT.md`).
|
||||
|
||||
## Abweichungen / Zuordnung
|
||||
|
||||
| Handoff / Backend | YpDaemon |
|
||||
|-------------------|----------|
|
||||
| `relationship_state.marriage_satisfaction` (Ehe) | **`relationship.marriage_satisfaction`** für Zeilen mit `relationship_type` ∈ `married`, `engaged`, `wooing` |
|
||||
| `months_underfunded` | Spalte `months_underfunded` (Migration 001; Legacy: `002` benennt `consecutive_underpayment_months` um) |
|
||||
| Idempotenz `last_daily_processed_at` / `last_monthly_processed_at` | Gesetzt von `FalukantFamilyWorker` pro Liebschaft |
|
||||
|
||||
## Ticks
|
||||
|
||||
- **Daily:** nur Zeilen mit `(last_daily_processed_at IS NULL OR last_daily_processed_at::date < CURRENT_DATE)`; danach `last_daily_processed_at = NOW()`.
|
||||
- **Monthly:** nur Zeilen mit `(last_monthly_processed_at IS NULL OR date_trunc('month', last_monthly_processed_at) < date_trunc('month', CURRENT_TIMESTAMP))`; nach Kosten/Unterversorgung `last_monthly_processed_at = NOW()`.
|
||||
|
||||
**Hinweis:** Der Worker nutzt weiterhin **Wandzeit** (24 h / 30 Tage) als Intervall; die Idempotenz über die Zeitstempel verhindert Doppelverarbeitung bei Neustarts am selben Tag/Monat.
|
||||
|
||||
## Migrationen
|
||||
|
||||
1. `migrations/001_falukant_family_lovers.sql`
|
||||
2. Optional: `migrations/002_falukant_family_rename_legacy_columns.sql` bei Altbestand
|
||||
62
migrations/001_falukant_family_lovers.sql
Normal file
62
migrations/001_falukant_family_lovers.sql
Normal file
@@ -0,0 +1,62 @@
|
||||
-- Falukant: Liebhaber, Ehezufriedenheit, uneheliche Kinder (Handoff: externer Daemon)
|
||||
-- Siehe docs/FALUKANT_DAEMON_HANDOFF.md
|
||||
|
||||
ALTER TABLE falukant_data.character
|
||||
ADD COLUMN IF NOT EXISTS reputation numeric(6,2) NOT NULL DEFAULT 50.00;
|
||||
|
||||
ALTER TABLE falukant_data.relationship
|
||||
ADD COLUMN IF NOT EXISTS marriage_satisfaction smallint NOT NULL DEFAULT 55
|
||||
CHECK (marriage_satisfaction >= 0 AND marriage_satisfaction <= 100),
|
||||
ADD COLUMN IF NOT EXISTS marriage_drift_high smallint NOT NULL DEFAULT 0
|
||||
CHECK (marriage_drift_high >= 0 AND marriage_drift_high < 3),
|
||||
ADD COLUMN IF NOT EXISTS marriage_drift_low smallint NOT NULL DEFAULT 0
|
||||
CHECK (marriage_drift_low >= 0 AND marriage_drift_low < 5);
|
||||
|
||||
COMMENT ON COLUMN falukant_data.relationship.marriage_satisfaction IS
|
||||
'Ehezufriedenheit 0..100 (married / engaged / wooing); Schreiben durch Daemon';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS falukant_data.relationship_state (
|
||||
relationship_id integer PRIMARY KEY
|
||||
REFERENCES falukant_data.relationship (id) ON DELETE CASCADE,
|
||||
lover_role varchar(32) NOT NULL
|
||||
CHECK (lover_role IN ('secret_affair', 'lover', 'mistress_or_favorite')),
|
||||
affection smallint NOT NULL DEFAULT 50
|
||||
CHECK (affection >= 0 AND affection <= 100),
|
||||
visibility smallint NOT NULL DEFAULT 20
|
||||
CHECK (visibility >= 0 AND visibility <= 100),
|
||||
discretion smallint NOT NULL DEFAULT 50
|
||||
CHECK (discretion >= 0 AND discretion <= 100),
|
||||
maintenance_level smallint NOT NULL DEFAULT 50
|
||||
CHECK (maintenance_level >= 0 AND maintenance_level <= 100),
|
||||
status_fit smallint NOT NULL DEFAULT 0
|
||||
CHECK (status_fit >= -2 AND status_fit <= 2),
|
||||
monthly_base_cost integer NOT NULL DEFAULT 30,
|
||||
active boolean NOT NULL DEFAULT true,
|
||||
acknowledged boolean NOT NULL DEFAULT false,
|
||||
exclusive boolean,
|
||||
months_underfunded smallint NOT NULL DEFAULT 0
|
||||
CHECK (months_underfunded >= 0 AND months_underfunded < 100),
|
||||
scandal_extra_daily_pct smallint NOT NULL DEFAULT 0
|
||||
CHECK (scandal_extra_daily_pct >= 0 AND scandal_extra_daily_pct <= 100),
|
||||
last_daily_processed_at timestamptz,
|
||||
last_monthly_processed_at timestamptz
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_relationship_state_active
|
||||
ON falukant_data.relationship_state (active)
|
||||
WHERE active = true;
|
||||
|
||||
COMMENT ON COLUMN falukant_data.relationship_state.last_daily_processed_at IS
|
||||
'Idempotenz: kein zweiter Daily-Tick am selben Kalendertag (Serverzeit)';
|
||||
COMMENT ON COLUMN falukant_data.relationship_state.last_monthly_processed_at IS
|
||||
'Idempotenz: kein zweiter Monthly-Tick im selben Kalendermonat (Serverzeit)';
|
||||
|
||||
ALTER TABLE falukant_data.child_relation
|
||||
ADD COLUMN IF NOT EXISTS legitimacy varchar(32) NOT NULL DEFAULT 'legitimate'
|
||||
CHECK (legitimacy IN ('legitimate', 'acknowledged_bastard', 'hidden_bastard')),
|
||||
ADD COLUMN IF NOT EXISTS birth_context varchar(32) NOT NULL DEFAULT 'marriage'
|
||||
CHECK (birth_context IN ('marriage', 'lover')),
|
||||
ADD COLUMN IF NOT EXISTS public_known boolean NOT NULL DEFAULT true;
|
||||
|
||||
COMMENT ON COLUMN falukant_data.child_relation.legitimacy IS 'legitimate | acknowledged_bastard | hidden_bastard';
|
||||
COMMENT ON COLUMN falukant_data.child_relation.birth_context IS 'marriage | lover';
|
||||
18
migrations/002_falukant_family_rename_legacy_columns.sql
Normal file
18
migrations/002_falukant_family_rename_legacy_columns.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- Upgrade falls bereits 001 mit alten Spaltennamen eingespielt wurde.
|
||||
-- Neuinstallationen nutzen 001 direkt und brauchen 002 nicht.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data' AND table_name = 'relationship_state'
|
||||
AND column_name = 'consecutive_underpayment_months'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data.relationship_state
|
||||
RENAME COLUMN consecutive_underpayment_months TO months_underfunded;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE falukant_data.relationship_state
|
||||
ADD COLUMN IF NOT EXISTS last_daily_processed_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS last_monthly_processed_at timestamptz;
|
||||
21
migrations/README.md
Normal file
21
migrations/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Datenbank-Migrationen (Falukant / YpDaemon)
|
||||
|
||||
Siehe auch **`docs/FALUKANT_DAEMON_HANDOFF.md`** (Abgleich mit Backend-Übergabe).
|
||||
|
||||
## `001_falukant_family_lovers.sql`
|
||||
|
||||
Voraussetzung für den **`FalukantFamilyWorker`** (Liebhaber, Ehezufriedenheit, Ansehen, Monatskosten, Kinder aus Liebschaften).
|
||||
|
||||
**Manuell ausführen** auf der Ziel-DB (nach Backup):
|
||||
|
||||
```bash
|
||||
psql "$DATABASE_URL" -f migrations/001_falukant_family_lovers.sql
|
||||
```
|
||||
|
||||
## `002_falukant_family_rename_legacy_columns.sql`
|
||||
|
||||
Nur nötig, wenn **`001`** bereits mit den **alten** Spaltennamen (`consecutive_underpayment_months`) eingespielt wurde.
|
||||
|
||||
**Backend (YourPart3):** Beim Anlegen einer `lover`-Beziehung `relationship_state` erzeugen; Ehezufriedenheit liegt auf **`relationship`** (married / engaged / wooing); Idempotenzfelder `last_daily_processed_at` / `last_monthly_processed_at` werden vom Daemon gesetzt.
|
||||
|
||||
Ohne passende Spalten (`last_daily_processed_at`) bleibt der Family-Worker inaktiv.
|
||||
@@ -16,6 +16,7 @@ use worker::{
|
||||
CharacterCreationWorker, ConnectionPool, DirectorWorker, EventsWorker, HouseWorker,
|
||||
PoliticsWorker, ProduceWorker, StockageManager, TransportWorker, UndergroundWorker,
|
||||
UserCharacterWorker, ValueRecalculationWorker, WeatherWorker, Worker,
|
||||
FalukantFamilyWorker,
|
||||
};
|
||||
|
||||
static KEEP_RUNNING: AtomicBool = AtomicBool::new(true);
|
||||
@@ -137,6 +138,10 @@ fn create_workers(pool: ConnectionPool, broker: MessageBroker) -> Vec<Box<dyn Wo
|
||||
pool.clone(),
|
||||
broker.clone(),
|
||||
)),
|
||||
Box::new(FalukantFamilyWorker::new(
|
||||
pool.clone(),
|
||||
broker.clone(),
|
||||
)),
|
||||
Box::new(HouseWorker::new(pool.clone(), broker.clone())),
|
||||
Box::new(PoliticsWorker::new(pool.clone(), broker.clone())),
|
||||
Box::new(TransportWorker::new(pool.clone(), broker.clone())),
|
||||
|
||||
766
src/worker/falukant_family.rs
Normal file
766
src/worker/falukant_family.rs
Normal file
@@ -0,0 +1,766 @@
|
||||
//! Liebhaber, Ehezufriedenheit, Ansehen, Monatskosten (Handoff: docs/FALUKANT_DAEMON_HANDOFF.md).
|
||||
//! Benötigt `migrations/001_falukant_family_lovers.sql` (ggf. `002` bei Altbestand).
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
|
||||
use super::base::{BaseWorker, Worker, WorkerState};
|
||||
use super::sql::{
|
||||
QUERY_FAMILY_SCHEMA_READY, QUERY_GET_ACTIVE_LOVER_ROWS_FOR_DAILY,
|
||||
QUERY_GET_ACTIVE_LOVER_ROWS_FOR_MONTHLY, QUERY_GET_LOVER_PREGNANCY_CANDIDATES,
|
||||
QUERY_GET_MARRIAGE_ROWS, QUERY_INSERT_CHILD, QUERY_INSERT_CHILD_RELATION_LOVER,
|
||||
QUERY_LOVER_BIRTH_PENALTY_MARRIAGE, QUERY_LOVER_BIRTH_PENALTY_REPUTATION,
|
||||
QUERY_MARK_LOVER_DAILY_DONE, QUERY_MARK_LOVER_MONTHLY_DONE,
|
||||
QUERY_MARRIAGE_SUBTRACT_SATISFACTION, QUERY_RESET_LOVER_UNDERPAY_COUNTERS,
|
||||
QUERY_UPDATE_CHARACTER_REPUTATION, QUERY_UPDATE_LOVER_UNDERPAY_STATE,
|
||||
QUERY_UPDATE_LOVER_VISIBILITY_DISCRETION, QUERY_UPDATE_MARRIAGE_STATE,
|
||||
};
|
||||
use crate::db::{ConnectionPool, DbError};
|
||||
use crate::message_broker::MessageBroker;
|
||||
|
||||
const DAILY_INTERVAL: Duration = Duration::from_secs(24 * 3600);
|
||||
const MONTHLY_INTERVAL: Duration = Duration::from_secs(30 * 24 * 3600);
|
||||
|
||||
pub struct FalukantFamilyWorker {
|
||||
base: BaseWorker,
|
||||
rng: StdRng,
|
||||
dist: Uniform<f64>,
|
||||
last_daily: Option<Instant>,
|
||||
last_monthly: Option<Instant>,
|
||||
schema_ready: bool,
|
||||
}
|
||||
|
||||
impl FalukantFamilyWorker {
|
||||
pub fn new(pool: ConnectionPool, broker: MessageBroker) -> Self {
|
||||
Self {
|
||||
base: BaseWorker::new("FalukantFamilyWorker", pool, broker),
|
||||
rng: StdRng::from_entropy(),
|
||||
dist: Uniform::from(0.0..1.0),
|
||||
last_daily: None,
|
||||
last_monthly: None,
|
||||
schema_ready: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_iteration(&mut self, state: &WorkerState) {
|
||||
self.base.set_current_step("FalukantFamilyWorker iteration");
|
||||
let now = Instant::now();
|
||||
|
||||
if !self.schema_ready {
|
||||
if let Ok(true) = self.check_schema() {
|
||||
self.schema_ready = true;
|
||||
} else {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if Self::should_run(self.last_daily, now, DAILY_INTERVAL) {
|
||||
if let Err(e) = self.process_daily() {
|
||||
eprintln!("[FalukantFamilyWorker] process_daily: {e}");
|
||||
}
|
||||
self.last_daily = Some(now);
|
||||
}
|
||||
|
||||
if Self::should_run(self.last_monthly, now, MONTHLY_INTERVAL) {
|
||||
if let Err(e) = self.process_monthly() {
|
||||
eprintln!("[FalukantFamilyWorker] process_monthly: {e}");
|
||||
}
|
||||
self.last_monthly = Some(now);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
if !state.running_worker.load(Ordering::Relaxed) {
|
||||
// stopping
|
||||
}
|
||||
}
|
||||
|
||||
fn should_run(last: Option<Instant>, now: Instant, interval: Duration) -> bool {
|
||||
match last {
|
||||
None => true,
|
||||
Some(t) => now.saturating_duration_since(t) >= interval,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_schema(&mut self) -> Result<bool, DbError> {
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("family_schema", QUERY_FAMILY_SCHEMA_READY)?;
|
||||
let rows = conn.execute("family_schema", &[])?;
|
||||
Ok(rows
|
||||
.first()
|
||||
.and_then(|r| r.get("ready"))
|
||||
.map(|v| v == "true" || v == "t")
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn process_daily(&mut self) -> Result<(), DbError> {
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("get_lovers", QUERY_GET_ACTIVE_LOVER_ROWS_FOR_DAILY)?;
|
||||
let lover_rows = conn.execute("get_lovers", &[])?;
|
||||
|
||||
conn.prepare("get_marriages", QUERY_GET_MARRIAGE_ROWS)?;
|
||||
let marriage_rows = conn.execute("get_marriages", &[])?;
|
||||
|
||||
let marriages: Vec<MarriageData> = marriage_rows
|
||||
.into_iter()
|
||||
.filter_map(|r| {
|
||||
Some(MarriageData {
|
||||
id: parse_i32(&r, "marriage_id", -1),
|
||||
m1: parse_i32(&r, "m1", -1),
|
||||
m2: parse_i32(&r, "m2", -1),
|
||||
satisfaction: parse_i32(&r, "marriage_satisfaction", 55),
|
||||
drift_high: parse_i32(&r, "marriage_drift_high", 0),
|
||||
drift_low: parse_i32(&r, "marriage_drift_low", 0),
|
||||
title1_tr: r.get("title1_tr").cloned().unwrap_or_default(),
|
||||
title2_tr: r.get("title2_tr").cloned().unwrap_or_default(),
|
||||
})
|
||||
})
|
||||
.filter(|m| m.id > 0)
|
||||
.collect();
|
||||
|
||||
let mut lovers: Vec<LoverData> = lover_rows
|
||||
.into_iter()
|
||||
.filter_map(|r| {
|
||||
Some(LoverData {
|
||||
rel_id: parse_i32(&r, "rel_id", -1),
|
||||
c1: parse_i32(&r, "c1", -1),
|
||||
c2: parse_i32(&r, "c2", -1),
|
||||
lover_role: r.get("lover_role").cloned().unwrap_or_default(),
|
||||
affection: parse_i32(&r, "affection", 50),
|
||||
visibility: parse_i32(&r, "visibility", 0),
|
||||
discretion: parse_i32(&r, "discretion", 50),
|
||||
maintenance_level: parse_i32(&r, "maintenance_level", 50),
|
||||
status_fit: parse_i32(&r, "status_fit", 0),
|
||||
scandal_extra: parse_i32(&r, "scandal_extra_daily_pct", 0),
|
||||
title1_tr: r.get("title1_tr").cloned().unwrap_or_default(),
|
||||
title2_tr: r.get("title2_tr").cloned().unwrap_or_default(),
|
||||
})
|
||||
})
|
||||
.filter(|l| l.rel_id > 0)
|
||||
.collect();
|
||||
|
||||
let mut char_lover_count: std::collections::HashMap<i32, usize> =
|
||||
std::collections::HashMap::new();
|
||||
for l in &lovers {
|
||||
*char_lover_count.entry(l.c1).or_insert(0) += 1;
|
||||
*char_lover_count.entry(l.c2).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
conn.prepare("upd_vd", QUERY_UPDATE_LOVER_VISIBILITY_DISCRETION)?;
|
||||
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
|
||||
});
|
||||
|
||||
let mut v_net = 0i32;
|
||||
if l.maintenance_level < 35 {
|
||||
v_net += 1;
|
||||
}
|
||||
if l.affection < 30 {
|
||||
v_net += 1;
|
||||
}
|
||||
if l.status_fit == -2 {
|
||||
v_net += 2;
|
||||
}
|
||||
if ehe_conflict {
|
||||
v_net += 1;
|
||||
}
|
||||
if l.discretion >= 60 {
|
||||
v_net -= 1;
|
||||
}
|
||||
if l.maintenance_level >= 70 {
|
||||
v_net -= 1;
|
||||
}
|
||||
let new_vis = clamp_i32(l.visibility + v_net, 0, 100);
|
||||
|
||||
let mut d_net = 0i32;
|
||||
if l.maintenance_level >= 70 {
|
||||
d_net += 1;
|
||||
}
|
||||
if l.maintenance_level < 35 {
|
||||
d_net -= 1;
|
||||
}
|
||||
if l.visibility > 60 {
|
||||
d_net -= 1;
|
||||
}
|
||||
let new_disc = clamp_i32(l.discretion + d_net, 0, 100);
|
||||
|
||||
conn.execute(
|
||||
"upd_vd",
|
||||
&[&new_vis, &new_disc, &l.rel_id],
|
||||
)?;
|
||||
l.visibility = new_vis;
|
||||
l.discretion = new_disc;
|
||||
}
|
||||
|
||||
conn.prepare("mark_daily", QUERY_MARK_LOVER_DAILY_DONE)?;
|
||||
for l in &lovers {
|
||||
conn.execute("mark_daily", &[&l.rel_id])?;
|
||||
}
|
||||
|
||||
for m in &marriages {
|
||||
let touching: Vec<&LoverData> = lovers
|
||||
.iter()
|
||||
.filter(|l| {
|
||||
let (a, b) = (l.c1, l.c2);
|
||||
((a == m.m1 && b != m.m2) || (b == m.m1 && a != m.m2))
|
||||
|| ((a == m.m2 && b != m.m1) || (b == m.m2 && a != m.m1))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if touching.is_empty() {
|
||||
let mut sat = m.satisfaction;
|
||||
let mut dh = m.drift_high;
|
||||
let mut dl = m.drift_low;
|
||||
if sat > 55 {
|
||||
dh += 1;
|
||||
if dh >= 3 {
|
||||
sat = (sat - 1).max(0);
|
||||
dh = 0;
|
||||
}
|
||||
} else if sat < 55 {
|
||||
dl += 1;
|
||||
if dl >= 5 {
|
||||
sat = (sat + 1).min(100);
|
||||
dl = 0;
|
||||
}
|
||||
}
|
||||
conn.prepare("upd_marriage", QUERY_UPDATE_MARRIAGE_STATE)?;
|
||||
conn.execute(
|
||||
"upd_marriage",
|
||||
&[&sat, &dh, &dl, &m.id],
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mg = marriage_rank_group(&m.title1_tr, &m.title2_tr);
|
||||
let mistress_n = touching
|
||||
.iter()
|
||||
.filter(|l| l.lover_role == "mistress_or_favorite")
|
||||
.count();
|
||||
let total_lovers_here = touching.len();
|
||||
let mut delta = 0i32;
|
||||
for l in &touching {
|
||||
delta += lover_marriage_daily_delta(
|
||||
mg,
|
||||
l,
|
||||
m.satisfaction,
|
||||
mistress_n,
|
||||
total_lovers_here,
|
||||
);
|
||||
}
|
||||
if touching.iter().any(|l| l.visibility >= 60) {
|
||||
delta -= 2;
|
||||
}
|
||||
if total_lovers_here >= 2 {
|
||||
delta -= 2;
|
||||
}
|
||||
if touching
|
||||
.iter()
|
||||
.any(|l| l.lover_role == "mistress_or_favorite" && l.maintenance_level < 35)
|
||||
{
|
||||
delta -= 1;
|
||||
}
|
||||
|
||||
let sat = clamp_i32(m.satisfaction + delta, 0, 100);
|
||||
conn.prepare("upd_marriage", QUERY_UPDATE_MARRIAGE_STATE)?;
|
||||
conn.execute(
|
||||
"upd_marriage",
|
||||
&[&sat, &m.drift_high, &m.drift_low, &m.id],
|
||||
)?;
|
||||
}
|
||||
|
||||
conn.prepare("upd_rep", QUERY_UPDATE_CHARACTER_REPUTATION)?;
|
||||
for l in &lovers {
|
||||
let g = pair_rank_group(&l.title1_tr, &l.title2_tr);
|
||||
let scandal = l.visibility > 70;
|
||||
let rm = rank_rep_modifier(g, scandal);
|
||||
let vf = 0.4 + (l.visibility as f64 / 100.0) * 1.6;
|
||||
let base = match l.lover_role.as_str() {
|
||||
"secret_affair" => -0.2,
|
||||
"lover" => -0.4,
|
||||
"mistress_or_favorite" => -0.6,
|
||||
_ => -0.4,
|
||||
};
|
||||
let mut delta = base * vf * rm;
|
||||
|
||||
let order_ok = l.maintenance_level >= 65
|
||||
&& l.discretion >= 60
|
||||
&& l.visibility <= 35
|
||||
&& mistress_count_for_pair(&lovers, l.c1, l.c2) <= 1;
|
||||
if l.lover_role == "mistress_or_favorite" && order_ok {
|
||||
if g == 2 {
|
||||
delta = 0.1;
|
||||
} else if g == 3 {
|
||||
delta = 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
delta = delta.clamp(-3.0, 1.0);
|
||||
|
||||
for cid in [l.c1, l.c2] {
|
||||
let cur = fetch_reputation(&mut conn, cid)?;
|
||||
let new_rep = (cur + delta).clamp(0.0, 100.0);
|
||||
let s = format!("{:.2}", new_rep);
|
||||
conn.execute("upd_rep", &[&s, &cid])?;
|
||||
}
|
||||
|
||||
let married_c1 = marriages
|
||||
.iter()
|
||||
.any(|m| m.m1 == l.c1 || m.m2 == l.c1);
|
||||
let married_c2 = marriages
|
||||
.iter()
|
||||
.any(|m| m.m1 == l.c2 || m.m2 == l.c2);
|
||||
let mut p = 1.0
|
||||
+ (l.visibility as f64) / 25.0
|
||||
+ if married_c1 || married_c2 { 2.0 } else { 0.0 }
|
||||
+ if l.status_fit == -2 { 2.0 } else { 0.0 }
|
||||
+ if l.maintenance_level < 25 { 3.0 } else { 0.0 }
|
||||
+ if char_lover_count.get(&l.c1).copied().unwrap_or(0) >= 2
|
||||
|| char_lover_count.get(&l.c2).copied().unwrap_or(0) >= 2
|
||||
{
|
||||
3.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
+ l.scandal_extra as f64;
|
||||
if l.discretion >= 75 {
|
||||
p -= 2.0;
|
||||
}
|
||||
if g == 3
|
||||
&& l.lover_role == "mistress_or_favorite"
|
||||
&& l.maintenance_level >= 65
|
||||
&& l.visibility <= 40
|
||||
{
|
||||
p -= 2.0;
|
||||
}
|
||||
p = p.clamp(0.0, 25.0);
|
||||
if self.dist.sample(&mut self.rng) * 100.0 < p {
|
||||
let _ = self.base.broker.publish(format!(
|
||||
r#"{{"event":"falukant_family_scandal_hint","relationship_id":{}}}"#,
|
||||
l.rel_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_monthly(&mut self) -> Result<(), DbError> {
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("get_lovers_m", QUERY_GET_ACTIVE_LOVER_ROWS_FOR_MONTHLY)?;
|
||||
let lover_rows = conn.execute("get_lovers_m", &[])?;
|
||||
|
||||
conn.prepare("upd_under", QUERY_UPDATE_LOVER_UNDERPAY_STATE)?;
|
||||
conn.prepare("reset_under", QUERY_RESET_LOVER_UNDERPAY_COUNTERS)?;
|
||||
conn.prepare("mar_sub", QUERY_MARRIAGE_SUBTRACT_SATISFACTION)?;
|
||||
conn.prepare("mark_monthly", QUERY_MARK_LOVER_MONTHLY_DONE)?;
|
||||
|
||||
for r in lover_rows {
|
||||
let rel_id = parse_i32(&r, "rel_id", -1);
|
||||
if rel_id < 0 {
|
||||
continue;
|
||||
}
|
||||
let lover_role = r.get("lover_role").cloned().unwrap_or_default();
|
||||
let maintenance_level = parse_i32(&r, "maintenance_level", 50);
|
||||
let status_fit = parse_i32(&r, "status_fit", 0);
|
||||
let mut base = parse_i32(&r, "monthly_base_cost", 0);
|
||||
if base <= 0 {
|
||||
base = match lover_role.as_str() {
|
||||
"secret_affair" => 10,
|
||||
"lover" => 30,
|
||||
"mistress_or_favorite" => 80,
|
||||
_ => 30,
|
||||
};
|
||||
}
|
||||
let title1 = r.get("title1_tr").cloned().unwrap_or_default();
|
||||
let title2 = r.get("title2_tr").cloned().unwrap_or_default();
|
||||
let g = pair_rank_group(&title1, &title2);
|
||||
let rank_m = rank_cost_multiplier(g);
|
||||
let maint_f = 0.6 + (maintenance_level as f64 / 100.0) * 1.2;
|
||||
let sf_m = match status_fit {
|
||||
-2 => 1.35,
|
||||
-1 => 1.15,
|
||||
_ => 1.0,
|
||||
};
|
||||
let cost = ((base as f64) * rank_m * maint_f * sf_m).round() as i32;
|
||||
|
||||
let u1 = parse_opt_i32(&r, "user1_id");
|
||||
let u2 = parse_opt_i32(&r, "user2_id");
|
||||
let payer = u1.or(u2).filter(|x| *x > 0);
|
||||
|
||||
let affection = parse_i32(&r, "affection", 50);
|
||||
let visibility = parse_i32(&r, "visibility", 0);
|
||||
let discretion = parse_i32(&r, "discretion", 50);
|
||||
let mut consec = parse_i32(&r, "months_underfunded", 0);
|
||||
let mut scandal_extra = parse_i32(&r, "scandal_extra_daily_pct", 0);
|
||||
|
||||
if let Some(uid) = payer {
|
||||
let money = self.get_user_money(uid)?;
|
||||
if money >= cost as f64 {
|
||||
self.base.change_falukant_user_money(
|
||||
uid,
|
||||
-(cost as f64),
|
||||
"lover maintenance",
|
||||
)?;
|
||||
conn.execute("reset_under", &[&rel_id])?;
|
||||
} else {
|
||||
let new_aff = clamp_i32(affection - 8, 0, 100);
|
||||
let new_disc = clamp_i32(discretion - 6, 0, 100);
|
||||
let new_vis = clamp_i32(visibility + 8, 0, 100);
|
||||
consec += 1;
|
||||
if consec >= 2 {
|
||||
scandal_extra = (scandal_extra + 2).min(100);
|
||||
}
|
||||
conn.execute(
|
||||
"upd_under",
|
||||
&[
|
||||
&new_aff,
|
||||
&new_disc,
|
||||
&new_vis,
|
||||
&consec,
|
||||
&scandal_extra,
|
||||
&rel_id,
|
||||
],
|
||||
)?;
|
||||
|
||||
for cid in [parse_i32(&r, "c1", 0), parse_i32(&r, "c2", 0)] {
|
||||
if cid <= 0 {
|
||||
continue;
|
||||
}
|
||||
if let Ok(Some(mid)) = marriage_id_for_character(&mut conn, cid) {
|
||||
conn.execute("mar_sub", &[&mid, &4])?;
|
||||
}
|
||||
}
|
||||
|
||||
if visibility >= 40 {
|
||||
for cid in [parse_i32(&r, "c1", 0), parse_i32(&r, "c2", 0)] {
|
||||
if cid > 0 {
|
||||
let cur = fetch_reputation(&mut conn, cid)?;
|
||||
let s = format!("{:.2}", (cur - 1.0).max(0.0));
|
||||
conn.prepare("upd_rep", QUERY_UPDATE_CHARACTER_REPUTATION)?;
|
||||
conn.execute("upd_rep", &[&s, &cid])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn.execute("mark_monthly", &[&rel_id])?;
|
||||
}
|
||||
|
||||
drop(conn);
|
||||
self.process_lover_births()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_user_money(&mut self, user_id: i32) -> Result<f64, DbError> {
|
||||
use super::sql::{QUERY_GET_CURRENT_MONEY};
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("gcm", QUERY_GET_CURRENT_MONEY)?;
|
||||
let rows = conn.execute("gcm", &[&user_id])?;
|
||||
Ok(rows
|
||||
.first()
|
||||
.and_then(|r| r.get("sum"))
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(0.0))
|
||||
}
|
||||
|
||||
fn process_lover_births(&mut self) -> Result<(), DbError> {
|
||||
let mut conn = self
|
||||
.base
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("lover_preg", QUERY_GET_LOVER_PREGNANCY_CANDIDATES)?;
|
||||
let rows = conn.execute("lover_preg", &[])?;
|
||||
|
||||
conn.prepare("insert_child", QUERY_INSERT_CHILD)?;
|
||||
conn.prepare("insert_child_rel_lover", QUERY_INSERT_CHILD_RELATION_LOVER)?;
|
||||
conn.prepare("pen_mar", QUERY_LOVER_BIRTH_PENALTY_MARRIAGE)?;
|
||||
conn.prepare("pen_rep", QUERY_LOVER_BIRTH_PENALTY_REPUTATION)?;
|
||||
|
||||
for row in rows {
|
||||
let father_cid = parse_i32(&row, "father_cid", -1);
|
||||
let mother_cid = parse_i32(&row, "mother_cid", -1);
|
||||
if father_cid < 0 || mother_cid < 0 {
|
||||
continue;
|
||||
}
|
||||
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 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 {
|
||||
continue;
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
"insert_child_rel_lover",
|
||||
&[&father_cid, &mother_cid, &child_cid],
|
||||
)?;
|
||||
|
||||
conn.execute("pen_mar", &[&father_cid])?;
|
||||
conn.execute("pen_mar", &[&mother_cid])?;
|
||||
conn.execute("pen_rep", &[&father_cid])?;
|
||||
conn.execute("pen_rep", &[&mother_cid])?;
|
||||
|
||||
if let Some(uid) = father_uid {
|
||||
self.publish_children(uid);
|
||||
}
|
||||
if let Some(uid) = mother_uid {
|
||||
self.publish_children(uid);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_children(&self, user_id: i32) {
|
||||
let children_update =
|
||||
format!(r#"{{"event":"children_update","user_id":{}}}"#, user_id);
|
||||
self.base.broker.publish(children_update);
|
||||
let update_status =
|
||||
format!(r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, user_id);
|
||||
self.base.broker.publish(update_status);
|
||||
}
|
||||
}
|
||||
|
||||
struct MarriageData {
|
||||
id: i32,
|
||||
m1: i32,
|
||||
m2: i32,
|
||||
satisfaction: i32,
|
||||
drift_high: i32,
|
||||
drift_low: i32,
|
||||
title1_tr: String,
|
||||
title2_tr: String,
|
||||
}
|
||||
|
||||
struct LoverData {
|
||||
rel_id: i32,
|
||||
c1: i32,
|
||||
c2: i32,
|
||||
lover_role: String,
|
||||
affection: i32,
|
||||
visibility: i32,
|
||||
discretion: i32,
|
||||
maintenance_level: i32,
|
||||
status_fit: i32,
|
||||
scandal_extra: i32,
|
||||
title1_tr: String,
|
||||
title2_tr: String,
|
||||
}
|
||||
|
||||
fn parse_i32(row: &crate::db::Row, key: &str, default: i32) -> i32 {
|
||||
row.get(key)
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn parse_opt_i32(row: &crate::db::Row, key: &str) -> Option<i32> {
|
||||
row.get(key).and_then(|v| v.parse::<i32>().ok())
|
||||
}
|
||||
|
||||
fn clamp_i32(v: i32, lo: i32, hi: i32) -> i32 {
|
||||
v.max(lo).min(hi)
|
||||
}
|
||||
|
||||
fn title_group(tr: &str) -> u8 {
|
||||
match tr {
|
||||
"noncivil" | "civil" | "sir" => 0,
|
||||
"townlord" | "by" | "landlord" => 1,
|
||||
"knight" | "baron" | "count" | "palsgrave" | "margrave" | "landgrave" => 2,
|
||||
"ruler" | "elector"
|
||||
| "imperial-prince"
|
||||
| "duke"
|
||||
| "grand-duke"
|
||||
| "prince-regent"
|
||||
| "king" => 3,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn marriage_rank_group(t1: &str, t2: &str) -> u8 {
|
||||
title_group(t1).max(title_group(t2))
|
||||
}
|
||||
|
||||
fn pair_rank_group(t1: &str, t2: &str) -> u8 {
|
||||
title_group(t1).max(title_group(t2))
|
||||
}
|
||||
|
||||
fn rank_cost_multiplier(g: u8) -> f64 {
|
||||
match g {
|
||||
0 => 1.0,
|
||||
1 => 1.6,
|
||||
2 => 2.6,
|
||||
3 => 4.0,
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn rank_rep_modifier(g: u8, scandal: bool) -> f64 {
|
||||
if g == 3 && scandal {
|
||||
return 1.5;
|
||||
}
|
||||
match g {
|
||||
0 => 1.8,
|
||||
1 => 1.3,
|
||||
2 => 1.0,
|
||||
3 => 0.7,
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn lover_marriage_daily_delta(
|
||||
mg: u8,
|
||||
l: &LoverData,
|
||||
m_sat: i32,
|
||||
mistress_n: usize,
|
||||
total_lovers: usize,
|
||||
) -> i32 {
|
||||
let role = l.lover_role.as_str();
|
||||
match mg {
|
||||
0 => match role {
|
||||
"secret_affair" => -1,
|
||||
"lover" => -2,
|
||||
"mistress_or_favorite" => -3,
|
||||
_ => -2,
|
||||
},
|
||||
1 => match role {
|
||||
"secret_affair" => -1,
|
||||
"lover" => -1,
|
||||
"mistress_or_favorite" => -2,
|
||||
_ => -1,
|
||||
},
|
||||
2 => match role {
|
||||
"secret_affair" => -1,
|
||||
"lover" => -1,
|
||||
"mistress_or_favorite" => -1,
|
||||
_ => -1,
|
||||
},
|
||||
3 => {
|
||||
if role == "mistress_or_favorite" {
|
||||
if l.visibility <= 35
|
||||
&& l.maintenance_level >= 65
|
||||
&& m_sat >= 45
|
||||
&& mistress_n <= 1
|
||||
&& total_lovers <= 1
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if l.visibility > 50 || l.maintenance_level < 40 {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if role == "secret_affair" {
|
||||
return 0;
|
||||
}
|
||||
if role == "lover" {
|
||||
return 0;
|
||||
}
|
||||
0
|
||||
}
|
||||
_ => -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn mistress_count_for_pair(lovers: &[LoverData], c1: i32, c2: i32) -> usize {
|
||||
lovers
|
||||
.iter()
|
||||
.filter(|l| {
|
||||
(l.c1 == c1 || l.c2 == c1 || l.c1 == c2 || l.c2 == c2)
|
||||
&& l.lover_role == "mistress_or_favorite"
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
fn fetch_reputation(conn: &mut crate::db::DbConnection, cid: i32) -> Result<f64, DbError> {
|
||||
conn.prepare("fr", "SELECT COALESCE(reputation, 50)::float8 AS r FROM falukant_data.character WHERE id = $1")?;
|
||||
let rows = conn.execute("fr", &[&cid])?;
|
||||
Ok(rows
|
||||
.first()
|
||||
.and_then(|r| r.get("r"))
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(50.0))
|
||||
}
|
||||
|
||||
fn marriage_id_for_character(
|
||||
conn: &mut crate::db::DbConnection,
|
||||
cid: i32,
|
||||
) -> Result<Option<i32>, DbError> {
|
||||
conn.prepare(
|
||||
"mid",
|
||||
r#"SELECT r.id FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id
|
||||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||||
WHERE r.character1_id = $1 OR r.character2_id = $1 LIMIT 1"#,
|
||||
)?;
|
||||
let rows = conn.execute("mid", &[&cid])?;
|
||||
Ok(rows
|
||||
.first()
|
||||
.and_then(|r| r.get("id"))
|
||||
.and_then(|v| v.parse::<i32>().ok()))
|
||||
}
|
||||
|
||||
impl Worker for FalukantFamilyWorker {
|
||||
fn start_worker_thread(&mut self) {
|
||||
let pool = self.base.pool.clone();
|
||||
let broker = self.base.broker.clone();
|
||||
|
||||
self.base
|
||||
.start_worker_with_loop(move |state: Arc<WorkerState>| {
|
||||
let mut worker = FalukantFamilyWorker::new(pool.clone(), broker.clone());
|
||||
while state.running_worker.load(Ordering::Relaxed) {
|
||||
worker.run_iteration(&state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn stop_worker_thread(&mut self) {
|
||||
self.base.stop_worker();
|
||||
}
|
||||
|
||||
fn enable_watchdog(&mut self) {
|
||||
self.base.start_watchdog();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ mod user_character;
|
||||
mod transport;
|
||||
mod weather;
|
||||
mod events;
|
||||
mod falukant_family;
|
||||
mod sql;
|
||||
|
||||
pub use base::Worker;
|
||||
@@ -27,4 +28,5 @@ pub use user_character::UserCharacterWorker;
|
||||
pub use transport::TransportWorker;
|
||||
pub use weather::WeatherWorker;
|
||||
pub use events::EventsWorker;
|
||||
pub use falukant_family::FalukantFamilyWorker;
|
||||
|
||||
|
||||
@@ -1677,7 +1677,10 @@ pub const QUERY_SET_MARRIAGES_BY_PARTY: &str = r#"
|
||||
SELECT id
|
||||
FROM falukant_type.relationship AS rt
|
||||
WHERE rt.tr = 'married'
|
||||
)
|
||||
),
|
||||
marriage_satisfaction = 55,
|
||||
marriage_drift_high = 0,
|
||||
marriage_drift_low = 0
|
||||
WHERE rel.id IN (
|
||||
SELECT rel2.id
|
||||
FROM falukant_data.party AS p
|
||||
@@ -2005,3 +2008,266 @@ pub const QUERY_GET_CHARACTERS_FOR_CHURCH_OFFICE: &str = r#"
|
||||
LIMIT $2;
|
||||
"#;
|
||||
|
||||
// --- Falukant: Familie / Liebhaber / Ehezufriedenheit (siehe migrations/001_falukant_family_lovers.sql) ---
|
||||
|
||||
pub const QUERY_FAMILY_SCHEMA_READY: &str = r#"
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data'
|
||||
AND table_name = 'relationship_state'
|
||||
AND column_name = 'last_daily_processed_at'
|
||||
) AS ready;
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_DAILY: &str = r#"
|
||||
SELECT
|
||||
r.id AS rel_id,
|
||||
r.character1_id AS c1,
|
||||
r.character2_id AS c2,
|
||||
rs.lover_role,
|
||||
rs.affection,
|
||||
rs.visibility,
|
||||
rs.discretion,
|
||||
rs.maintenance_level,
|
||||
rs.status_fit,
|
||||
rs.monthly_base_cost,
|
||||
rs.scandal_extra_daily_pct,
|
||||
rs.months_underfunded,
|
||||
c1.gender AS g1,
|
||||
c2.gender AS g2,
|
||||
COALESCE(t1.tr, '') AS title1_tr,
|
||||
COALESCE(t2.tr, '') AS title2_tr,
|
||||
COALESCE(c1.reputation, 50)::float8 AS rep1,
|
||||
COALESCE(c2.reputation, 50)::float8 AS rep2,
|
||||
fu1.id AS user1_id,
|
||||
fu2.id AS user2_id
|
||||
FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt
|
||||
ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id
|
||||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||||
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 rs.active = true
|
||||
AND (
|
||||
rs.last_daily_processed_at IS NULL
|
||||
OR (rs.last_daily_processed_at::date < CURRENT_DATE)
|
||||
);
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_MONTHLY: &str = r#"
|
||||
SELECT
|
||||
r.id AS rel_id,
|
||||
r.character1_id AS c1,
|
||||
r.character2_id AS c2,
|
||||
rs.lover_role,
|
||||
rs.affection,
|
||||
rs.visibility,
|
||||
rs.discretion,
|
||||
rs.maintenance_level,
|
||||
rs.status_fit,
|
||||
rs.monthly_base_cost,
|
||||
rs.scandal_extra_daily_pct,
|
||||
rs.months_underfunded,
|
||||
c1.gender AS g1,
|
||||
c2.gender AS g2,
|
||||
COALESCE(t1.tr, '') AS title1_tr,
|
||||
COALESCE(t2.tr, '') AS title2_tr,
|
||||
COALESCE(c1.reputation, 50)::float8 AS rep1,
|
||||
COALESCE(c2.reputation, 50)::float8 AS rep2,
|
||||
fu1.id AS user1_id,
|
||||
fu2.id AS user2_id
|
||||
FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt
|
||||
ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id
|
||||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||||
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 rs.active = true
|
||||
AND (
|
||||
rs.last_monthly_processed_at IS NULL
|
||||
OR date_trunc('month', rs.last_monthly_processed_at) < date_trunc('month', CURRENT_TIMESTAMP)
|
||||
);
|
||||
"#;
|
||||
|
||||
pub const QUERY_UPDATE_LOVER_VISIBILITY_DISCRETION: &str = r#"
|
||||
UPDATE falukant_data.relationship_state
|
||||
SET visibility = $1::smallint,
|
||||
discretion = $2::smallint
|
||||
WHERE relationship_id = $3::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_UPDATE_LOVER_UNDERPAY_STATE: &str = r#"
|
||||
UPDATE falukant_data.relationship_state
|
||||
SET affection = $1::smallint,
|
||||
discretion = $2::smallint,
|
||||
visibility = $3::smallint,
|
||||
months_underfunded = $4::smallint,
|
||||
scandal_extra_daily_pct = $5::smallint
|
||||
WHERE relationship_id = $6::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_MARRIAGE_ROWS: &str = r#"
|
||||
SELECT
|
||||
r.id AS marriage_id,
|
||||
r.character1_id AS m1,
|
||||
r.character2_id AS m2,
|
||||
r.marriage_satisfaction,
|
||||
r.marriage_drift_high,
|
||||
r.marriage_drift_low,
|
||||
COALESCE(t1.tr, '') AS title1_tr,
|
||||
COALESCE(t2.tr, '') AS title2_tr
|
||||
FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id
|
||||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility;
|
||||
"#;
|
||||
|
||||
pub const QUERY_UPDATE_MARRIAGE_STATE: &str = r#"
|
||||
UPDATE falukant_data.relationship
|
||||
SET marriage_satisfaction = $1::smallint,
|
||||
marriage_drift_high = $2::smallint,
|
||||
marriage_drift_low = $3::smallint
|
||||
WHERE id = $4::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_MARRIAGE_SUBTRACT_SATISFACTION: &str = r#"
|
||||
UPDATE falukant_data.relationship r
|
||||
SET marriage_satisfaction = GREATEST(0, r.marriage_satisfaction - $2::int)
|
||||
FROM falukant_type.relationship rt
|
||||
WHERE rt.id = r.relationship_type_id
|
||||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||||
AND r.id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_RESET_LOVER_UNDERPAY_COUNTERS: &str = r#"
|
||||
UPDATE falukant_data.relationship_state
|
||||
SET months_underfunded = 0
|
||||
WHERE relationship_id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_MARK_LOVER_DAILY_DONE: &str = r#"
|
||||
UPDATE falukant_data.relationship_state
|
||||
SET last_daily_processed_at = NOW()
|
||||
WHERE relationship_id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_MARK_LOVER_MONTHLY_DONE: &str = r#"
|
||||
UPDATE falukant_data.relationship_state
|
||||
SET last_monthly_processed_at = NOW()
|
||||
WHERE relationship_id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_UPDATE_CHARACTER_REPUTATION: &str = r#"
|
||||
UPDATE falukant_data.character
|
||||
SET reputation = $1::numeric,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_GET_LOVER_PREGNANCY_CANDIDATES: &str = r#"
|
||||
SELECT
|
||||
CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END AS father_cid,
|
||||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END AS mother_cid,
|
||||
CASE WHEN c1.gender = 'male' THEN c1.title_of_nobility ELSE c2.title_of_nobility END AS title_of_nobility,
|
||||
CASE WHEN c1.gender = 'male' THEN c1.last_name ELSE c2.last_name END AS last_name,
|
||||
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
|
||||
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
|
||||
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
|
||||
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days
|
||||
FROM falukant_data.relationship r
|
||||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id AND rs.active = true
|
||||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||||
JOIN falukant_data.character c_female ON c_female.id = (
|
||||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END
|
||||
)
|
||||
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 = 'female' AND c2.gender = 'male')
|
||||
OR (c1.gender = 'male' AND c2.gender = 'female')
|
||||
AND rs.affection >= 45
|
||||
AND rs.maintenance_level >= 30
|
||||
AND rs.last_monthly_processed_at IS NOT NULL
|
||||
AND date_trunc('month', rs.last_monthly_processed_at) = date_trunc('month', CURRENT_TIMESTAMP)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM falukant_data.child_relation cr
|
||||
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
|
||||
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
|
||||
AND cr.created_at >= date_trunc('month', CURRENT_TIMESTAMP)
|
||||
)
|
||||
AND (CURRENT_DATE - c_female.birthdate::date) >= 4380
|
||||
AND (CURRENT_DATE - c_female.birthdate::date) < 18993
|
||||
AND random() * 100.0 < (
|
||||
LEAST(12.0, GREATEST(0.0,
|
||||
CASE rs.lover_role
|
||||
WHEN 'secret_affair' THEN 2.0
|
||||
WHEN 'lover' THEN 4.0
|
||||
WHEN 'mistress_or_favorite' THEN 6.0
|
||||
ELSE 0.0
|
||||
END
|
||||
+ CASE WHEN rs.affection >= 75 THEN 2.0 ELSE 0.0 END
|
||||
+ CASE WHEN rs.visibility >= 70 AND rs.affection < 50 THEN -2.0 ELSE 0.0 END
|
||||
+ CASE
|
||||
WHEN (CURRENT_DATE - c_female.birthdate::date) > 14600
|
||||
THEN -3.0
|
||||
ELSE 0.0
|
||||
END
|
||||
))
|
||||
);
|
||||
"#;
|
||||
|
||||
pub const QUERY_LOVER_BIRTH_PENALTY_MARRIAGE: &str = r#"
|
||||
UPDATE falukant_data.relationship r
|
||||
SET marriage_satisfaction = GREATEST(0, r.marriage_satisfaction - 8)
|
||||
FROM falukant_type.relationship rt
|
||||
WHERE rt.id = r.relationship_type_id
|
||||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||||
"#;
|
||||
|
||||
pub const QUERY_LOVER_BIRTH_PENALTY_REPUTATION: &str = r#"
|
||||
UPDATE falukant_data.character
|
||||
SET reputation = GREATEST(0::numeric, COALESCE(reputation, 50::numeric) - 4::numeric),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1::int;
|
||||
"#;
|
||||
|
||||
pub const QUERY_INSERT_CHILD_RELATION_LOVER: &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,
|
||||
'hidden_bastard',
|
||||
'lover',
|
||||
FALSE,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
"#;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user