@@ -1,132 +1,98 @@
//! Produktionszertifikat: tägliche Neuberechnung von `falukant_user.certificate`.
//! Spec: docs/FALUKANT_PRODUCTION_CERTIFICATE.md
//! Produktionszertifikat: tägliche Neuberechnung von `falukant_user.certificate` im **FalukantFamilyWorker-Daily-Tick**
//! (nicht in einem eigenen Worker-Thread). Spec: ` docs/FALUKANT_PRODUCTION_CERTIFICATE.md` und
//! „Falukant: Produktionszertifikate – Fach- und Integrationsspezifikation“.
use crate ::db ::{ ConnectionPool , DbError, Row } ;
use crate ::db ::{ DbError , Row } ;
use crate ::message_broker ::MessageBroker ;
use std ::sync ::atomic ::Ordering ;
use std ::sync ::Arc ;
use std ::time ::Duration ;
use super ::base ::{ BaseWorker , Worker , WorkerState } ;
use super ::base ::BaseWorker ;
use crate ::worker ::sql ::{
QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS , QUERY_UPDATE_FALUKANT_USER_CERTIFICATE ,
} ;
const DAILY_INTERVAL : Duration = Duration ::from_secs ( 24 * 3600 ) ;
/// Wenn `money` darunter liegt, gilt der Spieler als bankrott → Zertifikat auf Stufe 1.
/// Wenn `money` darunter liegt, gilt der Spieler als bankrott → Zertifikat auf Stufe 1 (Spec §4.7).
const BANKRUPTCY_MONEY_THRESHOLD : f64 = - 5000.0 ;
pub struct FalukantCertificateWorker {
base : BaseWorker ,
}
/// Einmal pro Daily-Tick (`FalukantFamilyWorker::process_daily`).
pub fn run_daily ( base : & BaseWorker , broker : & MessageBroker ) -> Result < ( ) , DbError > {
let pool = & base . pool ;
let mut conn = pool
. get ( )
. map_err ( | e | DbError ::new ( format! ( " DB-Verbindung fehlgeschlagen: {e} " ) ) ) ? ;
impl FalukantCertificateWorker {
pub fn new ( pool : ConnectionPool , broker : MessageBroker ) -> Self {
Self {
base : BaseWorker ::new ( " FalukantCertificateWorker " , pool , broker ) ,
conn . prepare ( " cert_rows " , QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS ) ? ;
conn . prepare ( " cert_upd " , QUERY_UPDATE_FALUKANT_USER_CERTIFICATE ) ? ;
let rows = conn . execute ( " cert_rows " , & [ ] ) ? ;
for row in rows {
let fu_id = parse_i32 ( & row , " falukant_user_id " , - 1 ) ;
if fu_id < 0 {
continue ;
}
let app_uid = parse_i32 ( & row , " app_user_id " , - 1 ) ;
let event_uid = if app_uid > 0 { app_uid } else { fu_id } ;
let current = parse_i32 ( & row , " certificate " , 1 ) . clamp ( 1 , 5 ) ;
let money = parse_f64 ( & row , " money " , 0.0 ) ;
let avg_knowledge = parse_f64 ( & row , " avg_knowledge " , 0.0 ) ;
let completed = parse_i64 ( & row , " completed_production_count " , 0 ) ;
let max_church_hierarchy = parse_i32 ( & row , " max_church_hierarchy " , 0 ) ;
let pol_names = row
. get ( " political_office_names " )
. cloned ( )
. unwrap_or_default ( ) ;
let reputation = parse_f64 ( & row , " reputation " , 50.0 ) ;
let title_level = parse_i32 ( & row , " title_level " , 0 ) ;
let house_position = parse_i32 ( & row , " house_position " , 0 ) ;
// Bankrott: Herabsetzung (Spec §4.7)
if money < = BANKRUPTCY_MONEY_THRESHOLD & & current > 1 {
conn . execute ( " cert_upd " , & [ & 1_ i32 , & fu_id ] ) ? ;
publish_certificate_event ( broker , event_uid , current , 1 ) ;
continue ;
}
let knowledge_points = knowledge_points_from_avg ( avg_knowledge ) ;
let production_points = production_points_from_count ( completed ) ;
let political_rank = max_political_rank_from_names ( & pol_names ) ;
let church_rank = church_rank_from_hierarchy ( max_church_hierarchy ) ;
let highest_office_rank = political_rank . max ( church_rank ) . min ( 5 ) ;
let office_points = highest_office_rank . min ( 5 ) ;
let nobility_points = ( title_level - 1 ) . clamp ( 0 , 5 ) ;
let reputation_points = reputation_points_from_rep ( reputation ) ;
let house_points = house_points_from_position ( house_position ) ;
let certificate_score = knowledge_points as f64 * 0.45
+ production_points as f64 * 0.30
+ office_points as f64 * 0.08
+ nobility_points as f64 * 0.05
+ reputation_points as f64 * 0.07
+ house_points as f64 * 0.05 ;
let raw_target = raw_target_from_score ( certificate_score ) ;
let effective_target = effective_certificate_target (
raw_target ,
avg_knowledge ,
completed ,
office_points ,
nobility_points ,
reputation_points ,
house_points ,
) ;
let new_certificate = if effective_target > current {
( current + 1 ) . min ( effective_target ) . min ( 5 )
} else {
current
} ;
if new_certificate ! = current {
conn . execute ( " cert_upd " , & [ & new_certificate , & fu_id ] ) ? ;
publish_certificate_event ( broker , event_uid , current , new_certificate ) ;
}
}
fn run_loop ( pool : ConnectionPool , broker : MessageBroker , state : Arc < WorkerState > ) {
let mut last : Option < std ::time ::Instant > = None ;
while state . running_worker . load ( Ordering ::Relaxed ) {
let now = std ::time ::Instant ::now ( ) ;
let run = match last {
None = > true ,
Some ( t ) = > now . saturating_duration_since ( t ) > = DAILY_INTERVAL ,
} ;
if run {
if let Err ( e ) = Self ::process_daily ( & pool , & broker ) {
eprintln! ( " [FalukantCertificateWorker] process_daily: {e} " ) ;
}
last = Some ( now ) ;
}
for _ in 0 .. 60 {
if ! state . running_worker . load ( Ordering ::Relaxed ) {
break ;
}
std ::thread ::sleep ( Duration ::from_secs ( 1 ) ) ;
}
}
}
fn process_daily ( pool : & ConnectionPool , broker : & MessageBroker ) -> Result < ( ) , DbError > {
let mut conn = pool
. get ( )
. map_err ( | e | DbError ::new ( format! ( " DB-Verbindung fehlgeschlagen: {e} " ) ) ) ? ;
conn . prepare ( " cert_rows " , QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS ) ? ;
conn . prepare ( " cert_upd " , QUERY_UPDATE_FALUKANT_USER_CERTIFICATE ) ? ;
let rows = conn . execute ( " cert_rows " , & [ ] ) ? ;
for row in rows {
let fu_id = parse_i32 ( & row , " falukant_user_id " , - 1 ) ;
if fu_id < 0 {
continue ;
}
let current = parse_i32 ( & row , " certificate " , 1 ) . clamp ( 1 , 127 ) ;
let money = parse_f64 ( & row , " money " , 0.0 ) ;
let avg_knowledge = parse_f64 ( & row , " avg_knowledge " , 0.0 ) ;
let completed = parse_i64 ( & row , " completed_production_count " , 0 ) ;
let max_church_hierarchy = parse_i32 ( & row , " max_church_hierarchy " , 0 ) ;
let pol_names = row
. get ( " political_office_names " )
. cloned ( )
. unwrap_or_default ( ) ;
let reputation = parse_f64 ( & row , " reputation " , 50.0 ) ;
let title_level = parse_i32 ( & row , " title_level " , 0 ) ;
let house_position = parse_i32 ( & row , " house_position " , 0 ) ;
// Bankrott: Herabsetzung erlaubt (Spec)
if money < = BANKRUPTCY_MONEY_THRESHOLD & & current > 1 {
conn . execute ( " cert_upd " , & [ & 1_ i32 , & fu_id ] ) ? ;
publish_certificate_event ( broker , fu_id , current , 1 ) ;
continue ;
}
let knowledge_points = knowledge_points_from_avg ( avg_knowledge ) ;
let production_points = production_points_from_count ( completed ) ;
let political_rank = max_political_rank_from_names ( & pol_names ) ;
let church_rank = church_rank_from_hierarchy ( max_church_hierarchy ) ;
let highest_office_rank = political_rank . max ( church_rank ) . min ( 5 ) ;
let office_points = highest_office_rank . min ( 5 ) ;
let nobility_points = ( title_level - 1 ) . clamp ( 0 , 5 ) ;
let reputation_points = reputation_points_from_rep ( reputation ) ;
let house_points = house_points_from_position ( house_position ) ;
let certificate_score = knowledge_points as f64 * 0.35
+ production_points as f64 * 0.20
+ office_points as f64 * 0.15
+ nobility_points as f64 * 0.10
+ reputation_points as f64 * 0.10
+ house_points as f64 * 0.10 ;
let raw_target = raw_target_from_score ( certificate_score ) ;
let effective_target = effective_certificate_target (
raw_target ,
avg_knowledge ,
completed ,
office_points ,
nobility_points ,
reputation_points ,
house_points ,
) ;
let new_certificate = if effective_target > current {
( current + 1 ) . min ( effective_target )
} else {
current
} ;
if new_certificate ! = current {
conn . execute ( " cert_upd " , & [ & new_certificate , & fu_id ] ) ? ;
publish_certificate_event ( broker , fu_id , current , new_certificate ) ;
}
}
Ok ( ( ) )
}
Ok ( ( ) )
}
fn publish_certificate_event ( broker : & MessageBroker , user_id : i32 , old_c : i32 , new_c : i32 ) {
@@ -189,7 +155,6 @@ fn production_points_from_count(n: i64) -> i32 {
}
}
/// Kirchliche Ämter: `hierarchy_level` auf 0– 5 begrenzen (Daemon).
fn church_rank_from_hierarchy ( h : i32 ) -> i32 {
if h < = 0 {
0
@@ -198,7 +163,7 @@ fn church_rank_from_hierarchy(h: i32) -> i32 {
}
}
/// Politische Amtsnamen → Rang 1– 5 (konfigurierbar im Daemon ).
/// Politische Amtsnamen → Rang 1– 5 (Heuristik im Daemon, ohne DB-Änderung ).
fn political_name_to_rank ( name : & str ) -> i32 {
let n = name . to_lowercase ( ) ;
if n . contains ( " reich " )
@@ -282,72 +247,36 @@ fn house_points_from_position(position: i32) -> i32 {
}
}
/// Spec §4.6 Schwellen für die Zielstufe (vor Mindestanforderungen).
fn raw_target_from_score ( score : f64 ) -> i32 {
if score > = 4.0 {
if score > = 3.8 {
5
} else if score > = 3.0 {
} else if score > = 2.8 {
4
} else if score > = 2.1 {
} else if score > = 1.8 {
3
} else if score > = 1.2 {
} else if score > = 0.9 {
2
} else {
1
}
}
fn status_one (
office_points : i32 ,
nobility_points : i32 ,
reputation_points : i32 ,
house_points : i32 ,
) -> bool {
office_points > = 1
| | nobility_points > = 1
| | reputation_points > = 2
| | house_points > = 1
}
fn status_count_cert4 (
office_points : i32 ,
nobility_points : i32 ,
reputation_points : i32 ,
house_points : i32 ,
) -> i32 {
fn cert5_two_of_social ( office_points : i32 , nobility_points : i32 , house_points : i32 ) -> bool {
let mut c = 0 ;
if office_points > = 1 {
if office_points > = 2 {
c + = 1 ;
}
if nobility_points > = 1 {
c + = 1 ;
}
if reputation_points > = 2 {
c + = 1 ;
}
if house_points > = 1 {
c + = 1 ;
}
c
}
fn cert5_extra_two (
office_points : i32 ,
nobility_points : i32 ,
house_points : i32 ,
) -> i32 {
let mut c = 0 ;
if office_points > = 2 {
c + = 1 ;
}
if nobility_points > = 2 {
c + = 1 ;
}
if house_points > = 2 {
c + = 1 ;
}
c
c > = 2
}
/// Mindestanforderungen je Stufe (Spec §4.5).
fn meets_min_for_level (
level : i32 ,
avg_knowledge : f64 ,
@@ -359,38 +288,27 @@ fn meets_min_for_level(
) -> bool {
match level {
1 = > true ,
2 = > avg_knowledge > = 2 5.0 & & completed > = 5 ,
3 = > {
avg_knowledge > = 40.0
& & completed > = 20
& & status_one (
office_points ,
nobility_points ,
reputation_points ,
house_points ,
)
}
2 = > avg_knowledge > = 1 5.0 & & completed > = 4 ,
3 = > avg_knowledge > = 28.0 & & completed > = 15 ,
4 = > {
avg_knowledge > = 5 5.0
& & completed > = 60
& & status_count_cert4 (
office_points ,
nobility_points ,
reputation_points ,
house_points ,
) > = 2
avg_knowledge > = 4 5.0
& & completed > = 45
& & ( office_points > = 1
| | nobility_points > = 1
| | reputation_points > = 2
| | house_points > = 2 )
}
5 = > {
avg_knowledge > = 7 0.0
& & completed > = 15 0
& & reputation_points > = 3
& & cert5_extra_two ( office_points , nobility_points , house_points ) > = 2
avg_knowledge > = 6 0.0
& & completed > = 11 0
& & reputation_points > = 2
& & cert5_two_of_social ( office_points , nobility_points , house_points )
}
_ = > false ,
}
}
/// Höchste Stufe ≤ `raw_target`, die alle Mindestanforderungen erfüllt.
/// Höchste Stufe ≤ `raw_target`, die alle Mindestanforderungen erfüllt (Spec §4.6) .
fn effective_certificate_target (
raw_target : i32 ,
avg_knowledge : f64 ,
@@ -416,23 +334,3 @@ fn effective_certificate_target(
}
1
}
impl Worker for FalukantCertificateWorker {
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 > | {
FalukantCertificateWorker ::run_loop ( pool . clone ( ) , broker . clone ( ) , state ) ;
} ) ;
}
fn stop_worker_thread ( & mut self ) {
self . base . stop_worker ( ) ;
}
fn enable_watchdog ( & mut self ) {
self . base . start_watchdog ( ) ;
}
}