Files
ashen-realms/docs/Ashen_Realms_Waehrungssystem_Scherben_Taler_Kronen_Implementierung_V1.md
Bastian Wagner 5c479e1f93 art
2026-08-23 17:06:47 +02:00

28 KiB
Raw Blame History

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:

590 Scherben

Fortgeschrittenes Tier 1

Typische Beträge:

15 Taler

Tier 2

Typische Beträge:

325 Taler

Tier 3

Typische Beträge:

1580 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 5080 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 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:

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.