update
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

This commit is contained in:
Team3
2026-09-22 13:23:02 +02:00
parent 8944fdd017
commit dfcd3cb713
46 changed files with 1525 additions and 4141 deletions

View File

@@ -1,90 +1,39 @@
class_name Profile
extends RefCounted
## Spielstand: Sammlung, Münzen, Decks, Staub, Booster, Kartenstatistik (für Deckstärke und Schwierigkeit), Tagesaufgabe, Einstellungen.
## Spielstand: Münzen, Staub, Sammlung, Kampagne, Einstellungen.
## Jede Karte gehört jedem von Anfang an. Gesammelt wird die Seltenheitsstufe, und die
## ist reine Optik: sie ändert weder Werte noch Text einer Karte.
const SAVE_VERSION := 5
const PITY_BOOSTERS := 20 # danach ist eine mythische Karte garantiert
const SAVE_VERSION := 6
const SAVE_PATH := "user://spielstand.json"
const BOOSTER_COST := 100
const ELEMENT_BOOSTER_COST := 120 # nur Karten eines Elements (auch seine Kombos)
const CODE_PREFIX := "DINGER1:" # Deck-Code zum Teilen
const BOOSTER_SIZE := 5
const RARITY_WEIGHTS := {"gewöhnlich": 70.0, "selten": 20.0, "episch": 7.5, "legendär": 2.0, "mythisch": 0.5}
const START_COINS := 150
const REWARD_WIN := {1: 100, 2: 150, 3: 200, 4: 250, 5: 300}
const REWARD_LOSS := 50
const DECK_SLOTS := 5
# Schwierigkeit: Faktor auf die Zielstärke der Gegnerdecks, Belohnung und KI-Tiefe.
# Schwierigkeit: Leben der Gegner, KI und Münzen.
const DIFFICULTIES := {
"leicht": {"name": "Leicht", "factor": 0.9, "reward": 0.75, "smart": false, "tier": "leicht"},
"normal": {"name": "Normal", "factor": 1.0, "reward": 1.0, "smart": true, "tier": ""},
"schwer": {"name": "Schwer", "factor": 1.2, "reward": 1.5, "smart": true, "tier": "schwer"},
"leicht": {"name": "Leicht", "life": 25, "reward": 0.75, "smart": false},
"normal": {"name": "Normal", "life": 30, "reward": 1.0, "smart": true},
"schwer": {"name": "Schwer", "life": 35, "reward": 1.5, "smart": true},
}
# Sammlung: Zerlegen bringt Staub, Herstellen kostet Staub.
# Sammlung: eine Stufe abgeben bringt Staub, eine Stufe aufwerten kostet Staub.
const DUST_DISMANTLE := {"gewöhnlich": 5, "selten": 20, "episch": 100, "legendär": 400, "mythisch": 1600}
const DUST_CRAFT := {"gewöhnlich": 40, "selten": 100, "episch": 400, "legendär": 1600, "mythisch": 3200}
const DAILY_TASKS := [
{"id": "sieg", "kind": "win", "text": "Gewinne einen Kampf", "goal": 1, "reward": 80},
{"id": "kaempfe", "kind": "game", "text": "Bestreite zwei Kämpfe", "goal": 2, "reward": 60},
{"id": "booster", "kind": "booster", "text": "Öffne einen Booster", "goal": 1, "reward": 40},
{"id": "wesen", "kind": "creature_played", "text": "Spiele fünf Wesen", "goal": 5, "reward": 50},
{"id": "falle", "kind": "trap_set", "text": "Lege eine Falle", "goal": 1, "reward": 50},
{"id": "kombo", "kind": "combo", "text": "Löse zwei Feldkombos aus", "goal": 2, "reward": 60},
{"id": "herstellen", "kind": "craft", "text": "Stelle eine Karte her", "goal": 1, "reward": 50},
{"id": "tisch", "kind": "win_vs3", "text": "Gewinne gegen 3 oder mehr Gegner", "goal": 1, "reward": 120},
{"id": "feuer", "kind": "win_element:Feuer", "text": "Gewinne mit einem Feuer-Deck", "goal": 1, "reward": 90},
{"id": "wasser", "kind": "win_element:Wasser", "text": "Gewinne mit einem Wasser-Deck", "goal": 1, "reward": 90},
{"id": "erde", "kind": "win_element:Erde", "text": "Gewinne mit einem Erde-Deck", "goal": 1, "reward": 90},
{"id": "luft", "kind": "win_element:Luft", "text": "Gewinne mit einem Luft-Deck", "goal": 1, "reward": 90},
]
static var today_override := "" # für Tests
const DUST_UPGRADE := {"selten": 100, "episch": 400, "legendär": 1600, "mythisch": 3200}
var coins := 0
var dust := 0
var collection := {} # id -> Anzahl
var decks: Array = [] # DECK_SLOTS Einträge {"name": String, "cards": Array von ids}
var active_deck := 0
var stats := {"games": 0, "wins": 0, "boosters": 0, "wins_by_opp": {}}
var campaign_stage := 0 # Anzahl abgeschlossener Kampagnenstufen
var daily := {"date": "", "task": "", "progress": 0, "done": false}
var collection := {} # id -> Seltenheitsstufe (Index in Cards.RARITIES), fehlend = gewöhnlich
var campaign_stage := 0 # Anzahl abgeschlossener Kampagnenstufen
var settings := {"volume": 0.8, "music": 0.5, "ai_speed": 1.0, "animations": true, "language": "de", "difficulty": "normal", "big_font": false}
var notices: Array[String] = [] # Meldungen für die Oberfläche (Tagesaufgabe, Fortschritt)
var card_stats := {} # id -> {"played", "games", "wins"}
var booster_since_mythic := 0
var perk := "" # gewähltes eigenes Merkmal (nach Kapitel 1)
var records := {"streak": 0, "best_streak": 0, "fastest_win": 0, "loss_streak": 0, "margins": []}
## Das aktive Deck (Kartenliste des gewählten Slots).
var deck: Array:
get:
return decks[active_deck].cards
set(value):
decks[active_deck].cards = value
func _init() -> void:
for i in DECK_SLOTS:
decks.append({"name": "Deck %d" % (i + 1), "cards": []})
var notices: Array[String] = [] # Meldungen für die Oberfläche (Kampagnenfortschritt)
var perk := "" # gewähltes eigenes Merkmal (nach Kapitel 1)
static func starter() -> Profile:
var p := Profile.new()
p.coins = START_COINS
for c in Cards.of_rarity("gewöhnlich"):
if c.id.begins_with("f_") or c.id.begins_with("w_") or c.id.begins_with("e_") or c.id.begins_with("l_"):
p.collection[c.id] = 2
# Zwei seltene Karten zum Start, damit es etwas zum Aufbauen gibt
p.collection["f_drache"] = 1
p.collection["e_baum"] = 1
var starter_ids := ["f_funke", "f_kobold", "f_wolf", "f_feuerball", "f_asche",
"e_wall", "e_wurzel", "e_haerten", "e_golem", "e_troll"]
for cid in starter_ids:
p.deck.append(cid)
p.deck.append(cid)
p.deck[p.deck.find("f_asche")] = "f_drache"
p.deck[p.deck.find("e_troll")] = "e_baum"
return p
@@ -102,71 +51,34 @@ static func load_or_new(path: String = SAVE_PATH) -> Profile:
return p
## Liest jede Spielstand-Version; fehlende Felder bekommen Standardwerte (Migration).
## Liest jede Spielstand-Version; ältere Stände behalten Münzen, Staub, Kampagne und
## Einstellungen, die alte Sammlung (Anzahl je Karte) hat keine Entsprechung mehr.
static func from_dict(d: Dictionary) -> Profile:
var p := Profile.new()
var version := int(d.get("version", 1))
p.coins = int(d.get("coins", 0))
p.dust = int(d.get("dust", 0))
var coll = d.get("collection", {})
for cid in coll:
if Cards.has_id(cid):
p.collection[cid] = int(coll[cid])
if version >= 3:
var list = d.get("decks", [])
for i in mini(list.size(), DECK_SLOTS):
p.decks[i].name = str(list[i].get("name", "Deck %d" % (i + 1)))
p.decks[i].cards = []
for cid in list[i].get("cards", []):
if Cards.has_id(cid):
p.decks[i].cards.append(cid)
p.active_deck = clampi(int(d.get("active_deck", 0)), 0, DECK_SLOTS - 1)
else:
for cid in d.get("deck", []):
p.campaign_stage = int(d.get("campaign_stage", 0))
if int(d.get("version", 1)) >= SAVE_VERSION:
var coll = d.get("collection", {})
for cid in coll:
if Cards.has_id(cid):
p.decks[0].cards.append(cid)
if version >= 2:
var st = d.get("stats", {})
p.stats.games = int(st.get("games", 0))
p.stats.wins = int(st.get("wins", 0))
p.stats.boosters = int(st.get("boosters", 0))
for k in st.get("wins_by_opp", {}):
p.stats.wins_by_opp[str(k)] = int(st.wins_by_opp[k])
p.campaign_stage = int(d.get("campaign_stage", 0))
var dl = d.get("daily", {})
p.daily = {"date": str(dl.get("date", "")), "task": str(dl.get("task", "")),
"progress": int(dl.get("progress", 0)), "done": bool(dl.get("done", false))}
var se = d.get("settings", {})
p.settings = {"volume": float(se.get("volume", 0.8)), "music": float(se.get("music", 0.5)),
"ai_speed": float(se.get("ai_speed", 1.0)), "animations": bool(se.get("animations", true)),
"language": str(se.get("language", "de")), "difficulty": str(se.get("difficulty", "normal")),
"big_font": bool(se.get("big_font", false))}
if not DIFFICULTIES.has(p.settings.difficulty):
p.settings.difficulty = "normal"
if version >= 4:
for cid in d.get("card_stats", {}):
if Cards.has_id(cid):
var st = d.card_stats[cid]
p.card_stats[cid] = {"played": int(st.get("played", 0)), "games": int(st.get("games", 0)), "wins": int(st.get("wins", 0))}
p.booster_since_mythic = int(d.get("booster_since_mythic", 0))
if version >= 5:
p.perk = str(d.get("perk", ""))
if not Game.PERKS.has(p.perk):
p.perk = ""
var rc = d.get("records", {})
p.records = {"streak": int(rc.get("streak", 0)), "best_streak": int(rc.get("best_streak", 0)),
"fastest_win": int(rc.get("fastest_win", 0)), "loss_streak": int(rc.get("loss_streak", 0)),
"margins": Array(rc.get("margins", [])).map(func(m): return int(m))}
p.collection[cid] = clampi(int(coll[cid]), 0, Cards.RARITIES.size() - 1)
var se = d.get("settings", {})
p.settings = {"volume": float(se.get("volume", 0.8)), "music": float(se.get("music", 0.5)),
"ai_speed": float(se.get("ai_speed", 1.0)), "animations": bool(se.get("animations", true)),
"language": str(se.get("language", "de")), "difficulty": str(se.get("difficulty", "normal")),
"big_font": bool(se.get("big_font", false))}
if not DIFFICULTIES.has(p.settings.difficulty):
p.settings.difficulty = "normal"
p.perk = str(d.get("perk", ""))
if not Game.PERKS.has(p.perk):
p.perk = ""
return p
func to_dict() -> Dictionary:
return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "collection": collection,
"decks": decks, "active_deck": active_deck,
"stats": stats, "campaign_stage": campaign_stage,
"daily": daily, "settings": settings, "card_stats": card_stats,
"booster_since_mythic": booster_since_mythic,
"perk": perk, "records": records}
"campaign_stage": campaign_stage, "settings": settings, "perk": perk}
## Speichert und sichert den vorherigen Stand als .bak.
@@ -185,363 +97,114 @@ func take_notices() -> Array[String]:
return out
# --- Decks und Sammlung -----------------------------------------------------------
# --- Sammlung: Seltenheit als Aussehen --------------------------------------------
func deck_cards(slot: int = -1) -> Array:
var ids: Array = decks[slot if slot >= 0 else active_deck].cards
var out := []
for cid in ids:
out.append(Cards.by_id(cid))
func level(cid: String) -> int:
return int(collection.get(cid, 0))
func rarity(cid: String) -> String:
return Cards.RARITIES[level(cid)]
## Karten-ID -> Stufe für den Kampf (nur aufgewertete Karten, siehe Game.Player.looks).
func looks() -> Dictionary:
var out := {}
for cid in collection:
if int(collection[cid]) > 0:
out[cid] = int(collection[cid])
return out
func deck_valid(slot: int = -1) -> Array:
return Game.validate_deck(deck_cards(slot), collection)
func deck_name(slot: int = -1) -> String:
return decks[slot if slot >= 0 else active_deck].name
func rename_deck(slot: int, name: String) -> String:
var n := name.strip_edges()
if n.is_empty() or n.length() > 20:
return "Name muss 1 bis 20 Zeichen haben"
decks[slot].name = n
return ""
## Füllt einen Slot mit eigenen Karten auf 20 auf, entlang einer Energiekurve.
## Ohne Karten im Slot werden die beiden Elemente mit den meisten eigenen Karten gewählt.
func autofill_deck(slot: int) -> String:
var cards: Array = decks[slot].cards
var elems := Game.deck_elements(deck_cards(slot))
if elems.is_empty():
var count := {}
for c in owned_cards():
for e in c.elements:
count[e] = count.get(e, 0) + 1
var ranked: Array = count.keys()
ranked.sort_custom(func(a, b): return count[a] > count[b])
elems = ranked.slice(0, Game.MAX_DECK_ELEMENTS)
var pool := owned_cards().filter(func(c): return c.elements.all(func(e): return e in elems))
pool.sort_custom(func(a, b): return Cards.RARITIES.find(a.rarity) > Cards.RARITIES.find(b.rarity))
var counts := {}
for cid in cards:
counts[cid] = counts.get(cid, 0) + 1
var progressed := true
while cards.size() < Game.DECK_SIZE and progressed:
progressed = false
for cost in range(1, 11):
if cards.size() >= Game.DECK_SIZE:
break
for c in pool:
if c.cost != cost:
continue
var have: int = counts.get(c.id, 0)
if have < mini(collection.get(c.id, 0), Game.MAX_COPIES):
cards.append(c.id)
counts[c.id] = have + 1
progressed = true
break
var ok := Game.validate_deck(deck_cards(slot), collection)
return "" if ok[0] else ok[1]
func owned_cards() -> Array:
return Cards.all().filter(func(c): return collection.get(c.id, 0) > 0)
func distinct_owned() -> int:
## Anzahl Karten über der gewöhnlichen Stufe.
func upgraded_count() -> int:
var n := 0
for cid in collection:
if collection[cid] > 0:
if int(collection[cid]) > 0:
n += 1
return n
# --- Zerlegen und Herstellen ------------------------------------------------------
## Zerlegt n Kopien in Staub. Gibt "" zurück oder eine Fehlermeldung.
func dismantle(cid: String, n: int = 1) -> String:
var owned: int = collection.get(cid, 0)
if owned < n:
return "Nicht genug Kopien"
var needed := 0
for d in decks:
needed = maxi(needed, d.cards.count(cid))
if owned - n < needed:
return "Wird in einem Deck gebraucht"
collection[cid] = owned - n
dust += DUST_DISMANTLE[Cards.by_id(cid).rarity] * n
return ""
## Stellt eine Karte aus Staub her. Gibt "" zurück oder eine Fehlermeldung.
func craft(cid: String) -> String:
var cost: int = DUST_CRAFT[Cards.by_id(cid).rarity]
## Wertet eine Karte um eine Stufe auf. Gibt "" zurück oder eine Fehlermeldung.
func upgrade(cid: String) -> String:
var lvl := level(cid)
if lvl >= Cards.RARITIES.size() - 1:
return "Schon auf der höchsten Stufe"
var cost: int = DUST_UPGRADE[Cards.RARITIES[lvl + 1]]
if dust < cost:
return "Nicht genug Staub (%d nötig)" % cost
dust -= cost
collection[cid] = collection.get(cid, 0) + 1
daily_event("craft")
collection[cid] = lvl + 1
return ""
## Zerlegt alle Kopien über dem Deck-Limit. Gibt den gewonnenen Staub zurück.
func dismantle_extras() -> int:
var before := dust
for cid in collection.keys():
var extra: int = collection[cid] - Game.MAX_COPIES
if extra > 0:
dismantle(cid, extra)
return dust - before
## Gibt eine Stufe ab und bekommt Staub dafür. Gibt "" zurück oder eine Fehlermeldung.
func dismantle(cid: String) -> String:
var lvl := level(cid)
if lvl == 0:
return "Gewöhnliche Karten lassen sich nicht zerlegen"
dust += DUST_DISMANTLE[Cards.RARITIES[lvl]]
collection[cid] = lvl - 1
return ""
# --- Booster ----------------------------------------------------------------------
## Kartenpool einer Seltenheit, bei Elementboostern nur Karten mit diesem Element.
static func booster_pool(rarity: String, element: String) -> Array:
var pool := Cards.of_rarity(rarity)
if element != "":
pool = pool.filter(func(c): return element in c.elements)
return pool
static func roll_booster(r: RandomNumberGenerator, element: String = "") -> Array:
## Fünf Karten mit gewürfelter Seltenheit.
static func roll_booster(r: RandomNumberGenerator) -> Array:
var rarities := RARITY_WEIGHTS.keys()
var weights := PackedFloat32Array(RARITY_WEIGHTS.values())
var cards := []
for i in BOOSTER_SIZE:
var rarity: String = rarities[r.rand_weighted(weights)]
if i == BOOSTER_SIZE - 1 and rarity == "gewöhnlich" \
and cards.all(func(c): return c.rarity == "gewöhnlich"):
rarity = "selten" # mindestens eine seltene Karte pro Booster
var pool := booster_pool(rarity, element)
cards.append(pool[r.randi_range(0, pool.size() - 1)])
return cards
## Deck-Code zum Teilen: "DINGER1:id*anzahl,…" (sortiert, daher vergleichbar).
static func deck_code(card_ids: Array) -> String:
var counts := {}
for cid in card_ids:
counts[cid] = counts.get(cid, 0) + 1
var keys := counts.keys()
keys.sort()
return CODE_PREFIX + ",".join(keys.map(func(k): return "%s*%d" % [k, counts[k]]))
## Karten-IDs aus einem Deck-Code; leer bei ungültigem Code.
static func parse_deck_code(code: String) -> Array:
var s := code.strip_edges()
if not s.begins_with(CODE_PREFIX):
return []
var pool := Cards.all()
var out := []
for part in s.substr(CODE_PREFIX.length()).split(",", false):
var kv: PackedStringArray = part.strip_edges().split("*")
if kv.size() != 2 or not Cards.has_id(kv[0]) or not kv[1].is_valid_int():
return []
for i in clampi(int(kv[1]), 0, Game.MAX_COPIES):
out.append(kv[0])
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 == "gewöhnlich"):
rar = "selten" # mindestens eine seltene Karte pro Booster
out.append({"card": pool[r.randi_range(0, pool.size() - 1)], "rarity": rar})
return out
## Öffnet einen Booster (element: Elementbooster). Gibt die Karten zurück, leer wenn die Münzen nicht reichen.
func open_booster(r: RandomNumberGenerator = null, element: String = "") -> Array:
var cost := ELEMENT_BOOSTER_COST if element != "" else BOOSTER_COST
if coins < cost:
## Öffnet einen Booster. Höhere Stufen werten die Karte auf, alles andere wird zu Staub.
## Gibt die Ergebnisse zurück, leer wenn die Münzen nicht reichen.
func open_booster(r: RandomNumberGenerator = null) -> Array:
if coins < BOOSTER_COST:
return []
if r == null:
r = Game.make_rng()
coins -= cost
var cards := roll_booster(r, element)
booster_since_mythic += 1
var has_mythic: bool = cards.any(func(c): return c.rarity == "mythisch")
if booster_since_mythic >= PITY_BOOSTERS and not has_mythic:
var pool := booster_pool("mythisch", element)
cards[BOOSTER_SIZE - 1] = pool[r.randi_range(0, pool.size() - 1)]
has_mythic = true
if has_mythic:
booster_since_mythic = 0
for c in cards:
collection[c.id] = collection.get(c.id, 0) + 1
stats.boosters += 1
daily_event("booster")
return cards
coins -= BOOSTER_COST
var out := roll_booster(r)
for res in out:
var lvl: int = Cards.RARITIES.find(res.rarity)
if lvl > level(res.card.id):
collection[res.card.id] = lvl
res["upgraded"] = true
res["dust"] = 0
else:
res["upgraded"] = false
res["dust"] = DUST_DISMANTLE[res.rarity]
dust += res.dust
return out
# --- Kampfergebnis, Tagesaufgabe -------------------------------------------
# --- Kampf und Kampagne -----------------------------------------------------------
## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück.
## stage >= 0: Kampagnenstufe; ein erster Sieg schaltet die nächste Stufe frei.
## turns: Dauer der Partie für die Bestenliste.
func record_battle(won: bool, n_opp: int, deck_used: Array, stage: int = -1, played_ids: Array = [],
turns: int = 0, reward_factor: float = 1.0, remaining_life: int = -1) -> int:
stats.games += 1
records.margins.append(maxi(remaining_life, 0) if won else 0)
while records.margins.size() > 5:
records.margins.pop_front()
if won:
records.streak += 1
records.loss_streak = 0
records.best_streak = maxi(records.best_streak, records.streak)
if turns > 0 and (records.fastest_win == 0 or turns < records.fastest_win):
records.fastest_win = turns
else:
records.streak = 0
records.loss_streak += 1
var tip := suggest_difficulty()
if tip != "" and stage < 0:
notices.append("Tipp: %s. Schwierigkeit %s?" % [
"%d Siege in Folge" % records.streak if won else "%d Niederlagen in Folge" % records.loss_streak, DIFFICULTIES[tip].name])
for cid in played_ids:
_stat(cid).played += 1
var seen := {}
for c in deck_used:
if not seen.has(c.id):
seen[c.id] = true
_stat(c.id).games += 1
if won:
_stat(c.id).wins += 1
func record_battle(won: bool, n_opp: int, stage: int = -1, reward_factor: float = 1.0) -> int:
var reward := REWARD_LOSS
if won:
stats.wins += 1
var key := str(n_opp)
stats.wins_by_opp[key] = int(stats.wins_by_opp.get(key, 0)) + 1
reward = int(round(REWARD_WIN[n_opp] * reward_factor))
if stage >= 0 and stage == campaign_stage:
campaign_stage += 1
reward = Campaign.stage(stage).reward
notices.append("Kampagne: %s besiegt!" % Campaign.stage(stage).name)
notices.append("Kampagne: %s besiegt" % Campaign.stage(stage).name)
coins += reward
daily_event("game")
if won:
daily_event("win")
if n_opp >= 3:
daily_event("win_vs3")
for e in Game.deck_elements(deck_used):
daily_event("win_element:" + e)
return reward
func _stat(cid: String) -> Dictionary:
if not card_stats.has(cid):
card_stats[cid] = {"played": 0, "games": 0, "wins": 0}
return card_stats[cid]
func difficulty() -> Dictionary:
return DIFFICULTIES.get(settings.get("difficulty", "normal"), DIFFICULTIES.normal)
## Vorschlag: rauf nach drei deutlichen Siegen in Folge (im Schnitt mindestens 12 Leben übrig) oder fünf
## Siegen egal wie knapp; runter nach drei Niederlagen in Folge. "" = kein Vorschlag.
func suggest_difficulty() -> String:
var order := ["leicht", "normal", "schwer"]
var i := order.find(str(settings.get("difficulty", "normal")))
if i < order.size() - 1 and records.streak >= 3:
var last: Array = records.margins.slice(maxi(0, records.margins.size() - 3))
var avg := 0.0
for m in last:
avg += float(m) / last.size()
if records.streak >= 5 or (records.margins.size() >= 3 and avg >= 12.0):
return order[i + 1]
if records.loss_streak >= 3 and i > 0:
return order[i - 1]
return ""
## Eigene Deckstärke, mit Kartenstatistik gewichtet.
func deck_rating(cards: Array = []) -> float:
var deck_used: Array = cards if not cards.is_empty() else deck_cards()
return Game.deck_rating(deck_used, card_stats)
## Zielstärke der Gegnerdecks: eigene Deckstärke mal Schwierigkeitsfaktor.
func target_rating(cards: Array = []) -> float:
return deck_rating(cards) * float(difficulty().factor)
## Tauscht die schwächste Karte des aktiven Decks gegen cid, wenn das Deck dadurch stärker und gültig bleibt.
## dry_run: nur prüfen. Gibt "" zurück oder den Grund, warum nicht.
func improve_deck_with(cid: String, dry_run: bool = false) -> String:
if collection.get(cid, 0) <= 0:
return "Karte nicht in der Sammlung"
var card := Cards.by_id(cid)
if deck.count(cid) >= mini(collection[cid], Game.MAX_COPIES):
return "Schon so oft im Deck wie möglich"
var elems := Game.deck_elements(deck_cards())
for e in card.elements:
if not (e in elems):
elems.append(e)
if elems.size() > Game.MAX_DECK_ELEMENTS:
return "Passt nicht zu den Elementen des Decks"
var weakest := ""
var weakest_value := Cards.card_value(card)
for other in deck:
if other == cid:
continue
var v := Cards.card_value(Cards.by_id(other))
if v < weakest_value:
weakest_value = v
weakest = other
if weakest == "":
return "Keine schwächere Karte im Deck"
if dry_run:
return ""
var trial: Array = deck.duplicate()
trial[trial.find(weakest)] = cid
var ok := Game.validate_deck(trial.map(func(id): return Cards.by_id(id)), collection)
if not ok[0]:
return ok[1]
deck = trial
return ""
## Karten aus new_ids, die das aktive Deck verbessern würden.
func deck_suggestions(new_ids: Array) -> Array:
var out := []
for cid in new_ids:
if not (cid in out) and improve_deck_with(cid, true) == "":
out.append(cid)
return out
func perk_unlocked() -> bool:
return campaign_stage >= Campaign.STAGES.size()
static func today() -> String:
return today_override if today_override != "" else Time.get_date_string_from_system()
## Wählt die Tagesaufgabe neu, wenn ein neuer Tag begonnen hat.
func refresh_daily() -> void:
var t := today()
if daily.date != t:
var day_number := int(Time.get_unix_time_from_datetime_string(t + "T00:00:00") / 86400)
var task: Dictionary = DAILY_TASKS[day_number % DAILY_TASKS.size()]
daily = {"date": t, "task": task.id, "progress": 0, "done": false}
func daily_task() -> Dictionary:
refresh_daily()
for t in DAILY_TASKS:
if t.id == daily.task:
return t
return DAILY_TASKS[0]
## Zählt ein Ereignis (siehe DAILY_TASKS) für die Tagesaufgabe. Gibt die Belohnung zurück.
func daily_event(kind: String, n: int = 1) -> int:
var task := daily_task()
if daily.done or task.kind != kind:
return 0
daily.progress += n
if daily.progress >= task.goal:
daily.done = true
coins += task.reward
notices.append("Tagesaufgabe erfüllt: %s (+%d Münzen)" % [task.text, task.reward])
return task.reward
return 0