update
Tests / headless (push) Waiting to run
Tests / fenster-schnell (push) Waiting to run
Tests / fenster-voll (push) Skipped
Tests / balance (push) Skipped

This commit is contained in:
team3 committed 2026-10-15 17:16:01 +02:00
1 parent 1d563b9eec
commit 04fe9716e8
22 files changed
+764 -151

No files matched your search

+66 -11
View File
@@ -33,6 +33,7 @@ const DUST_UPGRADE := {"Rare": 100, "Super Rare": 200, "Ultra Rare": 500, "Ultim
var coins := 0
var dust := 0
var xp := 0 # Erfahrung: alle je verdienten Münzen (Skills.level_for)
var customs: Array = [] # Eigenbauten aus der Werkstatt (Card.to_dict), beim Laden registriert
var skills := {} # Skill-ID -> gelernte Stufe (Skills.SKILLS)
var collection := {} # eigene Karten: id -> Seltenheitsstufe (Index in Cards.RARITIES)
var deck: Array = [] # Karten-IDs des eigenen Stapels (Game.DECK_MIN bis DECK_MAX)
@@ -84,6 +85,11 @@ static func from_dict(d: Dictionary) -> Profile:
if Skills.SKILLS.has(id):
p.skills[id] = clampi(int(sk[id]), 0, int(Skills.SKILLS[id].max))
p.campaign_stage = int(d.get("campaign_stage", 0))
for cd in d.get("customs", []):
if cd is Dictionary and cd.has("id") and str(cd.id).begins_with("c_"):
var c := Cards.Card.from_dict(cd)
Cards.register(c)
p.customs.append(c.to_dict())
if int(d.get("version", 1)) >= SAVE_VERSION:
var coll = d.get("collection", {})
for cid in coll:
@@ -110,7 +116,7 @@ static func from_dict(d: Dictionary) -> Profile:
func to_dict() -> Dictionary:
return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "xp": xp, "skills": skills, "collection": collection,
return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "xp": xp, "skills": skills, "customs": customs, "collection": collection,
"deck": deck, "campaign_stage": campaign_stage, "settings": settings, "perk": perk}
@@ -225,16 +231,17 @@ func booster_unlocked(element: String) -> bool:
return campaign_stage >= Campaign.STAGES_PER_CHAPTER * Campaign.CHAPTER_ELEMENTS.find(element)
## Fünf Karten des Elements mit gewürfelter Seltenheit; luck (Packglück) verschiebt die Chancen.
static func roll_booster(r: RandomNumberGenerator, element: String, luck: int = 0) -> Array:
## Fünf Karten des Elements mit gewürfelter Seltenheit; luck (Packglück) verschiebt die Chancen,
## mindestens eine Karte hat die Seltenheit floor (Rare, mit Garantie Super Rare).
static func roll_booster(r: RandomNumberGenerator, element: String, luck: int = 0, floor: String = "Rare") -> Array:
var rarities := RARITY_WEIGHTS.keys()
var weights := Skills.pack_weights(luck)
var pool := Cards.all().filter(func(c): return c.elements[0] == element)
var out := []
for i in BOOSTER_SIZE:
var rar: String = rarities[r.rand_weighted(weights)]
if i == BOOSTER_SIZE - 1 and out.all(func(x): return x.rarity == "Common"):
rar = "Rare" # mindestens eine Rare pro Booster
if i == BOOSTER_SIZE - 1 and out.all(func(x): return Cards.RARITIES.find(x.rarity) < Cards.RARITIES.find(floor)):
rar = floor
out.append({"card": pool[r.randi_range(0, pool.size() - 1)], "rarity": rar})
return out
@@ -261,7 +268,7 @@ func open_boosters(element: String, n: int, r: RandomNumberGenerator = null) ->
func _open_one(element: String, r: RandomNumberGenerator) -> Array:
coins -= pack_price(element)
var out := roll_booster(r, element, skill("packglueck"))
var out := roll_booster(r, element, skill("packglueck"), Skills.pack_floor(skill("garantie")))
for res in out:
var lvl: int = Cards.RARITIES.find(res.rarity)
res["new"] = not owns(res.card.id)
@@ -330,10 +337,14 @@ func skill_points() -> int:
return player_level() - spent
## Lernt die nächste Stufe eines Skills für einen Punkt. Gibt "" zurück oder eine Fehlermeldung.
## Lernt die nächste Stufe eines Skills für einen Punkt; der Skill davor im Pfad braucht eine Stufe.
## Gibt "" zurück oder eine Fehlermeldung.
func learn(id: String) -> String:
if skill(id) >= int(Skills.SKILLS[id].max):
return "Schon auf der höchsten Stufe"
var prev := Skills.previous(id)
if prev != "" and skill(prev) == 0:
return "Erst den Skill davor lernen"
if skill_points() <= 0:
return "Kein Skillpunkt frei"
skills[id] = skill(id) + 1
@@ -379,15 +390,59 @@ func opponent_level() -> int:
return clampi(deck_level() + int(difficulty().level), 0, Cards.RARITIES.size() - 1)
## Elemente der offenen Kampagnenkapitel (Neutral immer).
## Elemente der offenen Kampagnenkapitel (Neutral immer, ab Kapitel 5 alle).
func unlocked_elements() -> Array:
return Campaign.CHAPTER_ELEMENTS.slice(0, campaign_stage / Campaign.STAGES_PER_CHAPTER + 1)
return Campaign.CHAPTER_ELEMENTS.slice(0, mini(Campaign.CHAPTER_ELEMENTS.size(), campaign_stage / Campaign.STAGES_PER_CHAPTER + 1))
## Alle Karten der offenen Elemente: was Sammlung, Deck und Booster zeigen.
## Alle Karten der offenen Elemente plus die eigenen Eigenbauten: was Sammlung und Deck zeigen.
func unlocked_cards() -> Array:
var open := unlocked_elements()
return Cards.all().filter(func(c): return c.elements[0] in open)
var out: Array = Cards.all().filter(func(c): return c.elements[0] in open)
for id in collection:
if Cards.has_id(id) and Cards.by_id(id).is_custom():
out.append(Cards.by_id(id))
return out
# --- Werkstatt ----------------------------------------------------------------------
## Die Werkstatt öffnet nach dem Boss von Kapitel 5.
func workshop_unlocked() -> bool:
return campaign_stage >= Campaign.CHAPTER_ELEMENTS.size() * Campaign.STAGES_PER_CHAPTER
## Preis eines Eigenbaus in Staub: der doppelte Kartenwert, plus 100 je weiterem Element.
func build_price(card: Cards.Card) -> int:
return 2 * card.value() + 100 * maxi(card.elements.size() - 1, 0)
## Baut eine Karte (Werkstatt): beliebig viele Elemente, Angriff 0 bis 10, Leben 1 bis 10, beliebig
## viele Eigenschaften, Kosten nach Cards.cost_with. Sie kommt als Common in Sammlung und Stapel.
## Gibt "" zurück oder eine Fehlermeldung.
func build_card(name: String, elements: Array, attack: int, health: int, keywords: Array) -> String:
if not workshop_unlocked():
return "Werkstatt noch gesperrt"
if name.strip_edges() == "":
return "Name fehlt"
if elements.is_empty():
return "Mindestens ein Element"
if attack < 0 or attack > 10 or health < 1 or health > 10:
return "Angriff 0 bis 10, Leben 1 bis 10"
var n := 1
for cd in customs:
n = maxi(n, int(str(cd.id).trim_prefix("c_")) + 1)
var c := Cards.custom("c_%d" % n, name.strip_edges(), elements, attack, health, keywords)
var price := build_price(c)
if dust < price:
return "Nicht genug Staub (%d nötig)" % price
dust -= price
Cards.register(c)
customs.append(c.to_dict())
collection[c.id] = 0
if deck.size() < Game.DECK_MAX:
deck.append(c.id)
return ""
## Einzelspieler-Gegner: ein Element aus den offenen Kapiteln, 30 Karten nach Manakurve in opponent_level;