update
Some checks are pending
Tests / headless (push) Waiting to run
Tests / fenster-schnell (push) Waiting to run
Tests / fenster-voll (push) Waiting to run
Tests / balance (push) Waiting to run

This commit is contained in:
team3
2026-10-13 15:39:55 +02:00
parent b91f475a6e
commit f2c609a08b
25 changed files with 1338 additions and 776 deletions

View File

@@ -2,14 +2,24 @@ class_name Cards
## Elemente, Kombonamen, Schlüsselwörter und der Kartenpool.
const ELEMENTS: Array[String] = ["Feuer", "Wasser", "Erde", "Luft"]
# Neutral ist kein Element: nie im Vorteil oder Nachteil, zählt nicht für Paare und Zähler.
# Verschmolzen mit einem Element nimmt das Wesen im schlechten Matchup die neutrale Seite.
const NEUTRAL := "Neutral"
const NEUTRAL_TEXT := "Nie im Vorteil oder Nachteil"
const ELEMENT_COLORS := {
"Feuer": Color("be3c23"),
"Wasser": Color("2d64be"),
"Erde": Color("73552d"),
"Luft": Color("78aac8"),
"Neutral": Color("6e7382"),
}
# Jede Karte hat genau ein Element. Kombos sind die Namen verschmolzener Wesen.
# Jede Karte hat genau ein Element (oder ist neutral). Kombos sind die Namen verschmolzener Wesen.
const COMBOS := {
"Neutral+Neutral": "Veteran",
"Feuer+Neutral": "Feuerritter",
"Wasser+Neutral": "Wasserritter",
"Erde+Neutral": "Erdritter",
"Luft+Neutral": "Luftritter",
"Feuer+Feuer": "Inferno",
"Wasser+Wasser": "Tiefsee",
"Erde+Erde": "Berg",
@@ -47,9 +57,15 @@ const ELEMENT_KEYWORD := {"Erde": "Spott", "Luft": "Flink", "Wasser": "Regenerat
const DATA_PATH := "res://data/karten.json"
## Reihenfolge der Elemente in Namen und Darstellung: wie ELEMENTS, Neutral zuletzt.
static func element_order(e: String) -> int:
var i := ELEMENTS.find(e)
return i if i >= 0 else ELEMENTS.size()
static func combo_key(elements: Array) -> String:
var sorted := elements.duplicate()
sorted.sort_custom(func(a, b): return ELEMENTS.find(a) < ELEMENTS.find(b))
sorted.sort_custom(func(a, b): return element_order(a) < element_order(b))
return "+".join(sorted)
@@ -98,11 +114,10 @@ static func cost_of(attack: int, health: int) -> int:
return mini(10, (2 * attack + health) / 3)
## Wert eines verschmolzenen Wesens: der bessere Wert plus ein Drittel des schwächeren (mindestens 1),
## aufgerundet. Bei gleichen Werten sind das zwei Drittel der Summe, sonst mehr; nie schlechter
## als das stärkere Wesen, und jeder Partner bringt mindestens +1, auch mit 0.
## Wert eines verschmolzenen Wesens aus den Grundwerten der Eltern: die aufgerundete Hälfte der
## Summe plus eins. Ein starker und ein schwacher Partner ergeben also weniger als der starke.
static func fusion_value(a: int, b: int) -> int:
return maxi(a, b) + maxi(1, ceili(mini(a, b) / 3.0))
return ceili((a + b) / 2.0) + 1
## Name des verschmolzenen Wesens: die Kombo seiner Elemente (ein Element zählt doppelt).
@@ -110,18 +125,18 @@ static func fusion_name(elements: Array) -> String:
return combo_name(elements if elements.size() >= 2 else [elements[0], elements[0]])
## Karte des verschmolzenen Wesens: Elemente und Eigenschaften beider Karten, Werte nach
## fusion_value (oder die übergebenen), eine Verschmelzung mehr als der erfahrenere Elternteil.
static func fused(a: Card, b: Card, attack: int = -1, health: int = -1) -> Card:
if attack < 0:
attack = fusion_value(a.attack, b.attack)
if health < 0:
health = fusion_value(a.health, b.health)
## Verschmelzung zweier Karten: Elemente (sortiert) und Eigenschaften beider, Angriff und Leben
## nach fusion_value aus den Grundwerten, Kosten daraus wie bei jeder Karte; dann die Stufe level
## mit ihrem +1/+1 (ohne Angabe die höhere der Eltern). Das Bild trägt der stärkere Elternteil
## (Angriff plus Leben).
static func fused(a: Card, b: Card, level: int = -1) -> Card:
var attack := fusion_value(a.base_attack(), b.base_attack())
var health := fusion_value(a.base_health(), b.base_health())
var els := []
for e in a.elements + b.elements:
if not (e in els):
els.append(e)
els.sort_custom(func(x, y): return ELEMENTS.find(x) < ELEMENTS.find(y))
els.sort_custom(func(x, y): return element_order(x) < element_order(y))
var c := Card.new()
c.id = "fus_%s_%s" % [a.id, b.id]
c.name = fusion_name(els)
@@ -132,8 +147,9 @@ static func fused(a: Card, b: Card, attack: int = -1, health: int = -1) -> Card:
for k in KEYWORDS:
if k in a.keywords or k in b.keywords:
c.keywords.append(k)
c.look = maxi(a.look, b.look) # die höhere Stufe gewinnt
return c
var strong := a if a.attack + a.health >= b.attack + b.health else b
c.art = strong.art_card().id
return c.leveled(maxi(level, maxi(maxi(a.look, b.look), 0)))
## Platzhalter für ein verdecktes gegnerisches Wesen: unbekanntes Element, geschätzte Werte.
@@ -159,6 +175,7 @@ class Card extends RefCounted:
var cost: int = 0 # abgeleitet: (2 · Angriff + Leben) / 3, abgerundet, höchstens 10 (aus den Grundwerten)
var keywords: Array = [] # feste Eigenschaften der Karte (Elementpaare geben ihre zur Laufzeit)
var look := -1 # Seltenheitsstufe des Exemplars (gibt +1/+1 je Stufe); -1: Grundkarte aus dem Pool
var art := "" # verschmolzen: Karte, deren Bild das Wesen trägt (der stärkere Elternteil)
func to_dict() -> Dictionary:
var d := {"id": id, "name": name, "name_en": name_en, "elements": elements, "attack": attack, "health": health}
@@ -166,10 +183,24 @@ class Card extends RefCounted:
d["keywords"] = keywords
if look >= 0:
d["look"] = look
if art != "":
d["art"] = art
if cost != Cards.cost_of(attack, health):
d["cost"] = cost
return d
## Wert der Karte: was sie über ihre Manakosten hinaus bringt, mal 100. Grundkarten liegen
## bei 100, jede Seltenheitsstufe gibt 100 dazu (Ultimate Rare 500); Fusionen aus ihren Werten.
func value() -> int:
return (Cards.cost_of(attack, health) - cost + 1) * 100
## Grundwerte ohne den Stufenbonus.
func base_attack() -> int:
return attack - maxi(look, 0)
func base_health() -> int:
return health - maxi(look, 0)
## Stufe dieser Karte für einen Besitzer mit dieser Sammlung (Game.Player.looks).
func rarity_in(looks: Dictionary) -> String:
return Cards.RARITIES[look if look >= 0 else int(looks.get(id, 0))]
@@ -185,8 +216,13 @@ class Card extends RefCounted:
c.cost = int(d.get("cost", Cards.cost_of(c.attack, c.health)))
c.keywords = Array(d.get("keywords", []))
c.look = int(d.get("look", -1))
c.art = str(d.get("art", ""))
return c
## Karte, deren Bild gezeichnet wird: bei Verschmelzungen der stärkere Elternteil, sonst sie selbst.
func art_card() -> Card:
return Cards.by_id(art) if art != "" and Cards._by_id.has(art) else self
## Eigenes Exemplar (Hand, Brett): Schlüsselwörter gehören dem Exemplar, nicht dem Pool.
func copy() -> Card:
return Card.from_dict(to_dict())
@@ -212,14 +248,19 @@ class Card extends RefCounted:
func combo() -> String:
return Cards.combo_name(elements)
## Kartentext: die festen Eigenschaften.
func is_neutral() -> bool:
return Cards.NEUTRAL in elements
## Kartentext: Neutral und die festen Eigenschaften.
func text() -> String:
if keywords.is_empty():
return ""
var kws: Array[String] = []
if is_neutral():
kws.append(tr(Cards.NEUTRAL))
for k in Cards.KEYWORDS:
if k in keywords:
kws.append(tr(k))
if kws.is_empty():
return ""
return ", ".join(kws) + "."