Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
299 lines
10 KiB
GDScript
299 lines
10 KiB
GDScript
class_name Cards
|
|
## Elemente, Kombos 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 Paarung ist eine Kombo, auch gleiche Elemente.
|
|
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",
|
|
# Dreifach-Kombos: die mythische Stufe
|
|
"Feuer+Feuer+Feuer": "Sonne",
|
|
"Wasser+Wasser+Wasser": "Ozean",
|
|
"Erde+Erde+Erde": "Gebirge",
|
|
"Luft+Luft+Luft": "Himmel",
|
|
}
|
|
# Feldkombos: entstehen, wenn ein Wesen neben einem Wesen mit anderem Element ausgespielt wird.
|
|
const FIELD_COMBO_TEXT := {
|
|
"Dampf": "Friere das stärkste gegnerische Wesen ein",
|
|
"Lava": "1 Schaden an alle gegnerischen Wesen",
|
|
"Blitz": "2 Schaden an den schwächsten Gegner",
|
|
"Schlamm": "Heile dich um 3",
|
|
"Sturm": "Ziehe 1 Karte",
|
|
"Sand": "Das neue Wesen erhält Schild",
|
|
}
|
|
const RESONANCE_TEXT := "Resonanz: Ein Wesen bekommt Angriff für eigene Wesen mit gleichem Element: +1, +2, +3, +3 bei ein bis vier Partnern."
|
|
const RARITIES: Array[String] = ["gewöhnlich", "selten", "episch", "legendär", "mythisch"]
|
|
const RARITY_COLORS := {
|
|
"gewöhnlich": Color("aaaaaa"),
|
|
"selten": Color("4682e6"),
|
|
"episch": Color("aa46dc"),
|
|
"legendär": Color("f0aa28"),
|
|
"mythisch": Color("ff5a8c"),
|
|
}
|
|
const KEYWORDS: Array[String] = ["Wächter", "Flink", "Schild", "Doppelschlag"]
|
|
const KEYWORD_HELP := {
|
|
"Wächter": "Muss zuerst angegriffen werden.",
|
|
"Flink": "Kann sofort angreifen.",
|
|
"Schild": "Blockt den nächsten Schaden.",
|
|
"Doppelschlag": "Greift zweimal pro Zug an.",
|
|
}
|
|
const CHOSEN_TARGETS: Array[String] = ["enemy_creature", "enemy_any", "friendly_creature"]
|
|
# Näherung der Kartenstärke: Seltenheit plus Zuschläge für Schlüsselwörter und Effekte.
|
|
const RARITY_POINTS := {"gewöhnlich": 1.0, "selten": 2.0, "episch": 3.0, "legendär": 4.0, "mythisch": 5.0}
|
|
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)
|
|
|
|
|
|
static func combo_name(elements: Array) -> String:
|
|
if elements.size() == 1:
|
|
return elements[0]
|
|
return COMBOS[combo_key(elements)]
|
|
|
|
|
|
static func field_combo_effect(name: String) -> Effect:
|
|
match name:
|
|
"Dampf":
|
|
return Effect.new("freeze", "strongest_enemy_creature")
|
|
"Lava":
|
|
return Effect.new("damage", "all_enemy_creatures", 1)
|
|
"Blitz":
|
|
return Effect.new("damage", "weakest_enemy_hero", 2)
|
|
"Schlamm":
|
|
return Effect.new("heal", "self_hero", 3)
|
|
"Sturm":
|
|
return Effect.new("draw", "none", 1)
|
|
"Sand":
|
|
return Effect.new("shield", "self_creature")
|
|
return null
|
|
|
|
|
|
const TRIGGER_TEXT := {
|
|
"attacked": "Wenn ein gegnerisches Wesen dich oder eines deiner Wesen angreift",
|
|
"creature_played": "Wenn ein Gegner ein Wesen ausspielt",
|
|
}
|
|
|
|
|
|
class Effect extends RefCounted:
|
|
var kind: String # damage, heal, draw, freeze, bounce, buff, shield, summon, cancel
|
|
var target: String # siehe CHOSEN_TARGETS und Game.resolve()
|
|
var amount: int
|
|
var amount2: int
|
|
var card_id: String # für summon
|
|
|
|
func _init(k: String, t: String, a: int = 0, a2: int = 0, cid: String = "") -> void:
|
|
kind = k
|
|
target = t
|
|
amount = a
|
|
amount2 = a2
|
|
card_id = cid
|
|
|
|
func to_dict() -> Dictionary:
|
|
var d := {"kind": kind, "target": target}
|
|
if amount != 0:
|
|
d["amount"] = amount
|
|
if amount2 != 0:
|
|
d["amount2"] = amount2
|
|
if card_id != "":
|
|
d["card_id"] = card_id
|
|
return d
|
|
|
|
static func from_dict(d: Dictionary) -> Effect:
|
|
return Effect.new(d.kind, d.get("target", "none"), int(d.get("amount", 0)), int(d.get("amount2", 0)), str(d.get("card_id", "")))
|
|
|
|
func text() -> String:
|
|
var n := str(amount)
|
|
if target == "trigger_source":
|
|
match kind:
|
|
"damage":
|
|
return n + " Schaden an den Angreifer"
|
|
"freeze":
|
|
return "Der Angreifer wird eingefroren"
|
|
"bounce":
|
|
return "Es geht zurück auf die Hand"
|
|
match kind:
|
|
"cancel":
|
|
return "Der Angriff verpufft"
|
|
"summon":
|
|
return "Beschwöre einen " + Cards.by_id(card_id).name
|
|
"damage":
|
|
match target:
|
|
"enemy_creature":
|
|
return n + " Schaden an ein gegnerisches Wesen"
|
|
"enemy_any":
|
|
return n + " Schaden an ein Ziel"
|
|
"all_enemy_creatures":
|
|
return n + " Schaden an alle gegnerischen Wesen"
|
|
"all_enemy_heroes":
|
|
return n + " Schaden an alle Gegner"
|
|
"all_enemies":
|
|
return n + " Schaden an alle Gegner und ihre Wesen"
|
|
"heal":
|
|
return "Heile dich um " + n
|
|
"draw":
|
|
return "Ziehe 1 Karte" if amount == 1 else "Ziehe " + n + " Karten"
|
|
"freeze":
|
|
if target == "enemy_creature":
|
|
return "Friere ein gegnerisches Wesen ein"
|
|
return "Friere alle gegnerischen Wesen ein"
|
|
"bounce":
|
|
if target == "enemy_creature":
|
|
return "Ein gegnerisches Wesen zurück auf die Hand"
|
|
return "Alle gegnerischen Wesen zurück auf die Hand"
|
|
"buff":
|
|
var b := "+%d/+%d" % [amount, amount2]
|
|
if target == "friendly_creature":
|
|
return "Ein befreundetes Wesen erhält " + b
|
|
return "Alle befreundeten Wesen erhalten " + b
|
|
"shield":
|
|
if target == "friendly_creature":
|
|
return "Ein befreundetes Wesen erhält Schild"
|
|
if target == "self_creature":
|
|
return "Erhält Schild"
|
|
return "Alle befreundeten Wesen erhalten Schild"
|
|
return ""
|
|
|
|
|
|
class Card extends RefCounted:
|
|
var id: String
|
|
var name: String
|
|
var elements: Array
|
|
var cost: int
|
|
var rarity: String
|
|
var kind: String # "Wesen", "Zauber" oder "Falle"
|
|
var attack: int = 0
|
|
var health: int = 0
|
|
var keywords: Array = []
|
|
var effects: Array = []
|
|
var trigger: String = "" # nur Fallen: attacked, creature_played
|
|
|
|
func is_creature() -> bool:
|
|
return kind == "Wesen"
|
|
|
|
func is_trap() -> bool:
|
|
return kind == "Falle"
|
|
|
|
func to_dict() -> Dictionary:
|
|
var d := {"id": id, "name": name, "elements": elements, "cost": cost, "rarity": rarity, "kind": kind}
|
|
if is_creature():
|
|
d["attack"] = attack
|
|
d["health"] = health
|
|
if not keywords.is_empty():
|
|
d["keywords"] = keywords
|
|
if not effects.is_empty():
|
|
d["effects"] = effects.map(func(e): return e.to_dict())
|
|
if trigger != "":
|
|
d["trigger"] = trigger
|
|
return d
|
|
|
|
static func from_dict(d: Dictionary) -> Card:
|
|
var c := Card.new()
|
|
c.id = d.id
|
|
c.name = d.name
|
|
c.elements = Array(d.elements)
|
|
c.cost = int(d.cost)
|
|
c.rarity = d.rarity
|
|
c.kind = d.kind
|
|
c.attack = int(d.get("attack", 0))
|
|
c.health = int(d.get("health", 0))
|
|
c.keywords = Array(d.get("keywords", []))
|
|
for e in d.get("effects", []):
|
|
c.effects.append(Effect.from_dict(e))
|
|
c.trigger = str(d.get("trigger", ""))
|
|
return c
|
|
|
|
func is_combo() -> bool:
|
|
return elements.size() >= 2
|
|
|
|
func combo() -> String:
|
|
return Cards.combo_name(elements)
|
|
|
|
func target_type() -> String:
|
|
for fx in effects:
|
|
if fx.target in Cards.CHOSEN_TARGETS:
|
|
return fx.target
|
|
return ""
|
|
|
|
func text() -> String:
|
|
var parts: Array[String] = []
|
|
if not keywords.is_empty():
|
|
var kws: Array[String] = []
|
|
for k in Cards.KEYWORDS:
|
|
if k in keywords:
|
|
kws.append(k)
|
|
parts.append(", ".join(kws) + ".")
|
|
if not effects.is_empty():
|
|
var fx: Array[String] = []
|
|
for f in effects:
|
|
fx.append(f.text())
|
|
var s := ", ".join(fx) + "."
|
|
if is_trap():
|
|
if trigger == "creature_played":
|
|
s = s.replace("an den Angreifer", "an dieses Wesen").replace("Der Angreifer wird", "Es wird")
|
|
parts.append("Falle. %s: %s" % [Cards.TRIGGER_TEXT[trigger], s])
|
|
else:
|
|
parts.append(("Beim Ausspielen: " + s) if is_creature() else s)
|
|
return " ".join(parts)
|
|
|
|
|
|
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)
|
|
|
|
|
|
static func card_value(c: Card) -> float:
|
|
return RARITY_POINTS[c.rarity] + 0.3 * c.keywords.size() + 0.3 * c.effects.size()
|
|
|
|
|
|
static func of_rarity(r: String) -> Array:
|
|
return all().filter(func(c): return c.rarity == r)
|
|
|
|
|
|
## 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)
|