class_name Profile extends RefCounted ## 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 := 6 const SAVE_PATH := "user://spielstand.json" const BOOSTER_COST := 100 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 # Schwierigkeit: Leben der Gegner, KI und Münzen. const DIFFICULTIES := { "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: 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_UPGRADE := {"selten": 100, "episch": 400, "legendär": 1600, "mythisch": 3200} var coins := 0 var dust := 0 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 (Kampagnenfortschritt) var perk := "" # gewähltes eigenes Merkmal (nach Kapitel 1) static func starter() -> Profile: var p := Profile.new() p.coins = START_COINS return p ## Lädt den Spielstand; bei Schaden die Sicherung; sonst ein neues Profil. static func load_or_new(path: String = SAVE_PATH) -> Profile: for candidate in [path, path + ".bak"]: if FileAccess.file_exists(candidate): var json := JSON.new() if json.parse(FileAccess.get_file_as_string(candidate)) == OK and json.data is Dictionary: return from_dict(json.data) if FileAccess.file_exists(path): DirAccess.rename_absolute(ProjectSettings.globalize_path(path), ProjectSettings.globalize_path(path) + ".corrupt") var p := starter() p.save(path) return p ## 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() p.coins = int(d.get("coins", 0)) p.dust = int(d.get("dust", 0)) 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.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, "campaign_stage": campaign_stage, "settings": settings, "perk": perk} ## Speichert und sichert den vorherigen Stand als .bak. func save(path: String = SAVE_PATH) -> void: var abs := ProjectSettings.globalize_path(path) if FileAccess.file_exists(path): DirAccess.copy_absolute(abs, abs + ".bak") var f := FileAccess.open(path, FileAccess.WRITE) f.store_string(JSON.stringify(to_dict(), "\t")) f.close() func take_notices() -> Array[String]: var out := notices.duplicate() notices.clear() return out # --- Sammlung: Seltenheit als Aussehen -------------------------------------------- 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 ## Anzahl Karten über der gewöhnlichen Stufe. func upgraded_count() -> int: var n := 0 for cid in collection: if int(collection[cid]) > 0: n += 1 return n ## 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] = lvl + 1 return "" ## 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 ---------------------------------------------------------------------- ## 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 pool := Cards.all() 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 == "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. 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 -= 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 # --- 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. func record_battle(won: bool, n_opp: int, stage: int = -1, reward_factor: float = 1.0) -> int: var reward := REWARD_LOSS if won: 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) coins += reward return reward func difficulty() -> Dictionary: return DIFFICULTIES.get(settings.get("difficulty", "normal"), DIFFICULTIES.normal) func perk_unlocked() -> bool: return campaign_stage >= Campaign.STAGES.size()