update
This commit is contained in:
240
scripts/cards.gd
240
scripts/cards.gd
@@ -1,5 +1,5 @@
|
||||
class_name Cards
|
||||
## Elemente, Kombos und der Kartenpool.
|
||||
## Elemente, Kombonamen, Schlüsselwörter und der Kartenpool.
|
||||
|
||||
const ELEMENTS: Array[String] = ["Feuer", "Wasser", "Erde", "Luft"]
|
||||
const ELEMENT_COLORS := {
|
||||
@@ -8,8 +8,7 @@ const ELEMENT_COLORS := {
|
||||
"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.
|
||||
# Jede Karte hat genau ein Element. Kombos sind die Namen verschmolzener Wesen.
|
||||
const COMBOS := {
|
||||
"Feuer+Feuer": "Inferno",
|
||||
"Wasser+Wasser": "Tiefsee",
|
||||
@@ -22,26 +21,8 @@ const COMBOS := {
|
||||
"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 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 := {
|
||||
@@ -51,8 +32,10 @@ const RARITY_COLORS := {
|
||||
"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"]
|
||||
# Schlüsselwörter haben Karten nicht von Haus aus; sie werden für Mana dazugekauft (Game.upgrade).
|
||||
const KEYWORDS: Array[String] = ["Schild", "Doppelschlag"]
|
||||
const KEYWORD_COST := {"Schild": 3, "Doppelschlag": 4}
|
||||
const KEYWORD_TEXT := {"Schild": "Blockt den ersten Schaden", "Doppelschlag": "Greift zweimal je Zug"}
|
||||
const DATA_PATH := "res://data/karten.json"
|
||||
|
||||
|
||||
@@ -70,20 +53,49 @@ static func combo_name(elements: Array) -> String:
|
||||
|
||||
|
||||
## Elementvorteil aus Sicht von own: +1 im Vorteil, -1 im Nachteil, sonst 0.
|
||||
## Mehrfachelemente nehmen den für sie besseren Wert.
|
||||
## 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:
|
||||
for e in own:
|
||||
if BEATS.get(e, "") in other:
|
||||
return 1
|
||||
for e in other:
|
||||
if BEATS.get(e, "") in own:
|
||||
return -1
|
||||
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
|
||||
|
||||
|
||||
## 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)
|
||||
## 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
|
||||
|
||||
|
||||
## Welches Element ein Element schlägt (und von welchem es geschlagen wird).
|
||||
static func beaten_by(element: String) -> String:
|
||||
for e in BEATS:
|
||||
if BEATS[e] == element:
|
||||
return e
|
||||
return ""
|
||||
|
||||
|
||||
## Kosten einer Karte: Angriff plus Leben, halbiert und aufgerundet.
|
||||
static func cost_of(attack: int, health: int) -> int:
|
||||
return ceili((attack + health) / 2.0)
|
||||
|
||||
|
||||
## Name des verschmolzenen Wesens: die Kombo seiner Elemente (ein Element zählt doppelt).
|
||||
@@ -91,8 +103,8 @@ 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).
|
||||
## Karte des verschmolzenen Wesens: Elemente und Schlüsselwörter beider Karten, die Werte
|
||||
## bestimmt Game.fuse, eine Verschmelzung mehr als der erfahrenere Elternteil.
|
||||
static func fused(a: Card, b: Card, attack: int, health: int) -> Card:
|
||||
var els := []
|
||||
for e in a.elements + b.elements:
|
||||
@@ -103,99 +115,27 @@ static func fused(a: Card, b: Card, attack: int, health: int) -> Card:
|
||||
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
|
||||
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
|
||||
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 den Gegner") % n
|
||||
"all_enemies":
|
||||
return tr("%s Schaden an den Gegner und seine 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 ""
|
||||
## 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:
|
||||
@@ -203,19 +143,18 @@ class Card extends RefCounted:
|
||||
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 = []
|
||||
var cost: int = 0 # abgeleitet: (Angriff + Leben) / 2, aufgerundet
|
||||
var keywords: Array = [] # dazugekauft, je Exemplar
|
||||
var fusions := 0 # wie oft verschmolzen (Verschmelzen kostet 1, 2, 4, 8 …)
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
var d := {"id": id, "name": name, "name_en": name_en, "elements": elements, "cost": cost,
|
||||
"attack": attack, "health": health}
|
||||
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 not effects.is_empty():
|
||||
d["effects"] = effects.map(func(e): return e.to_dict())
|
||||
if fusions > 0:
|
||||
d["fusions"] = fusions
|
||||
return d
|
||||
|
||||
static func from_dict(d: Dictionary) -> Card:
|
||||
@@ -224,14 +163,17 @@ class Card extends RefCounted:
|
||||
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.cost = Cards.cost_of(c.attack, c.health)
|
||||
c.keywords = Array(d.get("keywords", []))
|
||||
for e in d.get("effects", []):
|
||||
c.effects.append(Effect.from_dict(e))
|
||||
c.fusions = int(d.get("fusions", 0))
|
||||
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"):
|
||||
@@ -244,35 +186,19 @@ class Card extends RefCounted:
|
||||
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 ""
|
||||
|
||||
## Nur die Effekte (die Karte zeigt Schlüsselwörter als Symbole).
|
||||
func effect_text() -> String:
|
||||
if effects.is_empty():
|
||||
return ""
|
||||
var fx: Array[String] = []
|
||||
for f in effects:
|
||||
fx.append(f.text())
|
||||
return ", ".join(fx) + "."
|
||||
## Mana für die nächste Verschmelzung mit dieser Karte.
|
||||
func fusion_cost() -> int:
|
||||
return 1 << fusions
|
||||
|
||||
## Kartentext: die gekauften Schlüsselwörter.
|
||||
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)
|
||||
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 = []
|
||||
|
||||
Reference in New Issue
Block a user