Wöchentliche Herausforderung und die Netzwerk-Hinweise entfernt
- Challenge samt Bildschirm, Menüpunkt, Erfolg "Wochenheld" und Spielstandfeld entfernt; record_battle verliert den challenge-Parameter, die Bestenliste bleibt - Lobby: Erklärzeile unter dem Titel weg, "KI-Gegner (nur Host)" heißt jetzt "KI-Gegner" Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
@@ -37,7 +37,7 @@ Der Spielstand liegt unter `user://spielstand.json`
|
|||||||
- Deck-Namen, Auto-Ausfüllen, Hintergrundmusik und Oberfläche wahlweise auf Englisch
|
- Deck-Namen, Auto-Ausfüllen, Hintergrundmusik und Oberfläche wahlweise auf Englisch
|
||||||
- Geführte Anleitung mit Hervorhebungen, Rückgängig für Fehlklicks, Überspringen von Gegnerzügen
|
- Geführte Anleitung mit Hervorhebungen, Rückgängig für Fehlklicks, Überspringen von Gegnerzügen
|
||||||
- Zwei Kampagnenkapitel (Bosse mit ein bis drei Merkmalen), wählbares eigenes Merkmal nach Kapitel 1
|
- Zwei Kampagnenkapitel (Bosse mit ein bis drei Merkmalen), wählbares eigenes Merkmal nach Kapitel 1
|
||||||
- Wöchentliche Herausforderung mit Regelvarianten, Bestenliste (Serie, schnellster Sieg), Statistik-Export als CSV
|
- Bestenliste (Serie, schnellster Sieg) und Statistik-Export als CSV
|
||||||
- Zehn Fallenkarten, Netzwerk mit Wiederverbinden (Platz bleibt 60 s frei)
|
- Zehn Fallenkarten, Netzwerk mit Wiederverbinden (Platz bleibt 60 s frei)
|
||||||
- Deckstärke aus Seltenheit, Schlüsselwörtern und Effekten; Gegnerdecks werden auf die eigene Stärke gebaut
|
- Deckstärke aus Seltenheit, Schlüsselwörtern und Effekten; Gegnerdecks werden auf die eigene Stärke gebaut
|
||||||
- Schwierigkeit Leicht/Normal/Schwer: Zielstärke 0,9/1,0/1,2-fach, schwache oder starke Archetypen, einfache oder Vorausschau-KI, Münzen 0,75/1/1,5-fach
|
- Schwierigkeit Leicht/Normal/Schwer: Zielstärke 0,9/1,0/1,2-fach, schwache oder starke Archetypen, einfache oder Vorausschau-KI, Münzen 0,75/1/1,5-fach
|
||||||
@@ -51,7 +51,7 @@ Der Spielstand liegt unter `user://spielstand.json`
|
|||||||
- Kartentext passt sich der Schriftgröße an, Gamepad auch in Menüs (Fokusrahmen), Anleitung 2 erzwingt Maras Angriff in die Falle
|
- Kartentext passt sich der Schriftgröße an, Gamepad auch in Menüs (Fokusrahmen), Anleitung 2 erzwingt Maras Angriff in die Falle
|
||||||
- KI-Vorausschau in zwei Stufen (erst alle Züge mit den besten Zielen, dann die acht besten vertieft): bei 5 Gegnern 0,1 s statt 2 s, und sie rechnet im Hintergrund-Thread, Musik ebenso
|
- KI-Vorausschau in zwei Stufen (erst alle Züge mit den besten Zielen, dann die acht besten vertieft): bei 5 Gegnern 0,1 s statt 2 s, und sie rechnet im Hintergrund-Thread, Musik ebenso
|
||||||
- KI-Bewertung mit Bedrohung (gegnerische Angriffskraft ohne eigenen Wächter, tödliche besonders) und Führenden-Bremse; im Duell 62:38 gegen die alte Bewertung
|
- KI-Bewertung mit Bedrohung (gegnerische Angriffskraft ohne eigenen Wächter, tödliche besonders) und Führenden-Bremse; im Duell 62:38 gegen die alte Bewertung
|
||||||
- Unterbrochene Einzelspieler-Kämpfe (auch Kampagne und Herausforderung) werden laufend gespeichert und lassen sich im Menü fortsetzen
|
- Unterbrochene Einzelspieler-Kämpfe (auch Kampagne) werden laufend gespeichert und lassen sich im Menü fortsetzen
|
||||||
- Gamepad im Deckbau, in der Werkstatt und beim Booster: Karten, Deckzeilen und Kartenrücken sind fokussierbar, A wählt, B geht zurück
|
- Gamepad im Deckbau, in der Werkstatt und beim Booster: Karten, Deckzeilen und Kartenrücken sind fokussierbar, A wählt, B geht zurück
|
||||||
- Englisch auch für Kartennamen und Kartentexte (`name_en` in `data/karten.json`, Textbausteine über `tr()`)
|
- Englisch auch für Kartennamen und Kartentexte (`name_en` in `data/karten.json`, Textbausteine über `tr()`)
|
||||||
- Deck-Codes zum Teilen (`DINGER1:id*anzahl,…`) mit Zwischenablage im Deckbau
|
- Deck-Codes zum Teilen (`DINGER1:id*anzahl,…`) mit Zwischenablage im Deckbau
|
||||||
@@ -103,5 +103,4 @@ godot --headless --path . --export-release Windows build/dinger-windows.exe
|
|||||||
- `scripts/ui/` – Oberfläche: Menü, Gegnerauswahl, Kampagne, Lobby, Booster, Deckbau, Werkstatt, Kampf (Bildschirm, Layout, Effekte, Anleitung, Mulligan, Gamepad), Erfolge, Einstellungen, Regeln, Ton
|
- `scripts/ui/` – Oberfläche: Menü, Gegnerauswahl, Kampagne, Lobby, Booster, Deckbau, Werkstatt, Kampf (Bildschirm, Layout, Effekte, Anleitung, Mulligan, Gamepad), Erfolge, Einstellungen, Regeln, Ton
|
||||||
- `scenes/main.tscn` – Startszene
|
- `scenes/main.tscn` – Startszene
|
||||||
- `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden
|
- `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden
|
||||||
- `scripts/challenge.gd` – wöchentliche Herausforderungen
|
|
||||||
- `translations/ui.csv` – englische Oberflächentexte
|
- `translations/ui.csv` – englische Oberflächentexte
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
- Im lokalen Netz zu mehreren spielbar, auch als Zuschauer
|
- Im lokalen Netz zu mehreren spielbar, auch als Zuschauer
|
||||||
- Ein unterbrochener Kampf lässt sich später fortsetzen
|
- Ein unterbrochener Kampf lässt sich später fortsetzen
|
||||||
- Geführte erste Partie als Einstieg
|
- Geführte erste Partie als Einstieg
|
||||||
- Wöchentliche Herausforderungen und Bestenliste als Langzeitziel
|
- Bestenliste als Langzeitziel
|
||||||
|
|
||||||
## Sammeln
|
## Sammeln
|
||||||
- Booster öffnen
|
- Booster öffnen
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
class_name Challenge
|
|
||||||
## Wöchentliche Herausforderung: feste Regelvariante, einmal pro Woche belohnt.
|
|
||||||
|
|
||||||
const LIST := [
|
|
||||||
{"id": "eile", "name": "Eile", "text": "Alle beginnen mit 3 Energie und 20 Leben, drei Gegner.", "n_opp": 3,
|
|
||||||
"rules": {"everyone_perk": "start_energie", "start_life": 20}, "reward": 200},
|
|
||||||
{"id": "resonanz", "name": "Resonanzsturm", "text": "Resonanz zählt für alle doppelt, zwei Gegner.", "n_opp": 2,
|
|
||||||
"rules": {"everyone_perk": "resonanz_doppelt"}, "reward": 180},
|
|
||||||
{"id": "flink", "name": "Blitzkrieg", "text": "Alle Wesen sind flink, vier Gegner.", "n_opp": 4,
|
|
||||||
"rules": {"everyone_perk": "alle_flink"}, "reward": 250},
|
|
||||||
{"id": "schild", "name": "Panzerschlacht", "text": "Alle Wesen kommen mit Schild, Duell mit 40 Leben.", "n_opp": 1,
|
|
||||||
"rules": {"everyone_perk": "schild_wesen", "start_life": 40}, "reward": 150},
|
|
||||||
{"id": "karten", "name": "Kartenflut", "text": "Jeder zieht 2 Karten pro Zug, fünf Gegner am Tisch.", "n_opp": 5,
|
|
||||||
"rules": {"everyone_perk": "extra_karten"}, "reward": 300},
|
|
||||||
{"id": "mono", "name": "Reine Lehre", "text": "Nur Decks mit einem Element sind erlaubt, drei Gegner.", "n_opp": 3,
|
|
||||||
"rules": {"mono_only": true}, "reward": 220},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
static func week() -> int:
|
|
||||||
var t := Profile.today()
|
|
||||||
return int(Time.get_unix_time_from_datetime_string(t + "T00:00:00") / 604800)
|
|
||||||
|
|
||||||
|
|
||||||
static func current() -> Dictionary:
|
|
||||||
return LIST[week() % LIST.size()]
|
|
||||||
|
|
||||||
|
|
||||||
## Prüft, ob das Deck zur Herausforderung passt. Gibt "" oder eine Fehlermeldung zurück.
|
|
||||||
static func deck_allowed(ch: Dictionary, deck: Array) -> String:
|
|
||||||
if ch.rules.get("mono_only", false) and Game.deck_elements(deck).size() != 1:
|
|
||||||
return "Diese Woche sind nur Decks mit einem Element erlaubt"
|
|
||||||
return ""
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://n5h6q6kfoo34
|
|
||||||
@@ -38,7 +38,6 @@ const ACHIEVEMENTS := [
|
|||||||
{"id": "elementmeister", "name": "Meister der Elemente", "text": "Schließe Kapitel 2 der Kampagne ab.", "reward": 500},
|
{"id": "elementmeister", "name": "Meister der Elemente", "text": "Schließe Kapitel 2 der Kampagne ab.", "reward": 500},
|
||||||
{"id": "archimagier", "name": "Archimagier", "text": "Schließe Kapitel 3 der Kampagne ab.", "reward": 800},
|
{"id": "archimagier", "name": "Archimagier", "text": "Schließe Kapitel 3 der Kampagne ab.", "reward": 800},
|
||||||
{"id": "serie", "name": "Siegesserie", "text": "Gewinne fünf Kämpfe in Folge.", "reward": 150},
|
{"id": "serie", "name": "Siegesserie", "text": "Gewinne fünf Kämpfe in Folge.", "reward": 150},
|
||||||
{"id": "wochenheld", "name": "Wochenheld", "text": "Bestehe eine wöchentliche Herausforderung.", "reward": 100},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const DAILY_TASKS := [
|
const DAILY_TASKS := [
|
||||||
@@ -75,7 +74,6 @@ var tutorial_done := false
|
|||||||
var perk := "" # gewähltes eigenes Merkmal (nach Kapitel 1)
|
var perk := "" # gewähltes eigenes Merkmal (nach Kapitel 1)
|
||||||
var tutorial2_done := false
|
var tutorial2_done := false
|
||||||
var records := {"streak": 0, "best_streak": 0, "fastest_win": 0, "loss_streak": 0, "margins": []}
|
var records := {"streak": 0, "best_streak": 0, "fastest_win": 0, "loss_streak": 0, "margins": []}
|
||||||
var challenge_week := -1 # Woche der zuletzt bestandenen Herausforderung
|
|
||||||
|
|
||||||
## Das aktive Deck (Kartenliste des gewählten Slots).
|
## Das aktive Deck (Kartenliste des gewählten Slots).
|
||||||
var deck: Array:
|
var deck: Array:
|
||||||
@@ -181,7 +179,6 @@ static func from_dict(d: Dictionary) -> Profile:
|
|||||||
p.records = {"streak": int(rc.get("streak", 0)), "best_streak": int(rc.get("best_streak", 0)),
|
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)),
|
"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))}
|
"margins": Array(rc.get("margins", [])).map(func(m): return int(m))}
|
||||||
p.challenge_week = int(d.get("challenge_week", -1))
|
|
||||||
p.tutorial2_done = bool(d.get("tutorial2_done", false))
|
p.tutorial2_done = bool(d.get("tutorial2_done", false))
|
||||||
return p
|
return p
|
||||||
|
|
||||||
@@ -192,7 +189,7 @@ func to_dict() -> Dictionary:
|
|||||||
"stats": stats, "achievements": achievements, "campaign_stage": campaign_stage,
|
"stats": stats, "achievements": achievements, "campaign_stage": campaign_stage,
|
||||||
"daily": daily, "settings": settings, "card_stats": card_stats,
|
"daily": daily, "settings": settings, "card_stats": card_stats,
|
||||||
"booster_since_mythic": booster_since_mythic, "tutorial_done": tutorial_done,
|
"booster_since_mythic": booster_since_mythic, "tutorial_done": tutorial_done,
|
||||||
"perk": perk, "records": records, "challenge_week": challenge_week, "tutorial2_done": tutorial2_done}
|
"perk": perk, "records": records, "tutorial2_done": tutorial2_done}
|
||||||
|
|
||||||
|
|
||||||
## Speichert und sichert den vorherigen Stand als .bak.
|
## Speichert und sichert den vorherigen Stand als .bak.
|
||||||
@@ -415,9 +412,9 @@ func open_booster(r: RandomNumberGenerator = null, element: String = "") -> Arra
|
|||||||
|
|
||||||
## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück.
|
## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück.
|
||||||
## stage >= 0: Kampagnenstufe; ein erster Sieg schaltet die nächste Stufe frei.
|
## stage >= 0: Kampagnenstufe; ein erster Sieg schaltet die nächste Stufe frei.
|
||||||
## turns: Dauer der Partie für die Bestenliste. challenge: Herausforderung dieser Woche.
|
## 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 = [],
|
func record_battle(won: bool, n_opp: int, deck_used: Array, stage: int = -1, played_ids: Array = [],
|
||||||
turns: int = 0, challenge: Dictionary = {}, reward_factor: float = 1.0, remaining_life: int = -1) -> int:
|
turns: int = 0, reward_factor: float = 1.0, remaining_life: int = -1) -> int:
|
||||||
stats.games += 1
|
stats.games += 1
|
||||||
records.margins.append(maxi(remaining_life, 0) if won else 0)
|
records.margins.append(maxi(remaining_life, 0) if won else 0)
|
||||||
while records.margins.size() > 5:
|
while records.margins.size() > 5:
|
||||||
@@ -432,7 +429,7 @@ func record_battle(won: bool, n_opp: int, deck_used: Array, stage: int = -1, pla
|
|||||||
records.streak = 0
|
records.streak = 0
|
||||||
records.loss_streak += 1
|
records.loss_streak += 1
|
||||||
var tip := suggest_difficulty()
|
var tip := suggest_difficulty()
|
||||||
if tip != "" and stage < 0 and challenge.is_empty():
|
if tip != "" and stage < 0:
|
||||||
notices.append("Tipp: %s. Schwierigkeit %s?" % [
|
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])
|
"%d Siege in Folge" % records.streak if won else "%d Niederlagen in Folge" % records.loss_streak, DIFFICULTIES[tip].name])
|
||||||
for cid in played_ids:
|
for cid in played_ids:
|
||||||
@@ -454,10 +451,6 @@ func record_battle(won: bool, n_opp: int, deck_used: Array, stage: int = -1, pla
|
|||||||
campaign_stage += 1
|
campaign_stage += 1
|
||||||
reward = Campaign.stage(stage).reward
|
reward = Campaign.stage(stage).reward
|
||||||
notices.append("Kampagne: %s besiegt!" % Campaign.stage(stage).name)
|
notices.append("Kampagne: %s besiegt!" % Campaign.stage(stage).name)
|
||||||
if not challenge.is_empty() and challenge_week != Challenge.week():
|
|
||||||
challenge_week = Challenge.week()
|
|
||||||
reward = challenge.reward
|
|
||||||
notices.append("Herausforderung bestanden: %s (+%d Münzen)" % [challenge.name, challenge.reward])
|
|
||||||
coins += reward
|
coins += reward
|
||||||
daily_event("game")
|
daily_event("game")
|
||||||
if won:
|
if won:
|
||||||
@@ -520,8 +513,6 @@ func _achieved(id: String, ctx: Dictionary) -> bool:
|
|||||||
return campaign_stage >= Campaign.total()
|
return campaign_stage >= Campaign.total()
|
||||||
"serie":
|
"serie":
|
||||||
return records.best_streak >= 5
|
return records.best_streak >= 5
|
||||||
"wochenheld":
|
|
||||||
return challenge_week >= 0
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ var gp_zone: String:
|
|||||||
var gp_index: int:
|
var gp_index: int:
|
||||||
get:
|
get:
|
||||||
return pad.index
|
return pad.index
|
||||||
var challenge := {} # wöchentliche Herausforderung oder leer
|
|
||||||
var skip_ai := false # Gegnerzüge im Schnelldurchlauf
|
var skip_ai := false # Gegnerzüge im Schnelldurchlauf
|
||||||
var smart_ai := true # Vorausschau-KI (Leicht: einfache KI)
|
var smart_ai := true # Vorausschau-KI (Leicht: einfache KI)
|
||||||
var undo_state: Game = null # Zustand vor der letzten rückgängig machbaren Aktion
|
var undo_state: Game = null # Zustand vor der letzten rückgängig machbaren Aktion
|
||||||
@@ -141,18 +140,11 @@ static func guided(part: int = 1) -> BattleScreen:
|
|||||||
## Unterbrochenen Kampf aus der Datei fortsetzen (siehe save_battle).
|
## Unterbrochenen Kampf aus der Datei fortsetzen (siehe save_battle).
|
||||||
static func resume(data: Dictionary) -> BattleScreen:
|
static func resume(data: Dictionary) -> BattleScreen:
|
||||||
var s := BattleScreen.new(int(data.get("n_opp", 1)), int(data.get("stage", -1)))
|
var s := BattleScreen.new(int(data.get("n_opp", 1)), int(data.get("stage", -1)))
|
||||||
s.challenge = data.get("challenge", {})
|
|
||||||
s.played_ids = Array(data.get("played_ids", []))
|
s.played_ids = Array(data.get("played_ids", []))
|
||||||
s.saved_game = Game.from_snapshot(data.game)
|
s.saved_game = Game.from_snapshot(data.game)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
static func weekly(ch: Dictionary) -> BattleScreen:
|
|
||||||
var b := BattleScreen.new(int(ch.n_opp), -1)
|
|
||||||
b.challenge = ch
|
|
||||||
return b
|
|
||||||
|
|
||||||
|
|
||||||
func is_online() -> bool:
|
func is_online() -> bool:
|
||||||
return net_play
|
return net_play
|
||||||
|
|
||||||
@@ -198,10 +190,6 @@ func _ready() -> void:
|
|||||||
opps[0].perks = []
|
opps[0].perks = []
|
||||||
game = Game.new_battle_with(app.profile.deck_cards(), opps, r, true)
|
game = Game.new_battle_with(app.profile.deck_cards(), opps, r, true)
|
||||||
guide.inject_cards(game)
|
guide.inject_cards(game)
|
||||||
elif not challenge.is_empty():
|
|
||||||
var rules: Dictionary = challenge.rules.duplicate()
|
|
||||||
rules["target_rating"] = Game.deck_rating(app.profile.deck_cards())
|
|
||||||
game = Game.new_battle(app.profile.deck_cards(), n_opp, null, rules)
|
|
||||||
elif stage >= 0:
|
elif stage >= 0:
|
||||||
var r := Game.make_rng()
|
var r := Game.make_rng()
|
||||||
game = Game.new_battle_with(app.profile.deck_cards(), Campaign.opponents(stage, r, app.profile.target_rating()), r, false, own_rules())
|
game = Game.new_battle_with(app.profile.deck_cards(), Campaign.opponents(stage, r, app.profile.target_rating()), r, false, own_rules())
|
||||||
@@ -323,7 +311,7 @@ func _overlay() -> Control:
|
|||||||
return Style.overlay(self)
|
return Style.overlay(self)
|
||||||
|
|
||||||
|
|
||||||
## Einzelspieler-Kämpfe (auch Kampagne und Herausforderung) lassen sich unterbrechen und fortsetzen.
|
## Einzelspieler-Kämpfe (auch Kampagne) lassen sich unterbrechen und fortsetzen.
|
||||||
func resumable() -> bool:
|
func resumable() -> bool:
|
||||||
return counts_for_profile() and not tutorial
|
return counts_for_profile() and not tutorial
|
||||||
|
|
||||||
@@ -331,7 +319,7 @@ func resumable() -> bool:
|
|||||||
func save_battle() -> void:
|
func save_battle() -> void:
|
||||||
if not resumable() or game == null or game.winner != "":
|
if not resumable() or game == null or game.winner != "":
|
||||||
return
|
return
|
||||||
var d := {"version": 1, "n_opp": n_opp, "stage": stage, "challenge": challenge, "played_ids": played_ids, "game": game.snapshot(-1)}
|
var d := {"version": 1, "n_opp": n_opp, "stage": stage, "played_ids": played_ids, "game": game.snapshot(-1)}
|
||||||
var f := FileAccess.open(app.battle_save_path(), FileAccess.WRITE)
|
var f := FileAccess.open(app.battle_save_path(), FileAccess.WRITE)
|
||||||
if f != null:
|
if f != null:
|
||||||
f.store_string(JSON.stringify(d))
|
f.store_string(JSON.stringify(d))
|
||||||
@@ -567,8 +555,8 @@ func _exit_tree() -> void:
|
|||||||
func _finish() -> void:
|
func _finish() -> void:
|
||||||
var won := game.winner == "human"
|
var won := game.winner == "human"
|
||||||
if counts_for_profile():
|
if counts_for_profile():
|
||||||
var factor: float = float(app.profile.difficulty().reward) if stage < 0 and challenge.is_empty() and not tutorial else 1.0
|
var factor: float = float(app.profile.difficulty().reward) if stage < 0 and not tutorial else 1.0
|
||||||
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids, game.turn, challenge, factor, game.human.life)
|
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids, game.turn, factor, game.human.life)
|
||||||
if tutorial:
|
if tutorial:
|
||||||
if tutorial_part == 1:
|
if tutorial_part == 1:
|
||||||
app.profile.tutorial_done = true
|
app.profile.tutorial_done = true
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
class_name ChallengeScreen
|
|
||||||
extends Control
|
|
||||||
## Wöchentliche Herausforderung: feste Regelvariante mit einmaliger Belohnung.
|
|
||||||
|
|
||||||
var app
|
|
||||||
var msg: Label
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
var p: Profile = app.profile
|
|
||||||
var ch := Challenge.current()
|
|
||||||
var done: bool = p.challenge_week == Challenge.week()
|
|
||||||
var col := Style.column(self, 760, 60, 14)
|
|
||||||
Style.clabel(col, "Herausforderung der Woche", 36, Style.GOLD, "center", true)
|
|
||||||
Style.clabel(col, "Jede Woche eine andere Regel. Die Belohnung gibt es einmal pro Woche, danach die normale.", 16, Style.MUTED)
|
|
||||||
Style.spacer(col, 20)
|
|
||||||
var panel := PanelContainer.new()
|
|
||||||
panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL, 10, Style.GREEN if done else Style.GOLD, 2))
|
|
||||||
col.add_child(panel)
|
|
||||||
var inner := VBoxContainer.new()
|
|
||||||
inner.add_theme_constant_override("separation", 10)
|
|
||||||
panel.add_child(inner)
|
|
||||||
Style.spacer(inner, 6)
|
|
||||||
Style.clabel(inner, ch.name, 30, Style.TEXT, "center", true)
|
|
||||||
Style.clabel(inner, ch.text, 18, Style.MUTED)
|
|
||||||
Style.clabel(inner, "Belohnung: %d Münzen%s" % [ch.reward, " · diese Woche schon bestanden ✓" if done else ""], 16, Style.GOLD)
|
|
||||||
Style.spacer(inner, 6)
|
|
||||||
Style.spacer(col, 10)
|
|
||||||
msg = Style.clabel(col, "", 16, Style.ORANGE)
|
|
||||||
var deck_err := Challenge.deck_allowed(ch, p.deck_cards())
|
|
||||||
if deck_err != "":
|
|
||||||
msg.text = deck_err + " (aktives Deck: %s)" % p.deck_name()
|
|
||||||
var br := Style.row(col, 20)
|
|
||||||
br.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
||||||
var b := Style.cbutton(br, "Antreten", Vector2(200, 50), func(): app.show_screen(BattleScreen.weekly(ch)), Color("285a3c"))
|
|
||||||
b.disabled = deck_err != "" or not p.deck_valid()[0]
|
|
||||||
Style.cbutton(br, "Zurück", Vector2(160, 50), func(): app.show_screen(MenuScreen.new()))
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
|
||||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
|
||||||
app.show_screen(MenuScreen.new())
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cxheynenqemse
|
|
||||||
@@ -22,7 +22,6 @@ func _ready() -> void:
|
|||||||
net.my_deck = app.profile.deck.duplicate()
|
net.my_deck = app.profile.deck.duplicate()
|
||||||
var col := Style.column(self, 760, 40, 10)
|
var col := Style.column(self, 760, 40, 10)
|
||||||
Style.clabel(col, "Netzwerkspiel", 36, Style.GOLD, "center", true)
|
Style.clabel(col, "Netzwerkspiel", 36, Style.GOLD, "center", true)
|
||||||
Style.clabel(col, "Im selben Netz: einer eröffnet, die anderen treten mit seiner Adresse bei. Jeder gegen jeden, ohne Münzen.", 16, Style.MUTED)
|
|
||||||
Style.spacer(col, 10)
|
Style.spacer(col, 10)
|
||||||
var hr := Style.row(col, 12)
|
var hr := Style.row(col, 12)
|
||||||
hr.alignment = BoxContainer.ALIGNMENT_CENTER
|
hr.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
@@ -40,7 +39,7 @@ func _ready() -> void:
|
|||||||
Style.clabel(col, "Am Tisch", 20, Style.TEXT, "center", true)
|
Style.clabel(col, "Am Tisch", 20, Style.TEXT, "center", true)
|
||||||
list_label = Style.clabel(col, "", 16, Style.TEXT)
|
list_label = Style.clabel(col, "", 16, Style.TEXT)
|
||||||
Style.spacer(col, 6)
|
Style.spacer(col, 6)
|
||||||
Style.clabel(col, "KI-Gegner (nur Host)", 16, Style.MUTED)
|
Style.clabel(col, "KI-Gegner", 16, Style.MUTED)
|
||||||
var ar := Style.row(col, 10)
|
var ar := Style.row(col, 10)
|
||||||
ar.alignment = BoxContainer.ALIGNMENT_CENTER
|
ar.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
for n in range(0, 5):
|
for n in range(0, 5):
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ var b_single: Button
|
|||||||
var b_campaign: Button
|
var b_campaign: Button
|
||||||
var b_online: Button
|
var b_online: Button
|
||||||
var b_tutorial: Button
|
var b_tutorial: Button
|
||||||
var b_challenge: Button
|
|
||||||
var b_resume: Button
|
var b_resume: Button
|
||||||
var coins_label: Label
|
var coins_label: Label
|
||||||
var coll_label: Label
|
var coll_label: Label
|
||||||
@@ -44,7 +43,6 @@ func _ready() -> void:
|
|||||||
b_single = Style.cbutton(grid, "Einzelspieler", sz, func(): app.show_screen(OpponentSelectScreen.new()), Color("285a3c"))
|
b_single = Style.cbutton(grid, "Einzelspieler", sz, func(): app.show_screen(OpponentSelectScreen.new()), Color("285a3c"))
|
||||||
b_campaign = Style.cbutton(grid, "Kampagne", sz, func(): app.show_screen(CampaignScreen.new()), Color("285a3c"))
|
b_campaign = Style.cbutton(grid, "Kampagne", sz, func(): app.show_screen(CampaignScreen.new()), Color("285a3c"))
|
||||||
b_online = Style.cbutton(grid, "Netzwerkspiel", sz, func(): app.show_screen(LobbyScreen.new()), Color("285a3c"))
|
b_online = Style.cbutton(grid, "Netzwerkspiel", sz, func(): app.show_screen(LobbyScreen.new()), Color("285a3c"))
|
||||||
b_challenge = Style.cbutton(grid, "Herausforderung", sz, func(): app.show_screen(ChallengeScreen.new()), Color("285a3c"))
|
|
||||||
Style.cbutton(grid, "Booster öffnen", sz, func(): app.show_screen(BoosterScreen.new()))
|
Style.cbutton(grid, "Booster öffnen", sz, func(): app.show_screen(BoosterScreen.new()))
|
||||||
Style.cbutton(grid, "Deck bauen", sz, func(): app.show_screen(DeckScreen.new()))
|
Style.cbutton(grid, "Deck bauen", sz, func(): app.show_screen(DeckScreen.new()))
|
||||||
Style.cbutton(grid, "Werkstatt", sz, func(): app.show_screen(WorkshopScreen.new()))
|
Style.cbutton(grid, "Werkstatt", sz, func(): app.show_screen(WorkshopScreen.new()))
|
||||||
@@ -77,8 +75,6 @@ func refresh() -> void:
|
|||||||
b_campaign.disabled = not ok[0]
|
b_campaign.disabled = not ok[0]
|
||||||
b_tutorial.disabled = not ok[0]
|
b_tutorial.disabled = not ok[0]
|
||||||
b_online.disabled = not ok[0]
|
b_online.disabled = not ok[0]
|
||||||
b_challenge.disabled = not ok[0]
|
|
||||||
b_challenge.text = tr("Herausforderung") + (" ✓" if p.challenge_week == Challenge.week() else "")
|
|
||||||
var total := 0
|
var total := 0
|
||||||
for cid in p.collection:
|
for cid in p.collection:
|
||||||
total += p.collection[cid]
|
total += p.collection[cid]
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func _initialize() -> void:
|
|||||||
test_deck_umbenennen_und_auffuellen()
|
test_deck_umbenennen_und_auffuellen()
|
||||||
test_neue_fallen()
|
test_neue_fallen()
|
||||||
test_kapitel_zwei_und_merkmalwahl()
|
test_kapitel_zwei_und_merkmalwahl()
|
||||||
test_herausforderung_und_bestenliste()
|
test_bestenliste()
|
||||||
test_deckstaerke_und_schwierigkeit()
|
test_deckstaerke_und_schwierigkeit()
|
||||||
test_starter_gegen_passende_gegner()
|
test_starter_gegen_passende_gegner()
|
||||||
test_kurve_statistik_vorschlag_verbesserung()
|
test_kurve_statistik_vorschlag_verbesserung()
|
||||||
@@ -1145,32 +1145,19 @@ func test_kapitel_zwei_und_merkmalwahl() -> void:
|
|||||||
check(v.perk == "feuer_angriff" and v.campaign_stage == 20, "Version 5 gelesen")
|
check(v.perk == "feuer_angriff" and v.campaign_stage == 20, "Version 5 gelesen")
|
||||||
|
|
||||||
|
|
||||||
func test_herausforderung_und_bestenliste() -> void:
|
func test_bestenliste() -> void:
|
||||||
Profile.today_override = "2026-09-16"
|
|
||||||
var ch := Challenge.current()
|
|
||||||
check(ch.has("rules") and ch.n_opp >= 1, "Herausforderung der Woche")
|
|
||||||
var weeks := {}
|
|
||||||
for d in range(1, 43, 7):
|
|
||||||
Profile.today_override = "2026-%02d-%02d" % [1 + d / 28, 1 + d % 28]
|
|
||||||
weeks[Challenge.current().id] = true
|
|
||||||
check(weeks.size() >= 4, "Herausforderungen wechseln wöchentlich")
|
|
||||||
Profile.today_override = "2026-09-16"
|
Profile.today_override = "2026-09-16"
|
||||||
var mono := []
|
var mono := []
|
||||||
for i in 20:
|
for i in 20:
|
||||||
mono.append(Cards.by_id("w_reiter"))
|
mono.append(Cards.by_id("w_reiter"))
|
||||||
check(Challenge.deck_allowed(Challenge.LIST[5], Profile.starter().deck_cards()) != "", "Reine Lehre lehnt Zwei-Element-Deck ab")
|
|
||||||
check(Challenge.deck_allowed(Challenge.LIST[5], mono) == "", "Mono-Deck erlaubt")
|
|
||||||
var r := RandomNumberGenerator.new()
|
var r := RandomNumberGenerator.new()
|
||||||
r.seed = 4
|
r.seed = 4
|
||||||
var g := Game.new_battle(mono, 3, r, Challenge.LIST[0].rules)
|
var g := Game.new_battle(mono, 3, r, {"everyone_perk": "start_energie", "start_life": 20})
|
||||||
check(g.players.all(func(p): return p.life == 20 and "start_energie" in p.perks), "Eile: 20 Leben und Startenergie für alle")
|
check(g.players.all(func(p): return p.life == 20 and "start_energie" in p.perks), "Regelvariante: 20 Leben und Startenergie für alle")
|
||||||
check(g.players[0].max_energy == 3, "Startspieler hat 3 Energie")
|
check(g.players[0].max_energy == 3, "Startspieler hat 3 Energie")
|
||||||
var p := Profile.starter()
|
var p := Profile.starter()
|
||||||
var coins := p.coins
|
p.record_battle(true, 3, mono, -1, [], 7)
|
||||||
var reward := p.record_battle(true, 3, mono, -1, [], 7, Challenge.LIST[0])
|
p.record_battle(true, 3, mono, -1, [], 9)
|
||||||
check(reward == Challenge.LIST[0].reward and p.challenge_week == Challenge.week() and "wochenheld" in p.achievements, "Herausforderung belohnt")
|
|
||||||
reward = p.record_battle(true, 3, mono, -1, [], 9, Challenge.LIST[0])
|
|
||||||
check(reward == Profile.REWARD_WIN[3], "nur einmal pro Woche")
|
|
||||||
check(p.records.streak == 2 and p.records.best_streak == 2 and p.records.fastest_win == 7, "Serie und schnellster Sieg")
|
check(p.records.streak == 2 and p.records.best_streak == 2 and p.records.fastest_win == 7, "Serie und schnellster Sieg")
|
||||||
p.record_battle(false, 1, mono, -1, [], 12)
|
p.record_battle(false, 1, mono, -1, [], 12)
|
||||||
check(p.records.streak == 0 and p.records.best_streak == 2, "Niederlage bricht die Serie")
|
check(p.records.streak == 0 and p.records.best_streak == 2, "Niederlage bricht die Serie")
|
||||||
@@ -1211,7 +1198,7 @@ func test_deckstaerke_und_schwierigkeit() -> void:
|
|||||||
check(is_equal_approx(p.target_rating(), rs * 1.2) and p.difficulty().smart, "Schwer: 1,2-fach mit Vorausschau-KI")
|
check(is_equal_approx(p.target_rating(), rs * 1.2) and p.difficulty().smart, "Schwer: 1,2-fach mit Vorausschau-KI")
|
||||||
p.settings.difficulty = "leicht"
|
p.settings.difficulty = "leicht"
|
||||||
check(not p.difficulty().smart and is_equal_approx(p.target_rating(), rs * 0.9), "Leicht: einfache KI, 0,9-fach")
|
check(not p.difficulty().smart and is_equal_approx(p.target_rating(), rs * 0.9), "Leicht: einfache KI, 0,9-fach")
|
||||||
var reward := p.record_battle(true, 1, starter, -1, [], 5, {}, p.difficulty().reward)
|
var reward := p.record_battle(true, 1, starter, -1, [], 5, p.difficulty().reward)
|
||||||
check(reward == int(round(Profile.REWARD_WIN[1] * 0.75)), "Leicht: weniger Münzen")
|
check(reward == int(round(Profile.REWARD_WIN[1] * 0.75)), "Leicht: weniger Münzen")
|
||||||
var v := Profile.from_dict(JSON.parse_string(JSON.stringify(p.to_dict())))
|
var v := Profile.from_dict(JSON.parse_string(JSON.stringify(p.to_dict())))
|
||||||
check(v.settings.difficulty == "leicht", "Schwierigkeit gespeichert")
|
check(v.settings.difficulty == "leicht", "Schwierigkeit gespeichert")
|
||||||
@@ -1268,17 +1255,17 @@ func test_kurve_statistik_vorschlag_verbesserung() -> void:
|
|||||||
var mono := starter
|
var mono := starter
|
||||||
check(p.suggest_difficulty() == "", "kein Vorschlag am Anfang")
|
check(p.suggest_difficulty() == "", "kein Vorschlag am Anfang")
|
||||||
for i in 3:
|
for i in 3:
|
||||||
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 4)
|
p.record_battle(true, 1, mono, -1, [], 6, 1.0, 4)
|
||||||
check(p.suggest_difficulty() == "", "drei knappe Siege: noch kein Vorschlag")
|
check(p.suggest_difficulty() == "", "drei knappe Siege: noch kein Vorschlag")
|
||||||
p.records.streak = 0
|
p.records.streak = 0
|
||||||
p.records.margins = []
|
p.records.margins = []
|
||||||
for i in 3:
|
for i in 3:
|
||||||
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 20)
|
p.record_battle(true, 1, mono, -1, [], 6, 1.0, 20)
|
||||||
check(p.suggest_difficulty() == "schwer" and p.notices.any(func(n): return n.begins_with("Tipp: 3 Siege")), "drei deutliche Siege: Schwer vorschlagen")
|
check(p.suggest_difficulty() == "schwer" and p.notices.any(func(n): return n.begins_with("Tipp: 3 Siege")), "drei deutliche Siege: Schwer vorschlagen")
|
||||||
p.records.streak = 0
|
p.records.streak = 0
|
||||||
p.records.margins = []
|
p.records.margins = []
|
||||||
for i in 5:
|
for i in 5:
|
||||||
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 2)
|
p.record_battle(true, 1, mono, -1, [], 6, 1.0, 2)
|
||||||
check(p.suggest_difficulty() == "schwer" and p.records.margins.size() == 5, "fünf Siege egal wie knapp")
|
check(p.suggest_difficulty() == "schwer" and p.records.margins.size() == 5, "fünf Siege egal wie knapp")
|
||||||
p.settings.difficulty = "schwer"
|
p.settings.difficulty = "schwer"
|
||||||
p.take_notices()
|
p.take_notices()
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func _ready() -> void:
|
|||||||
await frames(3)
|
await frames(3)
|
||||||
var fast := OS.get_environment("DINGER_FAST") != ""
|
var fast := OS.get_environment("DINGER_FAST") != ""
|
||||||
var suite := [test_menu_und_regeln, test_gamepad_menue, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
|
var suite := [test_menu_und_regeln, test_gamepad_menue, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
|
||||||
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2, test_herausforderung,
|
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2,
|
||||||
test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster]
|
test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster]
|
||||||
if not fast:
|
if not fast:
|
||||||
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
|
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
|
||||||
@@ -695,26 +695,6 @@ func test_profil_zuruecksetzen() -> void:
|
|||||||
check(Profile.load_or_new(SAVE).coins == Profile.START_COINS, "zurückgesetzt gespeichert")
|
check(Profile.load_or_new(SAVE).coins == Profile.START_COINS, "zurückgesetzt gespeichert")
|
||||||
|
|
||||||
|
|
||||||
func test_herausforderung() -> void:
|
|
||||||
await click(find_button("Herausforderung"))
|
|
||||||
var scr = screen()
|
|
||||||
check(scr is ChallengeScreen, "Herausforderung geöffnet")
|
|
||||||
var ch := Challenge.current()
|
|
||||||
var allowed := Challenge.deck_allowed(ch, main.profile.deck_cards()) == ""
|
|
||||||
check(find_button("Antreten").disabled == not allowed, "Antreten je nach Deck")
|
|
||||||
await shot("28_herausforderung")
|
|
||||||
if allowed:
|
|
||||||
await click(find_button("Antreten"))
|
|
||||||
var b = screen()
|
|
||||||
check(b is BattleScreen and b.challenge == ch and b.game.opponents.size() == ch.n_opp, "Herausforderungskampf")
|
|
||||||
var everyone: String = ch.rules.get("everyone_perk", "")
|
|
||||||
check(everyone == "" or b.game.players.all(func(p): return everyone in p.perks), "Regel gilt für alle")
|
|
||||||
await give_up()
|
|
||||||
else:
|
|
||||||
await click(find_button("Zurück"))
|
|
||||||
check(screen() is MenuScreen, "zurück im Menü")
|
|
||||||
|
|
||||||
|
|
||||||
func test_sprache() -> void:
|
func test_sprache() -> void:
|
||||||
await click(find_button("Einstellungen"))
|
await click(find_button("Einstellungen"))
|
||||||
var scr = screen()
|
var scr = screen()
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ Hand tauschen?,Swap cards?
|
|||||||
Klicke Karten an die du zurücklegen willst. Einmal pro Partie.,Click the cards you want to put back. Once per game.
|
Klicke Karten an die du zurücklegen willst. Einmal pro Partie.,Click the cards you want to put back. Once per game.
|
||||||
Fallen,Traps
|
Fallen,Traps
|
||||||
Du bist dran,Your turn
|
Du bist dran,Your turn
|
||||||
Herausforderung,Challenge
|
|
||||||
Antreten,Enter
|
Antreten,Enter
|
||||||
Statistik exportieren,Export statistics
|
Statistik exportieren,Export statistics
|
||||||
Gegnerzüge überspringen,Skip AI turns
|
Gegnerzüge überspringen,Skip AI turns
|
||||||
@@ -209,3 +208,4 @@ Starten,Start
|
|||||||
1 Gegner,1 opponent
|
1 Gegner,1 opponent
|
||||||
%d Gegner,%d opponents
|
%d Gegner,%d opponents
|
||||||
Übernehmen,Apply
|
Übernehmen,Apply
|
||||||
|
KI-Gegner,AI opponents
|
||||||
|
|||||||
|
Binary file not shown.
Reference in New Issue
Block a user