feat(political-benefits): implement political powers and benefits system
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s

- Added new political powers and benefits functionalities, including reputation ticks, tax jurisdiction management, and appointment capabilities.
- Introduced a new job for periodic reputation updates and created necessary database tables for tracking political benefits.
- Enhanced the FalukantController and services to support new endpoints for managing political powers and appointments.
- Updated localization files to reflect new features and improve user experience across multiple languages.
- Modified the UI to display new political powers and benefits, ensuring accurate representation in the PoliticsView.
This commit is contained in:
Torsten Schulz (local)
2026-04-02 16:00:29 +02:00
parent 5d06d97737
commit 56be4b76c0
19 changed files with 1572 additions and 2 deletions

View File

@@ -0,0 +1,217 @@
# Falukant: Konzept — Daemon, Backend und UI für politische Amtsvorteile
Dieses Dokument beschreibt die **noch nicht implementierten** Teile zu den Amtsvorteilen (`reputation_periodic`, `appoint_politicians`, `set_regional_tax`, `free_lover_slots`, sowie optional `guard_protection` / `court_immunity` als Spielwirkung). Die **Anzeige** in der Politik-Übersicht existiert bereits; hier geht es um **Ausführung**, **Persistenz** und **Bedienoberfläche**.
---
## 1. Ausgangslage im Code
| Bereich | Stand |
|--------|--------|
| **Predefine** | `falukant_predefine.political_office_benefit` + Typen in `falukant_type.political_office_benefit_type`; JSON `value` pro Amt. |
| **Auslesen / Anzeige** | `FalukantService.politicsBenefitEntriesFromRows`, Frontend `PoliticsView.vue`. |
| **Bereits mechanisch** | `daily_salary` (beim Laden der Politik-Übersicht, `last_political_daily_salary_on` am `FalukantUser`); Steuerbefreiung in `getCumulativeTaxPercentForCharacter` (SQL mit exempt types). |
| **Daemon (C++)** | `PoliticsWorker` läuft **täglich nach 03:00** (`src/politics_worker.cpp`): Wahlen, Nachbesetzung, Benachrichtigungen — **kein** Bezug zu den neuen JSON-Vorteilen. |
| **Node-Daemon** | `backend/daemonServer.js` ist ein **WebSocket-Relay**, kein Spiel-Ticker. |
| **Steuern im Spiel** | Regionen haben `tax_percent`; kumulativ über Eltern-Regionen; Verkaufsbuchung in `falukantService` (Steueranteil). |
**Empfehlung zur Architektur:** zeitkritische **Batch-Jobs** (täglich, idempotent) entweder im **bestehenden C++-`PoliticsWorker`** direkt nach den bestehenden Schritten ausführen **oder** einen kleinen **Node-Cron-Job** (z. B. `node backend/jobs/politicalBenefitsTick.js` + systemd timer), der Sequelize nutzt. C++ vermeidet doppelte DB-Logik; Node vermeidet C++-Deploy für reine Sequelize-Regeln. Für **synchrone API-Aktionen** (Steuern setzen, Ernennungen) ist **ausschließlich das Node-Backend** sinnvoll.
---
## 2. Zielbild pro Vorteilstyp
### 2.1 `reputation_periodic` (Ansehen alle *x* Tage)
**Ziel:** Charaktere mit aktivem Amt erhalten in einem festen Intervall `gain` Ansehenspunkte (oberhalb 0, Cap z. B. 100), **höchstens einmal pro Intervall pro (Charakter × konkreter Benefit-Zeile)**.
**Persistenz (neu):**
- Tabelle z. B. `falukant_data.political_benefit_last_tick`
- `id`, `character_id`, `political_office_benefit_id` (FK auf Predefine-Zeile), `last_tick_at` (timestamptz), optional `ticks_count`.
- **Unique** `(character_id, political_office_benefit_id)`.
**Logik:**
1. Alle `political_office`-Zeilen mit `character_id IS NOT NULL` laden (oder seit letztem Lauf geänderte).
2. Pro Zeile zugehörige `office_type_id` → alle `PoliticalOfficeBenefit` mit `benefitDefinition.tr = 'reputation_periodic'`.
3. Für jede Kombination: `last_tick_at` lesen; wenn `now - last_tick_at >= intervalDays` (Kalendertage **Spielzeit** oder **UTC-Echtzeit** — festlegen; konsistent mit `daily_salary` ist **UTC-Datum** einfacher), dann `character.reputation` erhöhen, `last_tick_at = now`, Eintrag ins **Money-/Activity-Log** optional (`political_reputation_tick`).
4. **Mehrere Ämter:** mehrere Benefit-Zeilen = mehrere mögliche Ticks (jede mit eigenem Intervall), so wie konfiguriert.
5. **Idempotenz:** Pro Lauf nur ein Tick pro Unique-Key; Worker max. 1× täglich wie `PoliticsWorker` reicht, wenn Intervall ≥ 1 Tag; bei `intervalDays < 1` separaten Stunden-Job definieren (nicht empfohlen ohne Bedarf).
**Daemon-Ort:** neuer Schritt `performPoliticalBenefitTicks()` am Ende von `PoliticsWorker::performDailyPoliticsTask()` **oder** Node-Job 03:05 UTC.
**Push:** nach Tick `falukantUpdateStatus` an betroffene User (wie bei anderen Politics-Events).
---
### 2.2 `free_lover_slots` (kostenfreie Liebschaften / Mätressen)
**Ziel:** Die ersten *N* aktiven Liebschaften (Relationship-Typ `lover` + sichtbarer `relationship_state`, Rolle z. B. `lover` oder `mistress_or_favorite`) verursachen **keinen monatlichen Unterhalt** (`monthly_base_cost` effektiv 0 für diese Slots).
**Kein eigener Daemon nötig** — Anwendung beim **bestehenden monatlichen Abrechnungslauf** (C++ `UserCharacterWorker` oder Sequelize-Äquivalent), der `relationship_state` und Kosten verarbeitet.
**Backend-Mechanik:**
1. Hilfsfunktion `getFreePoliticalLoverSlotCount(characterId)`
- Aktive `PoliticalOffice` des Charakters → `office_type_id` → Summe `free_lover_slots.count` aus Benefits (max oder Summe — **Spec-Empfehlung: Summe**, Deckel optional `maxTotalFreeSlots: 5` in Config).
2. Liebschaften nach **Erstellungsdatum** oder fester Priorität sortieren; die ersten `N` erhalten `effectiveMonthlyCost = 0` für diese Abrechnung.
3. Optional: Flag in `relationship_state.flags_json` (`political_free_slot: true`) setzen/aktualisieren für Transparenz und UI — oder rein berechnet ohne Persistenz.
**Fairness:** Nur Charaktere, die **Inhaber** sind (nicht nur Kandidaten).
---
### 2.3 `appoint_politicians` (Ernennungsrecht)
**Ziel:** Amtsträger dürfen unter definierten Regeln **andere Ämter** in ihrer **Zuständigkeit** besetzen (NPC oder Spieler), ohne vollständige Wahl — oder: **Sonderwahl / Nachrücker** auslösen.
**Varianten (Priorisierung empfohlen):**
| Variante | Beschreibung | Aufwand |
|----------|--------------|--------|
| **A — Nachbesetzung** | Bei vakantem `political_office` in erlaubter Region: Appointer wählt **einen Kandidaten** aus Pool (nur Charaktere mit Titel/Alter wie bei Wahl); Server erstellt Eintrag `political_office` + Log. | Mittel |
| **B — Nur NPC** | Appointer „ernennt“ nur NPCs (bestehende NPC-Erzeugung); kein PvP-Konflikt. | Geringer |
| **C — Einladung** | `appointment_invite` an Spieler; Annahme erstellt Amt. | Höher |
**Persistenz (neu):**
- `falukant_data.political_appointment`
- `id`, `appointer_character_id`, `target_character_id` (nullable bei NPC), `office_type_id`, `region_id`, `status` (`pending`, `accepted`, `rejected`, `expired`, `completed`), `created_at`, `expires_at`, `completed_office_id` (nullable).
**Regeln:**
- Appointer muss **aktuelles** Amt haben, dessen Benefit `officeTrs` das Zielamt enthält.
- Zielregion muss zur **Kompetenz** passen (z. B. gleiche Stadt/Grafschaft wie Appointer-Amt oder laut `scope` aus JSON erweitert).
- Anti-Spam: Cooldown, max. offene Ernennungen pro Appointer.
**Daemon:** optional nur **Ablauf** (`expired`) einmal täglich im `PoliticsWorker` oder Node.
---
### 2.4 `set_regional_tax` (Steuersätze festlegen)
**Ziel:** Berechtigte setzen `tax_percent` (oder einen **Aufschlag**) für Regionen innerhalb ihrer Kompetenz (`scope`: `local`, `shire`, `duchy`, `national`).
**Datenmodell:**
- **Option A (minimal):** direkt `falukant_data.region.tax_percent` ändern + **Audit-Tabelle** `region_tax_history` (`region_id`, `old_value`, `new_value`, `setter_character_id`, `office_id`, `created_at`).
- **Option B (flexibler):** `region_political_tax_modifier` (additiv), Basis bleibt in `region`; effektive Steuer = Basis + Summe gültiger Modifier (mit TTL). Für „Rücknahme nach Amtsende“ einfacher.
**Backend-Mechanik:**
1. `GET /api/falukant/politics/tax-jurisdiction` — Regionen, für die der Charakter nach aktuellem Amt + `set_regional_tax` schreiben darf, inkl. aktuellem Wert und Grenzen.
2. `PUT /api/falukant/politics/region/:regionId/tax` — Body `{ percent }` oder `{ delta }`; Server prüft:
- Charakter hat passendes Amt;
- Region liegt in erlaubter Hierarchie (lokal = nur Amtsregion; shire = Teilbaum unter shire; …);
- **Clamp** min/max (global config, z. B. 025 %).
3. Verkaufs-/Steuer-SQL **unverändert** nutzen, sofern weiterhin `tax_percent` auf `region` liegt (bei Option B SQL um Modifier erweitern).
**Daemon:** nicht nötig; bei **Amtsende** optional Modifier entfernen oder auf Historie zurücksetzen (täglicher Job oder Trigger auf `political_office` delete).
---
### 2.5 `guard_protection` / `court_immunity` (Spielwirkung)
**Optional, später:**
- **guard_protection:** Reduktion von Untergrund-Erfolgswahrscheinlichkeit gegen den Charakter oder geringerer `visibility`-Anstieg bei Affären (wenn solche Formeln existieren).
- **court_immunity:** Abschwächung bestimmter Rechts-/Straf-Events (sobald es dafür Services gibt).
Konkrete Zahlen als JSON in `value` (`attackMitigationPercent`, …). **Daemon:** nicht zwingend; eher **bei Event-Auswertung** abfragen.
---
## 3. Daemon — Gesamtüberblick
```
03:00 (bestehend) PoliticsWorker
├─ … Wahlen, Nachbesetzung, Notifications …
├─ NEU: politicalBenefitReputationTicks()
└─ NEU: politicalAppointmentExpiry() [optional]
Monatlich / bestehender Character-Tick
└─ relationship monthly cost ← berücksichtigt free_lover_slots
```
**Idempotenz-Prinzip:** Jeder Batch-Job nutzt klare **Unique-Keys** und **Transaktionen**; bei Crash-Wiederholung keine Doppel-Ticks (Reputation).
**Observability:** strukturierte Logs `[PoliticalBenefits] characterId=… officeBenefitId=… action=reputation_tick`.
---
## 4. Backend-API (Node) — Übersicht
| Endpoint | Zweck |
|----------|--------|
| `GET /api/falukant/politics/my-powers` | Für eingeloggten Charakter: Liste **aktionabler** Vorteile inkl. Parameter, nächster `reputation`-Tick (read-only aus `political_benefit_last_tick`), freie Liebschaft-Slots-Zahl. |
| `GET /api/falukant/politics/tax-jurisdiction` | Regionen + aktuelle Steuer + erlaubter Bereich. |
| `PUT /api/falukant/politics/region/:id/tax` | Steuer setzen (siehe oben). |
| `GET /api/falukant/politics/appointable-offices` | Vakante oder ernennbare Posten + erlaubte Ziel-`officeTrs`. |
| `POST /api/falukant/politics/appointments` | Ernennung anstoßen (Body: `targetCharacterId`, `officeTypeId`, `regionId`). |
| `POST /api/falukant/politics/appointments/:id/accept` | Optional für Ziel-Spieler. |
**Service-Schicht:** `falukantPoliticalPowersService.js` (oder Erweiterung `falukantService`) mit klaren Guards und Wiederverwendung der Benefit-Loader aus `PoliticalOfficeBenefit`.
**Sicherheit:** Alle Routen mit `getFalukantUserByHashedId` + Charakter-Inhaber-Check; keine Überschreibung fremder Regionen.
---
## 5. UI-Konzept (Vue)
### 5.1 Einstieg
- In **Politik** neuer Unter-Tab oder Sektion **„Amtsbefugnisse“** (sichtbar nur, wenn `my-powers` nicht leer).
- Kurzinfo in **„Aktuelle Position“**: neben der Vorteilsliste ein Link „Befugnisse ausüben“.
### 5.2 Steuern (`set_regional_tax`)
- **Ansicht:** Tabelle Region (Name, Typ, aktueller Steuersatz, Slider oder Zahleneingabe).
- **Validierung clientseitig** spiegelt Server-Clamps.
- **Speichern** ruft `PUT` auf; Erfolgstoast; ggf. Eintrag in lokaler Historie (letzte 5 Änderungen aus `region_tax_history`).
### 5.3 Ernennungen (`appoint_politicians`)
- **Schritt 1:** Amt + Region wählen (nur erlaubte Kombinationen).
- **Schritt 2:** Ziel-Charakter suchen (Autocomplete über Community/Falukant-Suche) oder „NPC vorschlagen“ (Variante B).
- **Schritt 3:** Bestätigung, Anzeige der Regeln (Titel, Alter, Cooldown).
- **Einladungs-Variante:** Posteingang / Notification mit Annehmen/Ablehnen.
### 5.4 `reputation_periodic` / Meta
- Anzeige: **„Nächster Ansehens-Bonus: in X Tagen“** aus `last_tick_at` + `intervalDays` (nur lesend; Ausführung durch Daemon).
- Optional: Eintrag im **Tagebuch** / Notification nach Tick.
### 5.5 `free_lover_slots`
- In **Familie → Affären** (oder Liebschaften): Badge **„Amt: X Plätze ohne Unterhalt“**; in der Kostenübersicht pro Beziehung Kennzeichnung „politisch freigestellt“.
- Kein eigener Daemon-Dialog nötig.
### 5.6 Internationalisierung
- Alle neuen Strings unter `falukant.politics.powers.*` (de/en/es/ceb analog zu `benefits`).
---
## 6. Migrations-Reihenfolge (empfohlen)
1. `political_benefit_last_tick` (+ FKs, Indizes).
2. `region_tax_history` oder Modifier-Tabelle.
3. `political_appointment` (+ Indizes auf `status`, `appointer_character_id`).
4. Daemon-/Job-Implementierung Reputation.
5. API Steuern + UI.
6. API Ernennungen + UI.
7. Monatliche Kostenanpassung `free_lover_slots`.
---
## 7. Offene Produktentscheidungen
- **Echtzeit vs. Spielzeit** für `intervalDays` (aktuell Mischung möglich mit `daily_salary` auf UTC-Datum).
- **Summe vs. Maximum** bei mehreren Ämtern für `free_lover_slots`.
- **Ernennung:** nur innerhalb der eigenen Region oder auch in Unterregionen?
- **Steuer:** darf derselbe Region mehrfach von verschiedenen Ämtern gesetzt werden? (Empfehlung: letzte schreibende Instanz mit höherer `scope`-Priorität gewinnt, oder explizite Hierarchie-Regel.)
---
*Dokumentstand: abgestimmt auf Repository mit C++-`PoliticsWorker`, Sequelize-Falukant-Backend und bestehenden `political_office_benefit`-Seeds.*