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 Karte hat genau ein Element. Kombos entstehen erst im Spiel: beim Verschmelzen # (der Name des neuen Wesens) und als Feldkombo zwischen benachbarten Elementen. 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", } # 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 das stärkste gegnerische Wesen", "Blitz": "2 Schaden an den schwächsten Gegner", "Schlamm": "Heile dich um 3", "Sturm": "Erhalte 1 Mana", "Sand": "Das neue Wesen erhält +1/+1", } # 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"} # Das Feld gehört allen am Tisch: jede gespielte Karte lädt ihre Elemente, bei FIELD_CHARGE kippt es. const FIELD_CHARGE := 4 const FIELD_NAMES := {"Feuer": "Vulkan", "Wasser": "Flut", "Erde": "Fels", "Luft": "Wirbel"} const FIELD_TEXT := { "Feuer": "Feuerwesen haben +1 Angriff", "Wasser": "Wasserwesen haben +1 Angriff", "Erde": "Erdwesen haben +1 Angriff", "Luft": "Luftwesen haben +1 Angriff", } # Seltenheit ist reine Optik: jede Karte gibt es in jeder Stufe, die Werte bleiben gleich. 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 CHOSEN_TARGETS: Array[String] = ["enemy_creature", "enemy_any", "friendly_creature"] 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. ## Mehrfachelemente nehmen den für sie besseren Wert. static func matchup(own: Array, other: Array) -> int: for e in own: if BEATS.get(e, "") in other: return 1 for e in other: if BEATS.get(e, "") in own: return -1 return 0 ## Mana für eine Verschmelzung: Durchschnitt der Kartenkosten, aufgerundet. static func fusion_cost(a: Card, b: Card) -> int: return ceili((a.cost + b.cost) / 2.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 Schlüsselwörter beider Karten, ## Werte kommen vom Spiel (Durchschnitt plus 1, siehe Game.fuse). static func fused(a: Card, b: Card, attack: int, health: int) -> Card: 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.cost = fusion_cost(a, b) c.attack = attack c.health = health for k in KEYWORDS: if k in a.keywords or k in b.keywords: c.keywords.append(k) return c static func field_combo_effect(name: String) -> Effect: match name: "Dampf": return Effect.new("freeze", "strongest_enemy_creature") "Lava": return Effect.new("damage", "strongest_enemy_creature", 1) "Blitz": return Effect.new("damage", "weakest_enemy_hero", 2) "Schlamm": return Effect.new("heal", "self_hero", 3) "Sturm": return Effect.new("mana", "none", 1) "Sand": return Effect.new("buff", "self_creature", 1, 1) return null class Effect extends RefCounted: var kind: String # damage, heal, mana, freeze, bounce, buff, shield var target: String # siehe CHOSEN_TARGETS und Game.resolve() var amount: int var amount2: int func _init(k: String, t: String, a: int = 0, a2: int = 0) -> void: kind = k target = t amount = a amount2 = a2 func to_dict() -> Dictionary: var d := {"kind": kind, "target": target} if amount != 0: d["amount"] = amount if amount2 != 0: d["amount2"] = amount2 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))) ## Kartentext in der aktuellen Sprache. func text() -> String: var n := str(amount) match kind: "damage": match target: "enemy_creature": return tr("%s Schaden an ein gegnerisches Wesen") % n "enemy_any": return tr("%s Schaden an ein Ziel") % n "all_enemy_creatures": return tr("%s Schaden an alle gegnerischen Wesen") % n "all_enemy_heroes": return tr("%s Schaden an alle Gegner") % n "all_enemies": return tr("%s Schaden an alle Gegner und ihre Wesen") % n "heal": return tr("Heile dich um %s") % n "mana": if amount == 1: return tr("Erhalte 1 Mana") return tr("Erhalte %s Mana") % n "freeze": if target == "enemy_creature": return tr("Friere ein gegnerisches Wesen ein") if target == "strongest_enemy_creature": return tr("Friere das stärkste gegnerische Wesen ein") return tr("Friere alle gegnerischen Wesen ein") "bounce": if target == "enemy_creature": return tr("Ein gegnerisches Wesen zurück auf die Hand") return tr("Alle gegnerischen Wesen zurück auf die Hand") "buff": var b := "+%d/+%d" % [amount, amount2] if target == "friendly_creature": return tr("Ein befreundetes Wesen erhält %s") % b return tr("Alle befreundeten Wesen erhalten %s") % b "shield": if target == "friendly_creature": return tr("Ein befreundetes Wesen erhält Schild") if target == "self_creature": return tr("Erhält Schild") return tr("Alle befreundeten Wesen erhalten Schild") return "" 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 cost: int var attack: int = 0 var health: int = 0 var keywords: Array = [] var effects: Array = [] func to_dict() -> Dictionary: var d := {"id": id, "name": name, "name_en": name_en, "elements": elements, "cost": cost, "attack": attack, "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()) return d 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.cost = int(d.cost) 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)) return c ## 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) 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(tr(k)) parts.append(", ".join(kws) + ".") if not effects.is_empty(): var fx: Array[String] = [] for f in effects: fx.append(f.text()) parts.append(tr("Beim Ausspielen: %s") % (", ".join(fx) + ".")) 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) ## 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)