# 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.**