Refactor Falukant daemon logic for time-based processing: Updated the handling of monthly servant costs and lover installments to align with the new game time model, introducing a unified "Monatstick" concept for cost calculations. Enhanced SQL queries and worker logic to ensure accurate processing every 2 hours, improving financial interactions and family dynamics.

This commit is contained in:
Torsten Schulz (local)
2026-03-23 10:11:49 +01:00
parent c82fbc0f7c
commit 708ffc3eda
7 changed files with 78 additions and 24 deletions

View File

@@ -34,8 +34,8 @@ use crate::message_broker::MessageBroker;
const DAILY_INTERVAL: Duration = Duration::from_secs(24 * 3600);
const MONTHLY_INTERVAL: Duration = Duration::from_secs(30 * 24 * 3600);
/// 12 Teilzahlungen pro Spieltag (24 h = 1 Spieljahr); 2 h = 1 Spielmonat.
const LOVER_INSTALLMENT_INTERVAL: Duration = Duration::from_secs(2 * 3600);
/// 12 Monatsticke pro Spieltag (24 h = 1 Spieljahr); 2 h = 1 Spielmonat (Liebschaft + Dienerschaft).
const GAME_MONTH_SLICE_INTERVAL: Duration = Duration::from_secs(2 * 3600);
pub struct FalukantFamilyWorker {
base: BaseWorker,
@@ -43,7 +43,8 @@ pub struct FalukantFamilyWorker {
dist: Uniform<f64>,
last_daily: Option<Instant>,
last_monthly: Option<Instant>,
last_lover_installment: Option<Instant>,
/// Gemeinsamer Tick für Liebschafts-Raten + Dienerschaft (Monatstick ≈ 2 h).
last_game_month_slice: Option<Instant>,
schema_ready: bool,
/// Migration `004_falukant_servants_daemon.sql` (Dienerschaft-Ticks).
servants_schema_ready: bool,
@@ -59,7 +60,7 @@ impl FalukantFamilyWorker {
dist: Uniform::from(0.0..1.0),
last_daily: None,
last_monthly: None,
last_lover_installment: None,
last_game_month_slice: None,
schema_ready: false,
servants_schema_ready: false,
lover_installment_schema_ready: false,
@@ -93,12 +94,21 @@ impl FalukantFamilyWorker {
self.last_monthly = Some(now);
}
if self.lover_installment_schema_ready {
if Self::should_run(self.last_lover_installment, now, LOVER_INSTALLMENT_INTERVAL) {
if let Err(e) = self.process_lover_installments() {
eprintln!("[FalukantFamilyWorker] process_lover_installments: {e}");
if self.servants_schema_ready || self.lover_installment_schema_ready {
if Self::should_run(self.last_game_month_slice, now, GAME_MONTH_SLICE_INTERVAL) {
if self.servants_schema_ready {
if let Err(e) =
super::falukant_servants::run_monthly(&self.base, &self.base.broker)
{
eprintln!("[FalukantFamilyWorker] falukant_servants::run_monthly: {e}");
}
}
self.last_lover_installment = Some(now);
if self.lover_installment_schema_ready {
if let Err(e) = self.process_lover_installments() {
eprintln!("[FalukantFamilyWorker] process_lover_installments: {e}");
}
}
self.last_game_month_slice = Some(now);
}
}
@@ -651,10 +661,6 @@ impl FalukantFamilyWorker {
}
fn process_monthly(&mut self) -> Result<(), DbError> {
if self.servants_schema_ready {
super::falukant_servants::run_monthly(&self.base, &self.base.broker)?;
}
let mut conn = self
.base
.pool

View File

@@ -1,5 +1,10 @@
//! Dienerschaft: Daily/Monthly nach Spec (docs: Handoff Dienerschaft).
//! Dienerschaft: Daily + „Monats“-Kosten nach Spielzeitmodell.
//! Voraussetzung: `migrations/004_falukant_servants_daemon.sql`.
//!
//! **Zeitmaßstab:** 1 Spieltag = 1 Spieljahr; ein „Monatstick“ ≈ **2 h** Wandzeit.
//! Der berechnete `monthly_cost` ist ein **abstrakter** Unterhaltsbetrag pro **Spielmonat**
//! (kein realistischer Vollmonatslohn). Pro Tick wird nur **1/12** dieses Betrags abgebucht
//! (12 Monatsticke pro Spieltag), analog zur Liebschafts-Logik.
use crate::db::DbError;
use crate::message_broker::MessageBroker;
@@ -260,7 +265,7 @@ pub fn run_daily(base: &BaseWorker, broker: &MessageBroker) -> Result<(), DbErro
Ok(())
}
/// Monthly: Kosten, Unterversorgung, Boni.
/// Monatstick (~2 h): Teilzahlung (1/12 des abstrakten Monatsbudgets), Unterversorgung, Boni.
pub fn run_monthly(base: &BaseWorker, broker: &MessageBroker) -> Result<(), DbError> {
let mut conn = base
.pool
@@ -346,9 +351,12 @@ pub fn run_monthly(base: &BaseWorker, broker: &MessageBroker) -> Result<(), DbEr
"high" => 1.3,
_ => 1.0,
};
let monthly_cost =
// Abstraktes Monatsbudget (ein Spielmonat); Abrechnung erfolgt zwölftelweise pro Tick.
let abstract_monthly_cost =
(servant_count as f64 * base_per_servant * quality_factor * pay_factor * 100.0).round()
/ 100.0;
let installment =
((abstract_monthly_cost / 12.0) * 100.0).round() / 100.0;
let lover_rows = conn.execute("cnt_lov", &[&character_id])?;
let lover_cnt = lover_rows
@@ -357,13 +365,16 @@ pub fn run_monthly(base: &BaseWorker, broker: &MessageBroker) -> Result<(), DbEr
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0);
let notify = if servant_count <= 0 || monthly_cost <= 0.0 {
let notify = if servant_count <= 0 || abstract_monthly_cost <= 0.0 {
conn.execute("upd_uh_mon", &[&false, &user_house_id])?;
false
} else if user_money >= monthly_cost {
} else if installment <= 0.0 {
conn.execute("upd_uh_mon", &[&false, &user_house_id])?;
false
} else if user_money >= installment {
base.change_falukant_user_money(
falukant_user_id,
-monthly_cost,
-installment,
"servants_monthly",
)?;
conn.execute("upd_uh_mon", &[&false, &user_house_id])?;

View File

@@ -2103,8 +2103,9 @@ pub const QUERY_GET_SERVANT_MONTHLY_ROWS: &str = r#"
JOIN falukant_data.character c ON c.user_id = fu.id AND c.health > 0
LEFT JOIN falukant_type.title t ON t.id = c.title_of_nobility
LEFT JOIN falukant_type.house ht ON ht.id = uh.house_type_id
-- Monatstick (Spielzeit): alle ~2 h, nicht Kalendermonat (siehe docs/FALUKANT_DAEMON_AENDERUNGSNOTIZ_ZEITMASSSTAB.md)
WHERE (uh.servants_last_monthly_at IS NULL
OR date_trunc('month', uh.servants_last_monthly_at) < date_trunc('month', CURRENT_TIMESTAMP))
OR uh.servants_last_monthly_at < NOW() - INTERVAL '2 hours')
AND NOT EXISTS (
SELECT 1 FROM falukant_type.house h
WHERE h.id = uh.house_type_id AND h.label_tr = 'under_bridge'