28 KiB
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:
100 Scherben = 1 Taler
100 Taler = 1 Krone
1 Krone = 10.000 Scherben
Beispiele:
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:
1 Krone 42 Taler 65 Scherben
wird intern gespeichert als:
14.265 Scherben
Berechnung:
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:
Geld
+ regionalen Ruf
+ Weltruhm
Beispiel:
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:
1 Ruf = X Scherben
Ruf und Geld besitzen unterschiedliche Designfunktionen.
5. Designziel
Das System soll verhindern, dass normale Preise langfristig aussehen wie:
348.721 Silber
Stattdessen:
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:
5–90 Scherben
Fortgeschrittenes Tier 1
Typische Beträge:
1–5 Taler
Tier 2
Typische Beträge:
3–25 Taler
Tier 3
Typische Beträge:
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:
moneyShards
Alternativ:
currencyAmount
Empfohlen wird jedoch der explizite Name:
moneyShards
Beispiel:
@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:
BIGINT
TypeORM liefert BIGINT-Werte in JavaScript typischerweise als String.
Dadurch soll im Domain-Code bewusst mit:
bigint
gearbeitet werden.
Beispiel:
const currentMoney = BigInt(character.moneyShards);
11. Domain-Typ
Optional kann ein eigener Typ eingeführt werden:
export type MoneyAmount = bigint;
Keine Floating-Point-Zahlen verwenden.
Verboten:
number mit Dezimalstellen
Geld besitzt niemals Nachkommastellen.
12. Zentraler MoneyService
Datei:
apps/api/src/economy/money.service.ts
Der Service kapselt sämtliche grundlegenden Geldoperationen.
Beispielinterface:
@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:
amount >= 0
Ein Charakter darf niemals einen negativen Geldbestand besitzen.
Verboten:
-15 Scherben
Ein Kauf muss fehlschlagen, wenn:
currentMoney < price
Domain-Fehler:
INSUFFICIENT_FUNDS
Beispiel:
{
"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:
Geld abziehen
+ Item hinzufügen
muss innerhalb derselben Datenbanktransaktion passieren.
Entweder:
beides erfolgreich
oder:
gar nichts
Ein Fehler darf niemals zu folgendem Zustand führen:
Geld weg
aber Item nicht erhalten
15. Händlerkauf
Beispiel:
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:
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:
sellPrice
Neu:
sellPriceShards
Für kaufbare Angebote sollte der Preis nicht zwingend direkt am Item liegen.
Empfohlen:
ShopOffer
mit:
priceShards
Dadurch können verschiedene Händler dasselbe Item zu unterschiedlichen Preisen anbieten.
18. ShopOffer
Empfohlenes Modell:
ShopOffer {
id
shopId
itemDefinitionId
priceShards
requiredReputation
requiredGlobalRenown
enabled
}
Wichtig:
Der Preis wird vollständig in Scherben gespeichert.
Beispiel:
2 Taler 25 Scherben
wird gespeichert als:
225
19. Verkauf von Items
Auch Verkaufserlöse werden in Scherben angegeben.
Beispiel:
sellPriceShards: 35
Der Verkauf:
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:
{
moneyShards: 135,
regionalReputation: 24,
globalRenown: 3
}
Die UI zeigt daraus:
1 Taler 35 Scherben
21. Reward-Modell
Ein generisches Belohnungsmodell kann beispielsweise enthalten:
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:
moneyShards
gespeichert werden.
Empfohlenes separates Modell:
CharacterCurrency
Beispiel:
CharacterCurrency {
characterId
currencyKey
amount
}
Beispiele für currencyKey:
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:
{
"moneyShards": "14265"
}
Nicht:
{
"crowns": 1,
"talers": 42,
"shards": 65
}
Die Aufteilung ist eine Darstellungsaufgabe.
24. Warum keine aufgeteilten API-Werte?
Folgende Struktur sollte vermieden werden:
{
"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:
export interface MoneyDto {
amountShards: string;
}
Oder direkt im Character DTO:
export interface CharacterDto {
id: string;
name: string;
moneyShards: string;
}
26. Frontend Money Utility
Datei:
apps/web/src/app/shared/money/
Empfohlene Funktionen:
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:
18 Scherben
nicht:
0 Kronen 0 Taler 18 Scherben
2 Taler 15 Scherben
nicht:
0 Kronen 2 Taler 15 Scherben
3 Kronen 7 Taler 4 Scherben
28. Nullwerte
Bei exakt null:
0 Scherben
Nicht:
–
oder leer.
29. Kurzformat
Für sehr kompakte UI-Bereiche darf ein Kurzformat verwendet werden.
Beispiel:
3 K 42 T 18 S
Das Kurzformat ist jedoch nicht die Standarddarstellung.
Standard:
3 Kronen 42 Taler 18 Scherben
30. UI-Komponente
Empfohlene wiederverwendbare Komponente:
MoneyDisplayComponent
Input:
@Input({ required: true })
amountShards!: string | bigint;
Optionale Varianten:
variant:
'full'
| 'compact'
| 'price'
31. Beispiel MoneyDisplayComponent
<div class="money-display">
@if (money.crowns > 0n) {
<span class="money-display__unit">
<img src="/assets/ui/currency/crown.png" alt="" />
{{ money.crowns }}
</span>
}
@if (money.talers > 0n) {
<span class="money-display__unit">
<img src="/assets/ui/currency/taler.png" alt="" />
{{ money.talers }}
</span>
}
@if (money.shards > 0n || isZero) {
<span class="money-display__unit">
<img src="/assets/ui/currency/shard.png" alt="" />
{{ money.shards }}
</span>
}
</div>
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:
[Krone] 0 [Taler] 3 [Scherbe] 47
Wenn eine Einheit null ist, kann sie optional ausgeblendet werden.
Frühes Spiel:
[Taler] 2 [Scherbe] 18
35. Händleransicht
Preise müssen über dieselbe zentrale Komponente dargestellt werden.
Beispiel:
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:
1 Krone 42 Taler 65 Scherben
37. Preisfarben
Geld selbst erhält keine Seltenheitsfarbe.
Bei einem kaufbaren Angebot:
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:
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:
100 Scherben in 1 Taler wechseln
oder:
1 Krone aufbrechen
Das System rechnet automatisch.
Beispiel:
Spieler besitzt:
1 Krone
Kauft Item für:
20 Scherben
Danach besitzt er:
99 Taler 80 Scherben
Intern:
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:
Geld
+ Ruf
+ Weltruhm
Falls bestimmte Gegner später bewusst direkt Geld tragen sollen, bleibt das technisch möglich.
Beispiel:
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:
{
"moneyShards": 250
}
UI:
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:
silver
wird es migriert zu:
money_shards
Beispielmigration:
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:
sell_price
shop_price
quest_reward
monster_reward
sollen diese ebenfalls explizit auf Scherben umbenannt werden.
Beispiele:
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:
silver
silverAmount
silverReward
silverPrice
Ausnahme:
historische Migrationen.
47. Migration von bestehendem Content
Für jeden bestehenden Silberwert gilt initial:
newMoneyShards = oldSilver
Beispiel:
oldSilver = 180
wird:
moneyShards = 180
UI:
1 Taler 80 Scherben
48. Content-Schema
Empfohlene Felder:
Monster
Falls direkte Geldbelohnungen erlaubt sind:
moneyMinShards
moneyMaxShards
Nicht:
silverMin
silverMax
Item
sellPriceShards
ShopOffer
priceShards
QuestReward
moneyShards
TrophyExchange
moneyRewardShards
regionalReputationReward
globalRenownReward
49. Serverautorität
Der Client darf niemals an den Server senden:
{
"price": 150
}
oder:
{
"reward": 250
}
Ein Händlerkauf sendet ausschließlich die fachliche Auswahl.
Beispiel:
{
"shopOfferId": "uuid"
}
Der Server lädt anschließend:
Preis
Anforderungen
Item
Rufvoraussetzung
aus seinen eigenen Daten.
50. Beispiel Shop API
Request
POST /api/shops/:shopId/purchases
{
"offerId": "uuid"
}
Response
{
"purchasedItem": {
"itemDefinitionId": "uuid",
"quantity": 1
},
"moneyShards": "842",
"spentShards": "140"
}
Frontend:
Verbleibend:
8 Taler 42 Scherben
51. Beispiel Trophy Exchange API
Request
POST /api/npcs/:npcId/exchanges
{
"exchangeId": "raider-insignia-turn-in",
"quantity": 5
}
Server
prüft:
NPC gültig
Exchange gültig
Spieler besitzt benötigte Trophäen
Menge erlaubt
Rufbedingungen erfüllt
berechnet:
Geld
Ruf
Weltruhm
und führt alles atomar aus.
52. Beispiel Response
{
"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:
{
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:
+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:
id
characterId
amountDeltaShards
balanceAfterShards
reason
referenceType
referenceId
createdAt
Beispiele für reason:
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:
Krone: 0
Taler: 2
Scherben: 35
Gespeichert wird:
235
57. Tests – Money Utility
Pflichttests:
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:
Geld hinzufügen
Geld abziehen
genügend Geld
zu wenig Geld
Nullbetrag
negative Eingaben werden abgelehnt
kein negativer Kontostand
59. Tests – Händlerkauf
Mindestens:
Spieler hat genug Geld
→ Geld wird abgezogen
→ Item wird vergeben
Spieler hat zu wenig Geld
→ Fehler
→ Geld bleibt unverändert
→ kein Item
Itemgrant schlägt fehl
→ Transaktion rollback
→ Geld bleibt unverändert
60. Tests – parallele Käufe
Ein Integrationstest soll sicherstellen:
Spieler besitzt:
100 Scherben
Zwei parallele Requests versuchen jeweils:
80 Scherben
auszugeben.
Erwartung:
genau ein Kauf erfolgreich
und niemals:
-60 Scherben
61. Tests – Trophäenabgabe
Test:
5 Raider Insignias vorhanden
Abgabe:
5
Erwartung:
Items entfernt
Geld erhöht
regionaler Ruf erhöht
Weltruhm erhöht
Alles in einer Transaktion.
62. Frontend Tests
Mindestens:
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:
currency.shard
currency.shard_plural
currency.taler
currency.taler_plural
currency.crown
currency.crown_plural
Die deutsche Designbezeichnung bleibt:
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:
"1 Taler 35 Scherben"
Backend:
135
Frontend / Localization:
1 Taler 35 Scherben
Dadurch bleibt das System übersetzbar.
65. Empfohlene Modulstruktur
Backend:
apps/api/src/economy/
├── economy.module.ts
├── money.service.ts
├── money.service.spec.ts
├── types/
│ └── money.types.ts
└── errors/
└── insufficient-funds.error.ts
Frontend:
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:
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:
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
moneyShardsumbenennen. - PostgreSQL-Typ auf BIGINT setzen.
- bestehenden Wert 1:1 übernehmen.
- Migration testen.
Task 3 – Content-Migration
Alle bisherigen Felder prüfen:
silverMinsilverMaxsellPrice- 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:
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:
100 Scherben = 1 Taler
100 Taler = 1 Krone
Die Datenbank speichert ausschließlich:
moneyShards
Gebietswährungen bleiben separat.
Ruf und Weltruhm bleiben separat.
Trophäen können bei Händlern oder NPCs in:
Geld
+ regionalen Ruf
+ Weltruhm
umgewandelt werden.
Bestehende Silberwerte werden zunächst ohne Rebalancing migriert:
1 Silber = 1 Scherbe
Die wichtigste technische Regel lautet:
Der Server speichert und berechnet nur Scherben. Die UI macht daraus Scherben, Taler und Kronen.