Files
cards/scripts/cards.gd
Team3 0a612f91b3
Some checks failed
Tests / headless (push) Has been cancelled
Tests / fenster-schnell (push) Has been cancelled
Tests / fenster-voll (push) Has been cancelled
Tests / balance (push) Has been cancelled
update
2026-10-04 22:40:18 +02:00

259 lines
8.9 KiB
GDScript

class_name Cards
## Elemente, Kombonamen, Schlüsselwörter und der Kartenpool.
const ELEMENTS: Array[String] = ["Feuer", "Wasser", "Erde", "Luft"]
const ELEMENT_COLORS := {
"Feuer": Color("be3c23"),
"Wasser": Color("2d64be"),
"Erde": Color("73552d"),
"Luft": Color("78aac8"),
}
# Jede Karte hat genau ein Element. Kombos sind die Namen verschmolzener Wesen.
const COMBOS := {
"Feuer+Feuer": "Inferno",
"Wasser+Wasser": "Tiefsee",
"Erde+Erde": "Berg",
"Luft+Luft": "Orkan",
"Feuer+Wasser": "Dampf",
"Feuer+Erde": "Lava",
"Feuer+Luft": "Blitz",
"Wasser+Erde": "Schlamm",
"Wasser+Luft": "Sturm",
"Erde+Luft": "Sand",
}
# Elementkreis: Wasser löscht Feuer, Feuer verbrennt Luft, Luft trägt Erde ab, Erde saugt Wasser auf.
const BEATS := {"Wasser": "Feuer", "Feuer": "Luft", "Luft": "Erde", "Erde": "Wasser"}
# Seltenheit ist reine Optik: jede Karte gibt es in jeder Stufe, die Werte bleiben gleich.
# Stufen wie bei Yu-Gi-Oh: Common grau, Rare silber, Super Rare blau, Ultra Rare gold,
# Ultimate Rare schillert durch alle Farben.
const RARITIES: Array[String] = ["Common", "Rare", "Super Rare", "Ultra Rare", "Ultimate Rare"]
const RARITY_COLORS := {
"Common": Color("aaaaaa"),
"Rare": Color("c8d0dc"),
"Super Rare": Color("4682e6"),
"Ultra Rare": Color("f0aa28"),
"Ultimate Rare": Color("ff5a8c"),
}
# Eigenschaften haben Karten nicht von Haus aus; sie werden für Mana dazugekauft (Game.upgrade)
# oder kommen vom Elementpaar der eigenen Seite (ELEMENT_KEYWORD, siehe Game.Creature.has).
const KEYWORDS: Array[String] = ["Spott", "Flink", "Regeneration", "Doppelschlag"]
const KEYWORD_COST := {"Spott": 2, "Flink": 2, "Regeneration": 3, "Doppelschlag": 4}
const KEYWORD_TEXT := {
"Spott": "Muss zuerst angegriffen werden",
"Flink": "Greift sofort an",
"Regeneration": "Heilt sich am Zugende ganz",
"Doppelschlag": "Greift zweimal je Zug",
}
const ELEMENT_KEYWORD := {"Erde": "Spott", "Luft": "Flink", "Wasser": "Regeneration", "Feuer": "Doppelschlag"}
const DATA_PATH := "res://data/karten.json"
static func combo_key(elements: Array) -> String:
var sorted := elements.duplicate()
sorted.sort_custom(func(a, b): return ELEMENTS.find(a) < ELEMENTS.find(b))
return "+".join(sorted)
## Name der Elementkombination eines verschmolzenen Wesens; drei oder vier Elemente sind eine Chimäre.
static func combo_name(elements: Array) -> String:
if elements.size() == 1:
return elements[0]
return COMBOS.get(combo_key(elements), "Chimäre")
## Elementvorteil aus Sicht von own: +1 im Vorteil, -1 im Nachteil, sonst 0.
## Hat ein Wesen mehrere Elemente, kämpft es mit dem, das gegen die Gegenseite am besten steht.
static func matchup(own: Array, other: Array) -> int:
var mine := best_element(own, other)
var theirs := best_element(other, own)
if mine == "" or theirs == "":
return 0
return duel(mine, theirs)
## +1, wenn a b schlägt, -1, wenn b a schlägt, sonst 0.
static func duel(a: String, b: String) -> int:
if BEATS.get(a, "") == b:
return 1
if BEATS.get(b, "") == a:
return -1
return 0
## Das Element aus own, das gegen die Elemente in other am besten steht.
static func best_element(own: Array, other: Array) -> String:
var best := ""
var best_score := -9
for e in own:
var score := 0
for o in other:
score += duel(e, o)
if score > best_score:
best = e
best_score = score
return best
## Kosten einer Karte: zwei Drittel Angriff plus ein Drittel Leben, abgerundet (0/2 kostet nichts).
static func cost_of(attack: int, health: int) -> int:
return (2 * attack + health) / 3
## Wert eines verschmolzenen Wesens: der bessere Wert plus ein Drittel des schwächeren,
## aufgerundet. Bei gleichen Werten sind das zwei Drittel der Summe, sonst mehr; nie schlechter
## als das stärkere Wesen, und jeder Partner ab 1 bringt mindestens +1.
static func fusion_value(a: int, b: int) -> int:
return maxi(a, b) + ceili(mini(a, b) / 3.0)
## Name des verschmolzenen Wesens: die Kombo seiner Elemente (ein Element zählt doppelt).
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)
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))
var c := Card.new()
c.id = "fus_%s_%s" % [a.id, b.id]
c.name = fusion_name(els)
c.elements = els
c.attack = attack
c.health = health
c.cost = cost_of(attack, health)
for k in KEYWORDS:
if k in a.keywords or k in b.keywords:
c.keywords.append(k)
c.fusions = maxi(a.fusions, b.fusions) + 1
c.look = maxi(a.look, b.look) # die höhere Stufe gewinnt; Game.fuse rechnet die Sammlung ein
return c
## Platzhalter für ein verdecktes gegnerisches Wesen: unbekanntes Element, geschätzte Werte.
static func unknown() -> Card:
var c := Card.new()
c.id = "?"
c.name = "Verdeckt"
c.name_en = "Face-down"
c.elements = []
c.attack = 2
c.health = 3
c.cost = cost_of(2, 3)
return c
class Card extends RefCounted:
var id: String
var name: String # deutscher Name (auch im Verlauf und in Tests)
var name_en := "" # englischer Name
var elements: Array
var attack: int = 0
var health: int = 0
var cost: int = 0 # abgeleitet: (2 · Angriff + Leben) / 3, abgerundet, mindestens 1
var keywords: Array = [] # dazugekauft, je Exemplar
var fusions := 0 # wie oft verschmolzen (Verschmelzen kostet 1, 2, 4, 8 …)
var look := -1 # Seltenheitsstufe verschmolzener Karten (die höhere der Eltern); -1: aus der Sammlung des Besitzers
func to_dict() -> Dictionary:
var d := {"id": id, "name": name, "name_en": name_en, "elements": elements, "attack": attack, "health": health}
if not keywords.is_empty():
d["keywords"] = keywords
if fusions > 0:
d["fusions"] = fusions
if look >= 0:
d["look"] = look
return d
## 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))]
static func from_dict(d: Dictionary) -> Card:
var c := Card.new()
c.id = d.id
c.name = d.name
c.name_en = str(d.get("name_en", ""))
c.elements = Array(d.elements)
c.attack = int(d.get("attack", 0))
c.health = int(d.get("health", 0))
c.cost = Cards.cost_of(c.attack, c.health)
c.keywords = Array(d.get("keywords", []))
c.fusions = int(d.get("fusions", 0))
c.look = int(d.get("look", -1))
return c
## Eigenes Exemplar (Hand, Brett): Schlüsselwörter gehören dem Exemplar, nicht dem Pool.
func copy() -> Card:
return Card.from_dict(to_dict())
## Anzeigename in der aktuellen Sprache.
func title() -> String:
if name_en != "" and TranslationServer.get_locale().begins_with("en"):
return name_en
return name
func is_combo() -> bool:
return elements.size() >= 2
func combo() -> String:
return Cards.combo_name(elements)
## Mana für die nächste Verschmelzung mit dieser Karte.
func fusion_cost() -> int:
return 1 << fusions
## Kartentext: die gekauften Eigenschaften.
func text() -> String:
if keywords.is_empty():
return ""
var kws: Array[String] = []
for k in Cards.KEYWORDS:
if k in keywords:
kws.append(tr(k))
return ", ".join(kws) + "."
static var _all: Array = []
static var _by_id: Dictionary = {}
static func all() -> Array:
if _all.is_empty():
_build()
return _all
static func by_id(id: String) -> Card:
if _all.is_empty():
_build()
return _by_id[id]
static func has_id(id) -> bool:
if _all.is_empty():
_build()
return _by_id.has(id)
## Lädt den Kartenpool aus data/karten.json.
static func _build() -> void:
var json := JSON.new()
var ok := json.parse(FileAccess.get_file_as_string(DATA_PATH))
assert(ok == OK and json.data is Dictionary and json.data.has("karten"), "data/karten.json fehlt oder ist ungültig")
_all = []
_by_id = {}
for d in json.data.karten:
var c := Card.from_dict(d)
assert(not _by_id.has(c.id), "Karten-ID doppelt: " + c.id)
_by_id[c.id] = c
_all.append(c)