Anleitung mit Hervorhebungen, Rückgängig, Kapitel 2, Herausforderungen, Fallen, Wiederverbinden, CI
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
@@ -2,27 +2,30 @@ class_name AchievementsScreen
|
||||
extends Control
|
||||
|
||||
var app
|
||||
var export_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var p: Profile = app.profile
|
||||
var col := Style.column(self, 900, 20, 5)
|
||||
Style.clabel(col, "Erfolge", 36, Style.GOLD, "center", true)
|
||||
var col := Style.column(self, 900, 12, 4)
|
||||
Style.clabel(col, "Erfolge", 32, Style.GOLD, "center", true)
|
||||
var st: Dictionary = p.stats
|
||||
Style.clabel(col, "Kämpfe %d · Siege %d · Booster %d · Kampagne %d/%d" % [
|
||||
st.games, st.wins, st.boosters, p.campaign_stage, Campaign.STAGES.size()], 16, Style.MUTED)
|
||||
st.games, st.wins, st.boosters, p.campaign_stage, Campaign.total()], 16, Style.MUTED)
|
||||
var task := p.daily_task()
|
||||
var daily_text: String = ("Tagesaufgabe erledigt: %s ✓" % task.text) if p.daily.done else \
|
||||
"Tagesaufgabe: %s (%d/%d) · +%d Münzen" % [task.text, p.daily.progress, task.goal, task.reward]
|
||||
Style.clabel(col, daily_text, 16, Style.BLUE)
|
||||
Style.spacer(col, 8)
|
||||
Style.clabel(col, "Serie: %d (beste %d) · Schnellster Sieg: %s" % [p.records.streak, p.records.best_streak,
|
||||
("%d Züge" % p.records.fastest_win) if p.records.fastest_win > 0 else "–"], 15, Style.MUTED)
|
||||
Style.spacer(col, 4)
|
||||
for a in Profile.ACHIEVEMENTS:
|
||||
var done: bool = a.id in p.achievements
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL if done else Style.darker(Style.PANEL, 0.8), 6))
|
||||
col.add_child(panel)
|
||||
var r := Style.row(panel, 12)
|
||||
r.custom_minimum_size = Vector2(0, 42)
|
||||
r.custom_minimum_size = Vector2(0, 36)
|
||||
Style.clabel(r, "✓" if done else "·", 22, Style.GREEN if done else Style.MUTED, "center", true, 40)
|
||||
Style.clabel(r, a.name, 18, Style.TEXT if done else Style.MUTED, "left", true, 250)
|
||||
Style.clabel(r, a.text, 15, Style.MUTED, "left", false, 380)
|
||||
@@ -34,10 +37,18 @@ func _ready() -> void:
|
||||
for row in top:
|
||||
parts.append("%s %d/%d" % [row.card.name, row.wins, row.games])
|
||||
Style.clabel(col, "Beste Karten (Siege/Partien): " + " · ".join(parts), 14, Style.MUTED)
|
||||
Style.spacer(col, 6)
|
||||
var br := Style.row(col)
|
||||
export_label = Style.clabel(col, "", 13, Style.GREEN)
|
||||
Style.spacer(col, 2)
|
||||
var br := Style.row(col, 16)
|
||||
br.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
Style.cbutton(br, "Zurück", Vector2(160, 44), func(): app.show_screen(MenuScreen.new()))
|
||||
Style.cbutton(br, "Statistik exportieren", Vector2(220, 40), export_stats)
|
||||
Style.cbutton(br, "Zurück", Vector2(160, 40), func(): app.show_screen(MenuScreen.new()))
|
||||
|
||||
|
||||
func export_stats() -> void:
|
||||
var path := "user://statistik.csv"
|
||||
var err: String = app.profile.export_stats(path)
|
||||
export_label.text = ("Gespeichert: " + ProjectSettings.globalize_path(path)) if err == "" else err
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
||||
@@ -26,6 +26,11 @@ var preview_timer := 0.0
|
||||
var played_ids: Array = [] # vom Menschen gespielte Karten (Statistik)
|
||||
var mulligan_selected: Array = []
|
||||
var tutorial_step := 0
|
||||
var challenge := {} # wöchentliche Herausforderung oder leer
|
||||
var skip_ai := false # Gegnerzüge im Schnelldurchlauf
|
||||
var undo_state: Game = null # Zustand vor der letzten rückgängig machbaren Aktion
|
||||
var undo_log_len := 0
|
||||
var undo_t := 0.0
|
||||
|
||||
# Ansichten (auch für Tests)
|
||||
var tiles := {}
|
||||
@@ -70,6 +75,22 @@ var tutorial_panel: Panel
|
||||
var tutorial_text: RichTextLabel
|
||||
var b_tut_next: Button
|
||||
var wait_label: Label
|
||||
var b_skip: Button
|
||||
var b_undo: Button
|
||||
var highlight_layer: Control
|
||||
|
||||
|
||||
## Pulsierender Rahmen für die Anleitung.
|
||||
class Highlight extends Control:
|
||||
var t := 0.0
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
t += delta
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
var a := 0.55 + 0.45 * sin(t * 6.0)
|
||||
Style.border(self, Rect2(Vector2.ZERO, size), Color(Style.GOLD, a), 4, 8)
|
||||
|
||||
const TUTORIAL_STEPS := [
|
||||
["Willkommen! Unten liegt deine Hand. Jede Karte kostet Energie (◆), du hast im ersten Zug 1. Spiele ein Wesen mit Kosten 1 per Klick.",
|
||||
@@ -107,6 +128,12 @@ static func guided() -> BattleScreen:
|
||||
return b
|
||||
|
||||
|
||||
static func weekly(ch: Dictionary) -> BattleScreen:
|
||||
var b := BattleScreen.new(int(ch.n_opp), -1)
|
||||
b.challenge = ch
|
||||
return b
|
||||
|
||||
|
||||
func is_hotseat() -> bool:
|
||||
return game != null and game.humans.size() > 1 and online_seat < 0
|
||||
|
||||
@@ -146,14 +173,24 @@ func _ready() -> void:
|
||||
var opps := Campaign.opponents(0, r)
|
||||
opps[0].perks = []
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), opps, r, true)
|
||||
elif not challenge.is_empty():
|
||||
game = Game.new_battle(app.profile.deck_cards(), n_opp, null, challenge.rules)
|
||||
elif stage >= 0:
|
||||
var r := Game.make_rng()
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), Campaign.opponents(stage, r), r)
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), Campaign.opponents(stage, r), r, false, own_rules())
|
||||
else:
|
||||
game = Game.new_battle(app.profile.deck_cards(), n_opp)
|
||||
game = Game.new_battle(app.profile.deck_cards(), n_opp, null, own_rules())
|
||||
_begin()
|
||||
|
||||
|
||||
## Eigenes Merkmal des Spielers (nach Kapitel 1 wählbar).
|
||||
func own_rules() -> Dictionary:
|
||||
var p: Profile = app.profile
|
||||
if p.perk != "" and p.perk_unlocked():
|
||||
return {"human_perks": [p.perk]}
|
||||
return {}
|
||||
|
||||
|
||||
func _begin() -> void:
|
||||
n_opp = game.players.size() - 1
|
||||
if is_online():
|
||||
@@ -207,6 +244,11 @@ func _build_static() -> void:
|
||||
status_label = Style.label(self, "", 15, Style.MUTED, Rect2(349, 455, 400, 24))
|
||||
msg_label = Style.label(self, "", 16, Style.ORANGE, Rect2(600, 455, 331, 24), "right", true)
|
||||
b_end = Style.button(self, "Zug beenden", Rect2(1005, 527, 210, 48), end_turn, Color("285a3c"))
|
||||
b_skip = Style.button(self, "Gegnerzüge überspringen", Rect2(1005, 527, 210, 48), func(): skip_ai = true)
|
||||
b_skip.add_theme_font_size_override("font_size", 14)
|
||||
b_skip.visible = false
|
||||
b_undo = Style.button(self, "Rückgängig", Rect2(170, 553, 150, 40), undo)
|
||||
b_undo.visible = false
|
||||
wait_label = Style.label(self, "Warte auf den Host …", 22, Style.MUTED, Rect2(0, 300, 1280, 40), "center")
|
||||
wait_label.visible = false
|
||||
hero_layer = _layer()
|
||||
@@ -214,6 +256,7 @@ func _build_static() -> void:
|
||||
trap_layer = _layer()
|
||||
hand_layer = _layer()
|
||||
preview_holder = _layer()
|
||||
highlight_layer = _layer()
|
||||
zoom_layer = _layer()
|
||||
fx_layer = _layer()
|
||||
# Mulligan
|
||||
@@ -380,6 +423,11 @@ func _input(event: InputEvent) -> void:
|
||||
KEY_SPACE:
|
||||
if my_turn() and not any_overlay():
|
||||
end_turn()
|
||||
elif b_skip.visible:
|
||||
skip_ai = true
|
||||
KEY_U:
|
||||
if b_undo.visible:
|
||||
undo()
|
||||
KEY_ENTER, KEY_KP_ENTER:
|
||||
if handover.visible:
|
||||
take_over()
|
||||
@@ -414,6 +462,12 @@ func _process(delta: float) -> void:
|
||||
preview_timer -= delta
|
||||
if preview_timer <= 0.0:
|
||||
show_preview(null, false)
|
||||
if undo_t > 0.0:
|
||||
undo_t -= delta
|
||||
if undo_t <= 0.0:
|
||||
clear_undo()
|
||||
if is_online() and net.is_host:
|
||||
net.tick()
|
||||
if game.winner != "":
|
||||
if not ended:
|
||||
ended = true
|
||||
@@ -431,19 +485,24 @@ func _process(delta: float) -> void:
|
||||
return
|
||||
if is_client():
|
||||
return
|
||||
ai_timer -= delta
|
||||
if skip_ai:
|
||||
ai_timer = 0.0
|
||||
else:
|
||||
ai_timer -= delta
|
||||
if ai_timer > 0:
|
||||
return
|
||||
var action := AI.choose_action(game, cur)
|
||||
AI.apply_action(game, cur, action)
|
||||
ai_timer = ai_delay * (0.6 if action.type == "end" else 1.0)
|
||||
if game.current().is_human:
|
||||
skip_ai = false
|
||||
after_action()
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
var won := game.winner == "human"
|
||||
if counts_for_profile():
|
||||
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids)
|
||||
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids, game.turn, challenge)
|
||||
if tutorial:
|
||||
app.profile.tutorial_done = true
|
||||
app.save()
|
||||
@@ -480,15 +539,52 @@ func play(card: Cards.Card, target) -> void:
|
||||
if is_client():
|
||||
net.send_action({"type": "play", "hand": perspective.hand.find(card), "target": Net.ref_dict(target) if target != null else null})
|
||||
return
|
||||
var before: Game = game.clone() if card.is_creature() and not is_online() else null
|
||||
var log_len := game.log.size()
|
||||
var err := game.play_card(perspective, card, target)
|
||||
if err != "":
|
||||
say(err)
|
||||
say(tr(err))
|
||||
return
|
||||
# Rückgängig nur, wenn nichts weiter passiert ist (keine Effekte, Kombos, Fallen)
|
||||
if before != null and game.events.size() == 1 and game.events[0].type == "play":
|
||||
undo_state = before
|
||||
undo_log_len = log_len
|
||||
undo_t = 6.0
|
||||
else:
|
||||
clear_undo()
|
||||
|
||||
|
||||
func clear_undo() -> void:
|
||||
undo_state = null
|
||||
undo_t = 0.0
|
||||
b_undo.visible = false
|
||||
|
||||
|
||||
## Nimmt das letzte Ausspielen eines Wesens zurück (nur solange nichts Verdecktes offenbart wurde).
|
||||
func undo() -> void:
|
||||
if undo_state == null:
|
||||
return
|
||||
var idx := game.players.find(perspective)
|
||||
var restored := undo_state
|
||||
restored.quiet = false
|
||||
restored.rng = game.rng
|
||||
restored.log = game.log.slice(0, undo_log_len)
|
||||
restored.say("Rückgängig.")
|
||||
game = restored
|
||||
perspective = game.players[idx]
|
||||
if is_online() and net.is_host:
|
||||
net.game = game
|
||||
clear_undo()
|
||||
cancel()
|
||||
build_hero_boxes()
|
||||
after_action()
|
||||
|
||||
|
||||
func end_turn() -> void:
|
||||
if not my_turn():
|
||||
return
|
||||
cancel()
|
||||
clear_undo()
|
||||
zoom_player = null
|
||||
if is_client():
|
||||
net.send_action({"type": "end"})
|
||||
@@ -552,7 +648,7 @@ func on_click(kind: String, obj) -> void:
|
||||
if obj in g.valid_targets(perspective, card.target_type()):
|
||||
play(card, obj)
|
||||
else:
|
||||
say("Abgebrochen")
|
||||
say(tr("Abgebrochen"))
|
||||
zoom_player = null
|
||||
after_action()
|
||||
return
|
||||
@@ -560,12 +656,13 @@ func on_click(kind: String, obj) -> void:
|
||||
var cr = selected
|
||||
selected = null
|
||||
if kind == "enemy" or kind == "hero":
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "attack", "uid": cr.uid, "target": Net.ref_dict(obj)})
|
||||
else:
|
||||
var err := g.attack(cr, obj)
|
||||
if err != "":
|
||||
say(err)
|
||||
say(tr(err))
|
||||
zoom_player = null
|
||||
after_action()
|
||||
return
|
||||
@@ -573,18 +670,18 @@ func on_click(kind: String, obj) -> void:
|
||||
"hand":
|
||||
var ok := g.can_play(perspective, obj)
|
||||
if not ok[0]:
|
||||
say(ok[1])
|
||||
say(tr(ok[1]))
|
||||
elif obj.target_type() != "" and not g.valid_targets(perspective, obj.target_type()).is_empty():
|
||||
pending = obj
|
||||
say("Wähle ein Ziel")
|
||||
say(tr("Wähle ein Ziel"))
|
||||
else:
|
||||
play(obj, null)
|
||||
"own":
|
||||
if obj.can_attack():
|
||||
selected = obj
|
||||
say("Wähle ein Angriffsziel")
|
||||
say(tr("Wähle ein Angriffsziel"))
|
||||
else:
|
||||
say("%s kann nicht angreifen" % obj.name)
|
||||
say(tr("%s kann nicht angreifen") % obj.name)
|
||||
after_action()
|
||||
|
||||
|
||||
@@ -739,6 +836,51 @@ func tutorial_next() -> void:
|
||||
refresh()
|
||||
|
||||
|
||||
## Hebt hervor, was der aktuelle Schritt verlangt.
|
||||
func _tutorial_highlights(mine: bool) -> void:
|
||||
Style.clear(highlight_layer)
|
||||
if not mine or any_overlay():
|
||||
return
|
||||
var g := game
|
||||
var h := perspective
|
||||
var rects: Array[Rect2] = []
|
||||
match TUTORIAL_STEPS[tutorial_step][1]:
|
||||
"played_creature":
|
||||
for v in hand_views:
|
||||
if v.card.is_creature() and g.can_play(h, v.card)[0]:
|
||||
rects.append(Rect2(v.position, v.size))
|
||||
"turn_passed":
|
||||
rects.append(b_end.get_rect())
|
||||
"attacked":
|
||||
for v in own_views:
|
||||
if v.creature.can_attack():
|
||||
rects.append(Rect2(v.slot, v.size))
|
||||
if not own_views.is_empty() and own_views.any(func(v): return v.creature.can_attack()):
|
||||
for o in others():
|
||||
if o.alive() and hero_panels.has(o):
|
||||
rects.append(hero_panels[o].get_rect())
|
||||
"resonance":
|
||||
for v in hand_views:
|
||||
var c: Cards.Card = v.card
|
||||
if c.is_creature() and g.can_play(h, c)[0] and h.board.any(func(b): return b.card.elements.any(func(e): return e in c.elements)):
|
||||
rects.append(Rect2(v.position, v.size))
|
||||
"combo":
|
||||
for v in hand_views:
|
||||
var c: Cards.Card = v.card
|
||||
if c.is_creature() and g.can_play(h, c)[0] and h.board.any(func(b): return b.card.elements.any(func(e): return not (e in c.elements))):
|
||||
rects.append(Rect2(v.position, v.size))
|
||||
"spell":
|
||||
for v in hand_views:
|
||||
if v.card.kind == "Zauber" and g.can_play(h, v.card)[0]:
|
||||
rects.append(Rect2(v.position, v.size))
|
||||
for r in rects:
|
||||
var hl := Highlight.new()
|
||||
hl.position = r.position - Vector2(4, 4)
|
||||
hl.size = r.size + Vector2(8, 8)
|
||||
hl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
highlight_layer.add_child(hl)
|
||||
|
||||
|
||||
func _tutorial_refresh() -> void:
|
||||
while tutorial_step < TUTORIAL_STEPS.size() - 1 and tutorial_condition(TUTORIAL_STEPS[tutorial_step][1]):
|
||||
tutorial_step += 1
|
||||
@@ -876,8 +1018,12 @@ func refresh() -> void:
|
||||
life_label.text = "♥ %d" % maxi(h.life, 0)
|
||||
energy_label.text = "◆ %d/%d" % [h.energy, h.max_energy]
|
||||
deck_label.text = "Deck %d" % h.deck.size()
|
||||
status_label.text = "Zug %d · %s" % [g.turn, "Du bist dran" if mine else g.current().name + " ist dran …"]
|
||||
status_label.text = (tr("Zug %d · Du bist dran") % g.turn) if mine else (tr("Zug %d · %s ist dran …") % [g.turn, g.current().name])
|
||||
b_end.disabled = not mine
|
||||
var ai_running: bool = g.winner == "" and not g.current().is_human and not is_client()
|
||||
b_skip.visible = ai_running
|
||||
b_end.visible = not ai_running
|
||||
b_undo.visible = undo_state != null and mine
|
||||
var lines: Array[String] = []
|
||||
var start := maxi(0, g.log.size() - 60)
|
||||
for i in range(start, g.log.size()):
|
||||
@@ -891,6 +1037,7 @@ func refresh() -> void:
|
||||
log_box.text = "\n".join(lines)
|
||||
if tutorial:
|
||||
_tutorial_refresh()
|
||||
_tutorial_highlights(mine)
|
||||
|
||||
Style.clear(zoom_layer)
|
||||
if zoom_player != null and zoom_player.alive():
|
||||
|
||||
@@ -2,52 +2,88 @@ class_name CampaignScreen
|
||||
extends Control
|
||||
|
||||
var app
|
||||
var chapter := 1
|
||||
var perk_box: OptionButton
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var p: Profile = app.profile
|
||||
var col := Style.column(self, 1000, 24, 6)
|
||||
Style.clabel(col, "Kampagne", 36, Style.GOLD, "center", true)
|
||||
Style.clabel(col, "Zehn Magier warten. Jede Stufe bringt mehr Gegner an den Tisch, alle nach denselben Regeln.", 16, Style.MUTED)
|
||||
Style.spacer(col, 6)
|
||||
for i in Campaign.STAGES.size():
|
||||
var st: Dictionary = Campaign.STAGES[i]
|
||||
chapter = 2 if p.campaign_stage >= Campaign.STAGES.size() else 1
|
||||
build()
|
||||
|
||||
|
||||
func build() -> void:
|
||||
Style.clear(self)
|
||||
var p: Profile = app.profile
|
||||
var col := Style.column(self, 1000, 16, 5)
|
||||
Style.clabel(col, "Kampagne", 34, Style.GOLD, "center", true)
|
||||
var tabs := Style.row(col, 10)
|
||||
tabs.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
var b1 := Style.cbutton(tabs, "Kapitel 1", Vector2(160, 36), func(): set_chapter(1), Color("285a3c") if chapter == 1 else null)
|
||||
var b2 := Style.cbutton(tabs, "Kapitel 2", Vector2(160, 36), func(): set_chapter(2), Color("285a3c") if chapter == 2 else null)
|
||||
b2.disabled = p.campaign_stage < Campaign.STAGES.size()
|
||||
if p.perk_unlocked():
|
||||
var pr := Style.row(tabs, 8)
|
||||
Style.clabel(pr, "Dein Merkmal:", 15, Style.MUTED, "right", false, 110)
|
||||
perk_box = OptionButton.new()
|
||||
perk_box.custom_minimum_size = Vector2(300, 36)
|
||||
perk_box.add_item("keins")
|
||||
var keys: Array = Game.PERKS.keys()
|
||||
for k in keys:
|
||||
perk_box.add_item(Game.PERKS[k])
|
||||
perk_box.select(keys.find(p.perk) + 1 if p.perk != "" else 0)
|
||||
perk_box.item_selected.connect(func(i: int):
|
||||
p.perk = "" if i == 0 else keys[i - 1]
|
||||
app.save())
|
||||
pr.add_child(perk_box)
|
||||
Style.clabel(col, "Zehn Magier je Kapitel. Jede Stufe bringt mehr Gegner an den Tisch. Bosse haben Merkmale (★)." if chapter == 1
|
||||
else "Die Magier kehren mit zwei Merkmalen und größerem Gefolge zurück.", 14, Style.MUTED)
|
||||
var offset := 0 if chapter == 1 else Campaign.STAGES.size()
|
||||
for k in Campaign.STAGES.size():
|
||||
var i := offset + k
|
||||
var st := Campaign.stage(i)
|
||||
var done := i < p.campaign_stage
|
||||
var next := i == p.campaign_stage
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL if not done else Style.darker(Style.PANEL, 0.8), 6,
|
||||
Style.GOLD if next else null, 2 if next else 0))
|
||||
col.add_child(panel)
|
||||
var r := Style.row(panel, 12)
|
||||
var r := Style.row(panel, 10)
|
||||
r.custom_minimum_size = Vector2(0, 50)
|
||||
Style.clabel(r, str(i + 1), 20, Style.GOLD if next else Style.MUTED, "center", true, 40)
|
||||
Style.clabel(r, "%s · %s" % [st.name, st.title], 17, Style.TEXT if not done else Style.MUTED, "left", true, 300)
|
||||
Style.clabel(r, str(i + 1), 18, Style.GOLD if next else Style.MUTED, "center", true, 36)
|
||||
Style.clabel(r, "%s · %s" % [st.name, st.title], 16, Style.TEXT if not done else Style.MUTED, "left", true, 290)
|
||||
var dots := Control.new()
|
||||
dots.custom_minimum_size = Vector2(60, 0)
|
||||
dots.custom_minimum_size = Vector2(50, 0)
|
||||
dots.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var elems: Array = st.elements
|
||||
dots.draw.connect(func():
|
||||
for k in elems.size():
|
||||
dots.draw_circle(Vector2(10 + k * 18, dots.size.y / 2), 7, Cards.ELEMENT_COLORS[elems[k]]))
|
||||
for m in elems.size():
|
||||
dots.draw_circle(Vector2(10 + m * 18, dots.size.y / 2), 7, Cards.ELEMENT_COLORS[elems[m]]))
|
||||
r.add_child(dots)
|
||||
var table: String = "Duell" if st.extra == 0 else "%d am Tisch" % (st.extra + 2)
|
||||
Style.clabel(r, "%s · %d Münzen\n★ %s" % [table, st.reward, Game.PERKS[st.perk]], 13, Style.MUTED, "left", false, 300)
|
||||
var extra: int = mini(st.extra, Game.MAX_OPPONENTS - 1)
|
||||
var table: String = "Duell" if extra == 0 else "%d am Tisch" % (extra + 2)
|
||||
var perks_text: String = ", ".join(st.perks.map(func(pk): return Game.PERKS[pk]))
|
||||
Style.clabel(r, "%s · %d Münzen\n★ %s" % [table, st.reward, perks_text], 12, Style.MUTED, "left", false, 360)
|
||||
if done:
|
||||
Style.clabel(r, "abgeschlossen", 14, Style.GREEN, "right", false, 110)
|
||||
Style.cbutton(r, "Wiederholen", Vector2(140, 38), _start.bind(i))
|
||||
Style.clabel(r, "abgeschlossen", 13, Style.GREEN, "right", false, 100)
|
||||
Style.cbutton(r, "Wiederholen", Vector2(130, 36), _start.bind(i))
|
||||
elif next:
|
||||
Style.clabel(r, "", 14, Style.GREEN, "right", false, 110)
|
||||
Style.cbutton(r, "Kämpfen", Vector2(140, 38), _start.bind(i), Color("285a3c"))
|
||||
Style.clabel(r, "", 13, Style.GREEN, "right", false, 100)
|
||||
Style.cbutton(r, "Kämpfen", Vector2(130, 36), _start.bind(i), Color("285a3c"))
|
||||
else:
|
||||
Style.clabel(r, "", 14, Style.GREEN, "right", false, 110)
|
||||
var b := Style.cbutton(r, "Gesperrt", Vector2(140, 38), func(): pass)
|
||||
Style.clabel(r, "", 13, Style.GREEN, "right", false, 100)
|
||||
var b := Style.cbutton(r, "Gesperrt", Vector2(130, 36), func(): pass)
|
||||
b.disabled = true
|
||||
if p.campaign_stage >= Campaign.STAGES.size():
|
||||
Style.clabel(col, "Kampagne abgeschlossen. Der Archimagier ist besiegt!", 18, Style.GREEN, "center", true)
|
||||
Style.spacer(col, 4)
|
||||
if p.campaign_stage >= Campaign.total():
|
||||
Style.clabel(col, "Beide Kapitel abgeschlossen. Der Archimagier ist endgültig besiegt!", 16, Style.GREEN, "center", true)
|
||||
var br := Style.row(col)
|
||||
br.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
Style.cbutton(br, "Zurück", Vector2(160, 44), func(): app.show_screen(MenuScreen.new()))
|
||||
Style.cbutton(br, "Zurück", Vector2(160, 40), func(): app.show_screen(MenuScreen.new()))
|
||||
|
||||
|
||||
func set_chapter(c: int) -> void:
|
||||
chapter = c
|
||||
build()
|
||||
|
||||
|
||||
func _start(stage: int) -> void:
|
||||
|
||||
42
scripts/ui/challenge_screen.gd
Normal file
42
scripts/ui/challenge_screen.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
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
scripts/ui/challenge_screen.gd.uid
Normal file
1
scripts/ui/challenge_screen.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cxheynenqemse
|
||||
@@ -7,6 +7,7 @@ var b_campaign: Button
|
||||
var b_hotseat: Button
|
||||
var b_online: Button
|
||||
var b_tutorial: Button
|
||||
var b_challenge: Button
|
||||
var coins_label: Label
|
||||
var coll_label: Label
|
||||
var deck_label: Label
|
||||
@@ -40,6 +41,7 @@ func _ready() -> void:
|
||||
b_campaign = Style.cbutton(grid, "Kampagne", sz, func(): app.show_screen(CampaignScreen.new()), Color("285a3c"))
|
||||
b_hotseat = Style.cbutton(grid, "Lokal zu mehreren", sz, func(): app.show_screen(HotseatScreen.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, "Deck bauen", sz, func(): app.show_screen(DeckScreen.new()))
|
||||
Style.cbutton(grid, "Werkstatt", sz, func(): app.show_screen(WorkshopScreen.new()))
|
||||
@@ -61,20 +63,22 @@ func refresh() -> void:
|
||||
b_campaign.disabled = not ok[0]
|
||||
b_tutorial.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
|
||||
for cid in p.collection:
|
||||
total += p.collection[cid]
|
||||
coins_label.text = "Münzen: %d · Staub: %d" % [p.coins, p.dust]
|
||||
coll_label.text = "Sammlung: %d von %d Karten (%d insgesamt) · Kampagne: %d/%d" % [
|
||||
p.distinct_owned(), Cards.all().size(), total, p.campaign_stage, Campaign.STAGES.size()]
|
||||
coins_label.text = tr("Münzen: %d · Staub: %d") % [p.coins, p.dust]
|
||||
coll_label.text = tr("Sammlung: %d von %d Karten (%d insgesamt) · Kampagne: %d/%d") % [
|
||||
p.distinct_owned(), Cards.all().size(), total, p.campaign_stage, Campaign.total()]
|
||||
var elems := Game.deck_elements(p.deck_cards())
|
||||
deck_label.text = "Aktives Deck: %s (%s)" % [p.deck_name(), ", ".join(elems) if not elems.is_empty() else "leer"]
|
||||
deck_label.text = tr("Aktives Deck: %s (%s)") % [p.deck_name(), ", ".join(elems) if not elems.is_empty() else tr("leer")]
|
||||
var task := p.daily_task()
|
||||
if p.daily.done:
|
||||
daily_label.text = "Tagesaufgabe erledigt: %s ✓" % task.text
|
||||
daily_label.text = tr("Tagesaufgabe erledigt: %s ✓") % task.text
|
||||
else:
|
||||
daily_label.text = "Tagesaufgabe: %s (%d/%d) · +%d Münzen" % [task.text, p.daily.progress, task.goal, task.reward]
|
||||
warn_label.text = "" if ok[0] else "Aktives Deck nicht spielbar: " + ok[1]
|
||||
daily_label.text = tr("Tagesaufgabe: %s (%d/%d) · +%d Münzen") % [task.text, p.daily.progress, task.goal, task.reward]
|
||||
warn_label.text = "" if ok[0] else tr("Aktives Deck nicht spielbar: ") + tr(ok[1])
|
||||
if ok[0] and not p.tutorial_done:
|
||||
warn_label.text = "Neu hier? Die Anleitung erklärt Energie, Angriffe, Resonanz und Feldkombos."
|
||||
warn_label.add_theme_color_override("font_color", Style.GOLD)
|
||||
|
||||
Reference in New Issue
Block a user