diff --git a/art/items/money/kronen.png b/art/items/money/kronen.png new file mode 100644 index 0000000..bd6d464 Binary files /dev/null and b/art/items/money/kronen.png differ diff --git a/art/items/money/scherben.png b/art/items/money/scherben.png new file mode 100644 index 0000000..ec196d6 Binary files /dev/null and b/art/items/money/scherben.png differ diff --git a/art/items/money/taler.png b/art/items/money/taler.png new file mode 100644 index 0000000..0ff9e26 Binary files /dev/null and b/art/items/money/taler.png differ diff --git a/docs/Ashen_Realms_Waehrungssystem_Scherben_Taler_Kronen_Implementierung_V1.md b/docs/Ashen_Realms_Waehrungssystem_Scherben_Taler_Kronen_Implementierung_V1.md new file mode 100644 index 0000000..4aea907 --- /dev/null +++ b/docs/Ashen_Realms_Waehrungssystem_Scherben_Taler_Kronen_Implementierung_V1.md @@ -0,0 +1,2034 @@ +# Ashen Realms – Währungssystem Scherben, Taler & Kronen – Implementierungsspezifikation V1 + +## Zweck + +Dieses Dokument beschreibt die technische und fachliche Einführung des allgemeinen Geldsystems von **Ashen Realms**. + +Das bisherige Modell mit einer einzelnen sichtbaren Währung „Silber“ wird ersetzt durch ein dreistufiges Währungssystem: + +**Scherben → Taler → Kronen** + +Das System soll: + +- kleine und große Geldbeträge kompakt darstellen, +- zur Welt und Atmosphäre von Ashen Realms passen, +- keine unnötig großen Zahlen in der UI erzeugen, +- technisch weiterhin einfach berechenbar bleiben, +- vollständig serverautoritativ funktionieren, +- mit Händlern, Rufbelohnungen, Quests und späteren Wirtschaftssystemen kompatibel sein, +- klar von Gebietswährungen wie Grenzmarken, Dämmermarken und Siegelbruchstücken getrennt bleiben. + +--- + +# 1. Grundprinzip + +Die allgemeine Spielwährung besteht aus drei sichtbaren Einheiten: + +| Einheit | Verhältnis | +|---|---:| +| Scherbe | 1 | +| Taler | 100 Scherben | +| Krone | 100 Taler = 10.000 Scherben | + +Damit gilt: + +```text +100 Scherben = 1 Taler +100 Taler = 1 Krone +1 Krone = 10.000 Scherben +``` + +Beispiele: + +```text +18 Scherben +1 Taler 35 Scherben +8 Taler +1 Krone 42 Taler 65 Scherben +``` + +--- + +# 2. Wichtigste technische Entscheidung + +## Geld wird intern niemals als drei getrennte Werte gespeichert. + +Die Datenbank speichert ausschließlich den Gesamtwert in der kleinsten Einheit: + +**Scherben** + +Beispiel: + +```text +1 Krone 42 Taler 65 Scherben +``` + +wird intern gespeichert als: + +```text +14.265 Scherben +``` + +Berechnung: + +```text +1 × 10.000 ++ 42 × 100 ++ 65 += 14.265 +``` + +Dadurch bleiben: + +- Vergleiche einfach, +- Käufe atomar, +- Belohnungen einfach, +- API-Verträge eindeutig, +- Migrationen übersichtlich, +- Rundungsprobleme ausgeschlossen. + +--- + +# 3. Begriffe + +## Allgemeine Währung + +Die allgemeine handelbare Währung lautet: + +- **Scherben** +- **Taler** +- **Kronen** + +Sie wird für normale wirtschaftliche Vorgänge verwendet. + +Beispiele: + +- Händlerkäufe +- Händlerverkäufe +- Heiltränke +- Taschen +- Basis-Ausrüstung +- Dienstleistungen +- spätere Marktgebühren +- spätere Handelsfunktionen + +--- + +## Gebietswährungen + +Gebietswährungen bleiben vollständig separate Systeme. + +Beispiele: + +- Grenzmarken +- Dämmermarken +- Siegelbruchstücke + +Gebietswährungen dürfen niemals automatisch in Scherben umgerechnet werden. + +Sie besitzen: + +- eigene Balance, +- eigene Händler, +- eigene Freischaltbedingungen, +- eigene Progressionsfunktion. + +--- + +# 4. Abgrenzung zum Rufsystem + +Das allgemeine Geldsystem und das Rufsystem arbeiten zusammen, sind aber fachlich getrennt. + +Ein Händler oder Questgeber kann bei der Abgabe von Trophäen gleichzeitig vergeben: + +```text +Geld ++ regionalen Ruf ++ Weltruhm +``` + +Beispiel: + +```text +5 Raider Insignias abgegeben + +Belohnung: ++ 1 Taler 35 Scherben ++ 24 Ruf der Grenzwacht ++ 3 Weltruhm +``` + +Die drei Belohnungen müssen getrennt berechnet und persistiert werden. + +Es gibt keine automatische Formel wie: + +```text +1 Ruf = X Scherben +``` + +Ruf und Geld besitzen unterschiedliche Designfunktionen. + +--- + +# 5. Designziel + +Das System soll verhindern, dass normale Preise langfristig aussehen wie: + +```text +348.721 Silber +``` + +Stattdessen: + +```text +34 Kronen 87 Taler 21 Scherben +``` + +Dadurch bleibt die Economy visuell übersichtlich. + +--- + +# 6. Balancing-Grundsatz + +Der Vertical Slice soll überwiegend mit: + +- Scherben +- wenigen Talern + +arbeiten. + +Kronen sollen am Anfang selten oder gar nicht auftauchen. + +Empfohlene Progression: + +## Frühes Spiel + +Typische Beträge: + +```text +5–90 Scherben +``` + +## Fortgeschrittenes Tier 1 + +Typische Beträge: + +```text +1–5 Taler +``` + +## Tier 2 + +Typische Beträge: + +```text +3–25 Taler +``` + +## Tier 3 + +Typische Beträge: + +```text +15–80 Taler +``` + +## Spätere Spielphasen + +Erst hier werden Kronen zu einer regelmäßigen sichtbaren Einheit. + +--- + +# 7. Umstellung der bisherigen Silberwerte + +Die bisherigen Balancing-Dokumente verwenden für den Vertical Slice eine einzelne Währung „Silber“. + +Für die erste Migration gilt: + +> **1 bisheriges Silber = 1 Scherbe** + +Dadurch bleiben sämtliche bestehenden Balancing-Verhältnisse zunächst unverändert. + +Beispiele: + +| Bisher | Neu | +|---|---| +| 15 Silber | 15 Scherben | +| 50 Silber | 50 Scherben | +| 80 Silber | 80 Scherben | +| 120 Silber | 1 Taler 20 Scherben | +| 180 Silber | 1 Taler 80 Scherben | +| 250 Silber | 2 Taler 50 Scherben | +| 350 Silber | 3 Taler 50 Scherben | + +Diese Umstellung ist bewusst rein repräsentativ. + +Das eigentliche Economy-Balancing kann später separat angepasst werden. + +--- + +# 8. Aktualisierte Beispielpreise + +Die bisherigen Beispielpreise werden zunächst folgendermaßen dargestellt: + +| Gegenstand | Neuer Preis | +|---|---:| +| Kleiner Heiltrank | 15 Scherben | +| Basis Tier-1 Item | 50–80 Scherben | +| Basis Tier-2 Item | 1 Taler 20 Scherben – 1 Taler 80 Scherben | +| Basis Tier-3 Item | 2 Taler 50 Scherben – 3 Taler 50 Scherben | + +Diese Werte sind nur die direkte Migration des bisherigen Balancings. + +Nach Playtests darf die Economy unabhängig davon neu skaliert werden. + +--- + +# 9. Datenmodell + +## Character + +Der Charakter benötigt einen einzelnen Geldwert. + +Empfohlener Feldname: + +```ts +moneyShards +``` + +Alternativ: + +```ts +currencyAmount +``` + +Empfohlen wird jedoch der explizite Name: + +```ts +moneyShards +``` + +Beispiel: + +```ts +@Column({ + type: 'bigint', + default: 0, +}) +moneyShards: string; +``` + +--- + +# 10. Warum PostgreSQL BIGINT? + +Langfristige Online-Spiele können sehr große Geldmengen erzeugen. + +Deshalb sollte kein normales 32-Bit-Integer verwendet werden. + +PostgreSQL: + +```text +BIGINT +``` + +TypeORM liefert BIGINT-Werte in JavaScript typischerweise als String. + +Dadurch soll im Domain-Code bewusst mit: + +```ts +bigint +``` + +gearbeitet werden. + +Beispiel: + +```ts +const currentMoney = BigInt(character.moneyShards); +``` + +--- + +# 11. Domain-Typ + +Optional kann ein eigener Typ eingeführt werden: + +```ts +export type MoneyAmount = bigint; +``` + +Keine Floating-Point-Zahlen verwenden. + +Verboten: + +```ts +number mit Dezimalstellen +``` + +Geld besitzt niemals Nachkommastellen. + +--- + +# 12. Zentraler MoneyService + +Datei: + +```text +apps/api/src/economy/money.service.ts +``` + +Der Service kapselt sämtliche grundlegenden Geldoperationen. + +Beispielinterface: + +```ts +@Injectable() +export class MoneyService { + canAfford(current: bigint, price: bigint): boolean; + + add(current: bigint, amount: bigint): bigint; + + subtract(current: bigint, amount: bigint): bigint; + + assertCanAfford(current: bigint, price: bigint): void; +} +``` + +--- + +# 13. Validierungsregeln + +Für jeden Geldbetrag gilt: + +```text +amount >= 0 +``` + +Ein Charakter darf niemals einen negativen Geldbestand besitzen. + +Verboten: + +```text +-15 Scherben +``` + +Ein Kauf muss fehlschlagen, wenn: + +```text +currentMoney < price +``` + +Domain-Fehler: + +```text +INSUFFICIENT_FUNDS +``` + +Beispiel: + +```json +{ + "statusCode": 400, + "code": "INSUFFICIENT_FUNDS", + "message": "You do not have enough money." +} +``` + +--- + +# 14. Atomare Geldtransaktionen + +Alle Vorgänge, bei denen Geld und ein weiterer Spielzustand verändert werden, müssen atomar erfolgen. + +Beispiel Händlerkauf: + +```text +Geld abziehen ++ Item hinzufügen +``` + +muss innerhalb derselben Datenbanktransaktion passieren. + +Entweder: + +```text +beides erfolgreich +``` + +oder: + +```text +gar nichts +``` + +Ein Fehler darf niemals zu folgendem Zustand führen: + +```text +Geld weg +aber Item nicht erhalten +``` + +--- + +# 15. Händlerkauf + +Beispiel: + +```ts +await dataSource.transaction(async manager => { + const character = await lockCharacter(manager, characterId); + + const current = BigInt(character.moneyShards); + const price = BigInt(shopItem.priceShards); + + moneyService.assertCanAfford(current, price); + + character.moneyShards = (current - price).toString(); + + await manager.save(character); + + await inventoryService.grantItem( + manager, + character.id, + shopItem.itemDefinitionId, + 1, + ); +}); +``` + +--- + +# 16. Row Locking + +Bei wirtschaftlich relevanten Schreiboperationen soll der Character-Datensatz gesperrt werden. + +Empfohlen: + +```text +SELECT ... FOR UPDATE +``` + +bzw. TypeORM pessimistic write lock. + +Dadurch werden parallele Käufe verhindert, die denselben Geldbetrag mehrfach ausgeben könnten. + +--- + +# 17. Itempreise + +`ItemDefinition` kann weiterhin einen Basispreis besitzen. + +Bisher: + +```ts +sellPrice +``` + +Neu: + +```ts +sellPriceShards +``` + +Für kaufbare Angebote sollte der Preis nicht zwingend direkt am Item liegen. + +Empfohlen: + +```text +ShopOffer +``` + +mit: + +```ts +priceShards +``` + +Dadurch können verschiedene Händler dasselbe Item zu unterschiedlichen Preisen anbieten. + +--- + +# 18. ShopOffer + +Empfohlenes Modell: + +```ts +ShopOffer { + id + shopId + itemDefinitionId + priceShards + requiredReputation + requiredGlobalRenown + enabled +} +``` + +Wichtig: + +Der Preis wird vollständig in Scherben gespeichert. + +Beispiel: + +```text +2 Taler 25 Scherben +``` + +wird gespeichert als: + +```text +225 +``` + +--- + +# 19. Verkauf von Items + +Auch Verkaufserlöse werden in Scherben angegeben. + +Beispiel: + +```ts +sellPriceShards: 35 +``` + +Der Verkauf: + +```text +Item entfernen ++ Geld erhöhen +``` + +erfolgt atomar. + +--- + +# 20. Trophäen und Händlerabgabe + +Für das neue Rufsystem sollte die Abgabe von Trophäen ebenfalls Geld in Scherben vergeben. + +Beispiel Reward-Konfiguration: + +```ts +{ + moneyShards: 135, + regionalReputation: 24, + globalRenown: 3 +} +``` + +Die UI zeigt daraus: + +```text +1 Taler 35 Scherben +``` + +--- + +# 21. Reward-Modell + +Ein generisches Belohnungsmodell kann beispielsweise enthalten: + +```ts +export interface RewardDefinition { + moneyShards?: number; + regionalReputation?: number; + globalRenown?: number; + items?: RewardItem[]; + currencies?: RewardCurrency[]; +} +``` + +Langfristig kann `moneyShards` intern zu `bigint` werden. + +Content-Werte können für den Vertical Slice weiterhin normale Ganzzahlen sein, solange sie beim Domain-Übergang validiert werden. + +--- + +# 22. Gebietswährungen separat halten + +Gebietswährungen dürfen nicht in: + +```text +moneyShards +``` + +gespeichert werden. + +Empfohlenes separates Modell: + +```text +CharacterCurrency +``` + +Beispiel: + +```ts +CharacterCurrency { + characterId + currencyKey + amount +} +``` + +Beispiele für `currencyKey`: + +```text +border-marks +dusk-marks +seal-fragments +``` + +Dadurch bleiben allgemeines Geld und Progressionswährungen sauber getrennt. + +--- + +# 23. API-Darstellung + +Die API sollte Geld immer als kleinste Einheit übertragen. + +Beispiel: + +```json +{ + "moneyShards": "14265" +} +``` + +Nicht: + +```json +{ + "crowns": 1, + "talers": 42, + "shards": 65 +} +``` + +Die Aufteilung ist eine Darstellungsaufgabe. + +--- + +# 24. Warum keine aufgeteilten API-Werte? + +Folgende Struktur sollte vermieden werden: + +```json +{ + "crowns": 1, + "talers": 42, + "shards": 65 +} +``` + +Sie erzeugt unnötige Probleme bei: + +- Addition, +- Subtraktion, +- Validierung, +- Käufen, +- Rundung, +- Synchronisierung, +- späteren Änderungen des Umrechnungsverhältnisses. + +Die Domain besitzt genau einen Wert. + +--- + +# 25. Shared Contract + +Im Shared Package: + +```ts +export interface MoneyDto { + amountShards: string; +} +``` + +Oder direkt im Character DTO: + +```ts +export interface CharacterDto { + id: string; + name: string; + moneyShards: string; +} +``` + +--- + +# 26. Frontend Money Utility + +Datei: + +```text +apps/web/src/app/shared/money/ +``` + +Empfohlene Funktionen: + +```ts +export interface FormattedMoney { + crowns: bigint; + talers: bigint; + shards: bigint; +} + +export function splitMoney(amount: bigint): FormattedMoney { + const crowns = amount / 10_000n; + const remainderAfterCrowns = amount % 10_000n; + + const talers = remainderAfterCrowns / 100n; + const shards = remainderAfterCrowns % 100n; + + return { + crowns, + talers, + shards, + }; +} +``` + +--- + +# 27. Anzeigeformat + +Die Standardanzeige soll leere hohe Einheiten ausblenden. + +Beispiele: + +```text +18 Scherben +``` + +nicht: + +```text +0 Kronen 0 Taler 18 Scherben +``` + +--- + +```text +2 Taler 15 Scherben +``` + +nicht: + +```text +0 Kronen 2 Taler 15 Scherben +``` + +--- + +```text +3 Kronen 7 Taler 4 Scherben +``` + +--- + +# 28. Nullwerte + +Bei exakt null: + +```text +0 Scherben +``` + +Nicht: + +```text +– +``` + +oder leer. + +--- + +# 29. Kurzformat + +Für sehr kompakte UI-Bereiche darf ein Kurzformat verwendet werden. + +Beispiel: + +```text +3 K 42 T 18 S +``` + +Das Kurzformat ist jedoch nicht die Standarddarstellung. + +Standard: + +```text +3 Kronen 42 Taler 18 Scherben +``` + +--- + +# 30. UI-Komponente + +Empfohlene wiederverwendbare Komponente: + +```text +MoneyDisplayComponent +``` + +Input: + +```ts +@Input({ required: true }) +amountShards!: string | bigint; +``` + +Optionale Varianten: + +```ts +variant: + 'full' + | 'compact' + | 'price' +``` + +--- + +# 31. Beispiel MoneyDisplayComponent + +```html +
+ @if (money.crowns > 0n) { + + + {{ money.crowns }} + + } + + @if (money.talers > 0n) { + + + {{ money.talers }} + + } + + @if (money.shards > 0n || isZero) { + + + {{ money.shards }} + + } +
+``` + +--- + +# 32. Visuelles Design + +Die drei Währungseinheiten sollen eigene Icons erhalten. + +## Scherben + +Visuell: + +- kleines unregelmäßiges Metallstück, +- dunkel, +- abgenutzt, +- niedriger Wert, +- klare Silhouette. + +--- + +## Taler + +Visuell: + +- geprägte runde Münze, +- dunkles bzw. gealtertes Metall, +- deutlich wertiger als Scherben, +- kein glänzender moderner Coin-Look. + +--- + +## Krone + +Visuell: + +- hochwertige große Münze oder schwere Prägemünze, +- goldener oder warmer Metallton, +- königliche bzw. alte Prägung, +- deutlich wertvoll. + +--- + +# 33. Designregel für Icons + +Die Icons müssen der bestehenden Ashen-Realms-UI folgen: + +- Dark Fantasy, +- realistisch bis malerisch, +- keine Cartoon-Optik, +- keine Mobile-Game-Goldmünzen, +- keine grellen Glows, +- klare Lesbarkeit bei kleiner Größe, +- transparente Hintergründe. + +--- + +# 34. Topbar + +Die globale Topbar darf das Geld kompakt darstellen. + +Empfohlen: + +```text +[Krone] 0 [Taler] 3 [Scherbe] 47 +``` + +Wenn eine Einheit null ist, kann sie optional ausgeblendet werden. + +Frühes Spiel: + +```text +[Taler] 2 [Scherbe] 18 +``` + +--- + +# 35. Händleransicht + +Preise müssen über dieselbe zentrale Komponente dargestellt werden. + +Beispiel: + +```text +Basic Hide Bag + +Preis: +1 Taler 40 Scherben +``` + +Der Händler darf niemals selbst Geld formatieren. + +--- + +# 36. Tooltips + +Bei kompakten Darstellungen darf ein Tooltip den vollständigen Wert zeigen. + +Beispiel: + +```text +1 Krone 42 Taler 65 Scherben +``` + +--- + +# 37. Preisfarben + +Geld selbst erhält keine Seltenheitsfarbe. + +Bei einem kaufbaren Angebot: + +```text +bezahlbar → normale Textfarbe / Goldakzent +nicht bezahlbar → gedämpft / rot markiert +``` + +Die Einheiten behalten ihr eigenes Icon. + +--- + +# 38. Keine physische Inventarbelegung + +Scherben, Taler und Kronen belegen: + +```text +keine Inventarslots +``` + +Sie sind kein normales Item. + +Auch wenn sie visuell Münzen darstellen, werden sie als Charakterwert behandelt. + +--- + +# 39. Keine manuellen Wechselvorgänge + +Der Spieler muss niemals: + +```text +100 Scherben in 1 Taler wechseln +``` + +oder: + +```text +1 Krone aufbrechen +``` + +Das System rechnet automatisch. + +Beispiel: + +Spieler besitzt: + +```text +1 Krone +``` + +Kauft Item für: + +```text +20 Scherben +``` + +Danach besitzt er: + +```text +99 Taler 80 Scherben +``` + +Intern: + +```text +10.000 - 20 = 9.980 +``` + +--- + +# 40. Geldbelohnungen aus Kämpfen + +Im aktuellen Rufdesign sollen normale Gegner grundsätzlich nicht automatisch allgemeines Geld vergeben. + +Stattdessen können Gegner: + +- Trophäen, +- Felle, +- Insignien, +- andere verwertbare Materialien + +liefern. + +Diese werden bei passenden NPCs oder Händlern abgegeben. + +Dort entstehen: + +```text +Geld ++ Ruf ++ Weltruhm +``` + +Falls bestimmte Gegner später bewusst direkt Geld tragen sollen, bleibt das technisch möglich. + +Beispiel: + +```text +Bandit trägt 12 Scherben bei sich +``` + +Das ist eine Content-Entscheidung und keine technische Einschränkung. + +--- + +# 41. Quests + +Quests dürfen Geld direkt vergeben. + +Beispiel: + +```json +{ + "moneyShards": 250 +} +``` + +UI: + +```text +2 Taler 50 Scherben +``` + +--- + +# 42. Geldquellen + +Mögliche Geldquellen: + +- Verkauf von Trophäen, +- Trophäenabgabe, +- Questbelohnungen, +- Itemverkauf, +- direkte humanoide Gegnerbeute, +- Events, +- spätere Spielerökonomie. + +--- + +# 43. Geldsenken + +Mögliche Geldsenken: + +- Heiltränke, +- Taschen, +- Basis-Ausrüstung, +- Händlerangebote, +- Dienstleistungen, +- später Reparatur nur falls jemals eingeführt, +- Handelsgebühren, +- Reise- oder Teleportservices, +- kosmetische Komfortangebote. + +Für den Vertical Slice sollen Geldsenken überschaubar bleiben. + +--- + +# 44. Keine künstliche Inflation im Vertical Slice + +Für V1 nicht einführen: + +- tägliche Geldbelohnungen, +- Login-Gold, +- massive passive Einnahmen, +- exponentielle Questbelohnungen, +- zufällige Geldmultiplikatoren. + +Die Economy soll zunächst durch echtes Gameplay entstehen. + +--- + +# 45. Migration der Datenbank + +Falls aktuell ein Feld existiert wie: + +```text +silver +``` + +wird es migriert zu: + +```text +money_shards +``` + +Beispielmigration: + +```sql +ALTER TABLE "character" +RENAME COLUMN "silver" TO "money_shards"; + +ALTER TABLE "character" +ALTER COLUMN "money_shards" TYPE BIGINT; +``` + +Falls `silver` an anderen Tabellen verwendet wird: + +```text +sell_price +shop_price +quest_reward +monster_reward +``` + +sollen diese ebenfalls explizit auf Scherben umbenannt werden. + +Beispiele: + +```text +sell_price_shards +price_shards +money_reward_shards +``` + +--- + +# 46. Keine Legacy-Bezeichnung „silver“ + +Nach Abschluss der Migration soll die Codebasis keine fachlich relevante Verwendung mehr besitzen von: + +```text +silver +silverAmount +silverReward +silverPrice +``` + +Ausnahme: + +historische Migrationen. + +--- + +# 47. Migration von bestehendem Content + +Für jeden bestehenden Silberwert gilt initial: + +```text +newMoneyShards = oldSilver +``` + +Beispiel: + +```text +oldSilver = 180 +``` + +wird: + +```text +moneyShards = 180 +``` + +UI: + +```text +1 Taler 80 Scherben +``` + +--- + +# 48. Content-Schema + +Empfohlene Felder: + +## Monster + +Falls direkte Geldbelohnungen erlaubt sind: + +```ts +moneyMinShards +moneyMaxShards +``` + +Nicht: + +```ts +silverMin +silverMax +``` + +--- + +## Item + +```ts +sellPriceShards +``` + +--- + +## ShopOffer + +```ts +priceShards +``` + +--- + +## QuestReward + +```ts +moneyShards +``` + +--- + +## TrophyExchange + +```ts +moneyRewardShards +regionalReputationReward +globalRenownReward +``` + +--- + +# 49. Serverautorität + +Der Client darf niemals an den Server senden: + +```json +{ + "price": 150 +} +``` + +oder: + +```json +{ + "reward": 250 +} +``` + +Ein Händlerkauf sendet ausschließlich die fachliche Auswahl. + +Beispiel: + +```json +{ + "shopOfferId": "uuid" +} +``` + +Der Server lädt anschließend: + +```text +Preis +Anforderungen +Item +Rufvoraussetzung +``` + +aus seinen eigenen Daten. + +--- + +# 50. Beispiel Shop API + +## Request + +```http +POST /api/shops/:shopId/purchases +``` + +```json +{ + "offerId": "uuid" +} +``` + +## Response + +```json +{ + "purchasedItem": { + "itemDefinitionId": "uuid", + "quantity": 1 + }, + "moneyShards": "842", + "spentShards": "140" +} +``` + +Frontend: + +```text +Verbleibend: +8 Taler 42 Scherben +``` + +--- + +# 51. Beispiel Trophy Exchange API + +## Request + +```http +POST /api/npcs/:npcId/exchanges +``` + +```json +{ + "exchangeId": "raider-insignia-turn-in", + "quantity": 5 +} +``` + +## Server + +prüft: + +```text +NPC gültig +Exchange gültig +Spieler besitzt benötigte Trophäen +Menge erlaubt +Rufbedingungen erfüllt +``` + +berechnet: + +```text +Geld +Ruf +Weltruhm +``` + +und führt alles atomar aus. + +--- + +# 52. Beispiel Response + +```json +{ + "consumed": [ + { + "itemKey": "raider-insignia", + "quantity": 5 + } + ], + "rewards": { + "moneyShards": "135", + "regionalReputation": 24, + "globalRenown": 3 + }, + "character": { + "moneyShards": "427" + } +} +``` + +--- + +# 53. Event-System / WebSocket + +Sobald der zentrale WebSocket aus dem geplanten Realtime-Slice vorhanden ist, soll eine Geldänderung als Character-State-Event veröffentlicht werden. + +Beispiel: + +```ts +{ + type: 'CHARACTER_MONEY_CHANGED', + payload: { + moneyShards: '14265', + deltaShards: '135' + } +} +``` + +Der Client verwendet weiterhin den Serverwert als Wahrheit. + +--- + +# 54. Kein Client-seitiges Hochzählen als Wahrheit + +Animationen wie: + +```text ++1 Taler 35 Scherben +``` + +dürfen clientseitig dargestellt werden. + +Der tatsächliche neue Kontostand stammt jedoch immer vom Server. + +--- + +# 55. Auditierbarkeit + +Für V1 ist kein vollständiges Finanz-Ledger zwingend notwendig. + +Die Economy sollte aber so gebaut werden, dass später ein `CharacterMoneyTransaction`-Log ergänzt werden kann. + +Mögliche Felder: + +```text +id +characterId +amountDeltaShards +balanceAfterShards +reason +referenceType +referenceId +createdAt +``` + +Beispiele für `reason`: + +```text +SHOP_PURCHASE +ITEM_SALE +QUEST_REWARD +TROPHY_EXCHANGE +LOOT +ADMIN_ADJUSTMENT +``` + +Für den aktuellen Vertical Slice ist dies optional. + +--- + +# 56. Admin-Balancing + +Die geplante Admin-/Balancing-Oberfläche sollte Geldbeträge grundsätzlich in Scherben speichern, aber benutzerfreundlich formatieren. + +Eingabe darf später beispielsweise sein: + +```text +Krone: 0 +Taler: 2 +Scherben: 35 +``` + +Gespeichert wird: + +```text +235 +``` + +--- + +# 57. Tests – Money Utility + +Pflichttests: + +```ts +splitMoney(0n) +→ 0 / 0 / 0 + +splitMoney(18n) +→ 0 / 0 / 18 + +splitMoney(135n) +→ 0 / 1 / 35 + +splitMoney(10_000n) +→ 1 / 0 / 0 + +splitMoney(14_265n) +→ 1 / 42 / 65 +``` + +--- + +# 58. Tests – MoneyService + +Mindestens: + +```text +Geld hinzufügen +Geld abziehen +genügend Geld +zu wenig Geld +Nullbetrag +negative Eingaben werden abgelehnt +kein negativer Kontostand +``` + +--- + +# 59. Tests – Händlerkauf + +Mindestens: + +```text +Spieler hat genug Geld +→ Geld wird abgezogen +→ Item wird vergeben +``` + +```text +Spieler hat zu wenig Geld +→ Fehler +→ Geld bleibt unverändert +→ kein Item +``` + +```text +Itemgrant schlägt fehl +→ Transaktion rollback +→ Geld bleibt unverändert +``` + +--- + +# 60. Tests – parallele Käufe + +Ein Integrationstest soll sicherstellen: + +Spieler besitzt: + +```text +100 Scherben +``` + +Zwei parallele Requests versuchen jeweils: + +```text +80 Scherben +``` + +auszugeben. + +Erwartung: + +```text +genau ein Kauf erfolgreich +``` + +und niemals: + +```text +-60 Scherben +``` + +--- + +# 61. Tests – Trophäenabgabe + +Test: + +```text +5 Raider Insignias vorhanden +``` + +Abgabe: + +```text +5 +``` + +Erwartung: + +```text +Items entfernt +Geld erhöht +regionaler Ruf erhöht +Weltruhm erhöht +``` + +Alles in einer Transaktion. + +--- + +# 62. Frontend Tests + +Mindestens: + +```text +18 → 18 Scherben +135 → 1 Taler 35 Scherben +10000 → 1 Krone +14265 → 1 Krone 42 Taler 65 Scherben +``` + +Zusätzlich: + +- Nullwerte korrekt, +- große Werte korrekt, +- Kaufbutton bei zu wenig Geld deaktiviert oder klar markiert, +- Tooltip zeigt vollständigen Wert. + +--- + +# 63. Internationalisierung + +Da der Spielinhalt auf Englisch umgestellt werden soll, sollte die technische Implementierung keine deutsche Währungsbezeichnung fest verdrahten. + +Keys: + +```text +currency.shard +currency.shard_plural +currency.taler +currency.taler_plural +currency.crown +currency.crown_plural +``` + +Die deutsche Designbezeichnung bleibt: + +```text +Scherbe +Taler +Krone +``` + +Die endgültigen englischen Ingame-Namen können separat festgelegt werden. + +--- + +# 64. Keine Formatstrings im Backend + +Der Backend-Code soll niemals Texte erzeugen wie: + +```text +"1 Taler 35 Scherben" +``` + +Backend: + +```text +135 +``` + +Frontend / Localization: + +```text +1 Taler 35 Scherben +``` + +Dadurch bleibt das System übersetzbar. + +--- + +# 65. Empfohlene Modulstruktur + +Backend: + +```text +apps/api/src/economy/ +├── economy.module.ts +├── money.service.ts +├── money.service.spec.ts +├── types/ +│ └── money.types.ts +└── errors/ + └── insufficient-funds.error.ts +``` + +Frontend: + +```text +apps/web/src/app/shared/money/ +├── money-display.component.ts +├── money-display.component.html +├── money-display.component.scss +├── money-display.component.spec.ts +├── money.utils.ts +└── money.utils.spec.ts +``` + +--- + +# 66. Shared Contracts + +Optional: + +```text +packages/shared/src/economy/ +├── money.dto.ts +└── index.ts +``` + +--- + +# 67. Implementierungsreihenfolge + +## Task 1 – Domain und Konstanten + +- [ ] Umrechnungswerte definieren. +- [ ] `MoneyAmount`-Typ definieren. +- [ ] MoneyService implementieren. +- [ ] Unit Tests schreiben. + +Konstanten: + +```ts +export const SHARDS_PER_TALER = 100n; +export const TALERS_PER_CROWN = 100n; +export const SHARDS_PER_CROWN = 10_000n; +``` + +--- + +## Task 2 – Character-Migration + +- [ ] vorhandenes Silberfeld identifizieren. +- [ ] zu `moneyShards` umbenennen. +- [ ] PostgreSQL-Typ auf BIGINT setzen. +- [ ] bestehenden Wert 1:1 übernehmen. +- [ ] Migration testen. + +--- + +## Task 3 – Content-Migration + +Alle bisherigen Felder prüfen: + +- [ ] `silverMin` +- [ ] `silverMax` +- [ ] `sellPrice` +- [ ] Shoppreise +- [ ] Questbelohnungen +- [ ] Lootbelohnungen + +und auf explizite Scherben-Felder migrieren. + +--- + +## Task 4 – Shop-System + +- [ ] `ShopOffer.priceShards` +- [ ] serverautoritativen Kauf implementieren. +- [ ] Character pessimistisch sperren. +- [ ] Geld und Item in einer Transaktion ändern. +- [ ] Fehler `INSUFFICIENT_FUNDS`. + +--- + +## Task 5 – Verkaufssystem + +- [ ] `ItemDefinition.sellPriceShards` +- [ ] Verkauf serverseitig validieren. +- [ ] Item entfernen. +- [ ] Geld erhöhen. +- [ ] Transaktion verwenden. + +--- + +## Task 6 – Ruf-/Trophäenabgabe + +- [ ] Geldreward in Scherben ergänzen. +- [ ] regionalen Ruf separat berechnen. +- [ ] Weltruhm separat berechnen. +- [ ] Trophäen entfernen. +- [ ] alles atomar persistieren. + +--- + +## Task 7 – Frontend Formatter + +- [ ] `splitMoney` +- [ ] Formatierung +- [ ] Unit Tests +- [ ] Lokalisierung vorbereiten. + +--- + +## Task 8 – MoneyDisplayComponent + +- [ ] Full-Variante +- [ ] Compact-Variante +- [ ] Price-Variante +- [ ] Icons integrieren. +- [ ] responsive Verhalten prüfen. + +--- + +## Task 9 – Topbar + +- [ ] bisherige Silberanzeige entfernen. +- [ ] neue MoneyDisplay-Komponente verwenden. +- [ ] keine separate Businesslogik in Topbar. + +--- + +## Task 10 – Händler + +- [ ] Preise mit MoneyDisplay darstellen. +- [ ] Kaufbarkeit anhand Server-/Character-State anzeigen. +- [ ] nach Kauf neuen Server-Kontostand übernehmen. + +--- + +## Task 11 – Reward UI + +- [ ] Questbelohnungen +- [ ] Trophäenabgabe +- [ ] Itemverkauf +- [ ] spätere Loot-Zusammenfassung + +mit denselben Währungskomponenten darstellen. + +--- + +## Task 12 – Legacy Cleanup + +Projektweit suchen nach: + +```text +silver +Silver +SILVER +``` + +und veraltete fachliche Referenzen entfernen. + +Historische Migrationen bleiben unverändert. + +--- + +# 68. Definition of Done + +Das Währungssystem gilt als implementiert, wenn: + +- der Charakter nur einen allgemeinen Geldwert besitzt, +- dieser Wert intern vollständig in Scherben gespeichert wird, +- PostgreSQL BIGINT verwendet wird, +- Scherben, Taler und Kronen nur Darstellungsformen dieses Werts sind, +- 100 Scherben = 1 Taler gilt, +- 100 Taler = 1 Krone gilt, +- Händlerpreise serverseitig geladen werden, +- der Client keine Kaufpreise vorgeben kann, +- Käufe atomar durchgeführt werden, +- negative Geldbestände unmöglich sind, +- parallele Käufe nicht zu Doppel-Ausgaben führen, +- Gebietswährungen vollständig separat bleiben, +- Ruf und Weltruhm separat bleiben, +- die Trophäenabgabe Geld + Ruf + Weltruhm gemeinsam vergeben kann, +- alle bisherigen Silberwerte zunächst 1:1 zu Scherben migriert wurden, +- die UI Geld zentral über eine wiederverwendbare Komponente formatiert, +- keine Geschäftslogik für Kronen/Taler/Scherben in einzelnen Screens dupliziert wird, +- Backend und Datenbank keine formatierten Währungstexte speichern, +- automatisierte Tests für Umrechnung und Käufe existieren. + +--- + +# 69. Bewusst nicht Teil von V1 + +Noch nicht implementieren: + +- Wechselstuben, +- physische Münzstapel im Inventar, +- unterschiedliche Wechselkurse, +- regionale Geldsysteme, +- Inflation-Simulation, +- Bankkonten, +- Kredite, +- Zinsen, +- Spieler-zu-Spieler-Handel, +- Auktionshaus, +- Marktsteuern, +- komplexes Economy-Ledger, +- Währungsverfall. + +--- + +# 70. Zentrale Designregel + +> **Scherben, Taler und Kronen sind keine drei Währungen. Sie sind drei Darstellungsstufen derselben allgemeinen Währung.** + +Dadurch erhält Ashen Realms die kompakte, klassische Darstellung eines Browser-MMORPGs, ohne technisch unnötige Komplexität einzuführen. + +--- + +# 71. Kurzfassung + +Ashen Realms verwendet zukünftig: + +```text +100 Scherben = 1 Taler +100 Taler = 1 Krone +``` + +Die Datenbank speichert ausschließlich: + +```text +moneyShards +``` + +Gebietswährungen bleiben separat. + +Ruf und Weltruhm bleiben separat. + +Trophäen können bei Händlern oder NPCs in: + +```text +Geld ++ regionalen Ruf ++ Weltruhm +``` + +umgewandelt werden. + +Bestehende Silberwerte werden zunächst ohne Rebalancing migriert: + +```text +1 Silber = 1 Scherbe +``` + +Die wichtigste technische Regel lautet: + +> **Der Server speichert und berechnet nur Scherben. Die UI macht daraus Scherben, Taler und Kronen.**