Anleitung 2, Gamepad, große Schrift, Profil-Reset, Würfel-Limit, Tipp nach Spielverlauf, KI-Zeitbudget, Deck-Warnung, Gischtläufer, Kampfbildschirm aufgeteilt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
@@ -24,8 +24,18 @@ var ended := false
|
||||
var zoom_player = null
|
||||
var preview_timer := 0.0
|
||||
var played_ids: Array = [] # vom Menschen gespielte Karten (Statistik)
|
||||
var mulligan_selected: Array = []
|
||||
var tutorial_step := 0
|
||||
var guide: BattleTutorial
|
||||
var tutorial_part := 1
|
||||
var tutorial_step: int:
|
||||
get:
|
||||
return guide.step if guide != null else 0
|
||||
var pad: BattleGamepad
|
||||
var gp_zone: String:
|
||||
get:
|
||||
return pad.zone
|
||||
var gp_index: int:
|
||||
get:
|
||||
return pad.index
|
||||
var challenge := {} # wöchentliche Herausforderung oder leer
|
||||
var preset_opponents: Array = [] # aus der Gegnervorschau übernommene Gegner
|
||||
var skip_ai := false # Gegnerzüge im Schnelldurchlauf
|
||||
@@ -69,10 +79,23 @@ var notice_label: Label
|
||||
var confirm: Control
|
||||
var handover: Control
|
||||
var handover_label: Label
|
||||
var mulligan_box: Control
|
||||
var mulligan_layer: Control
|
||||
var b_keep: Button
|
||||
var b_swap: Button
|
||||
var mulligan: BattleMulligan
|
||||
var mulligan_box: Control:
|
||||
get:
|
||||
return mulligan
|
||||
var mulligan_layer: Control:
|
||||
get:
|
||||
return mulligan.layer
|
||||
var b_keep: Button:
|
||||
get:
|
||||
return mulligan.b_keep
|
||||
var b_swap: Button:
|
||||
get:
|
||||
return mulligan.b_swap
|
||||
var mulligan_selected: Array:
|
||||
get:
|
||||
return mulligan.selected
|
||||
var cursor_layer: Control
|
||||
var tutorial_panel: Panel
|
||||
var tutorial_text: RichTextLabel
|
||||
var b_tut_next: Button
|
||||
@@ -94,17 +117,6 @@ class Highlight extends Control:
|
||||
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.",
|
||||
"played_creature"],
|
||||
["Wesen greifen erst ab dem nächsten Zug an. Beende deinen Zug mit dem Knopf rechts oder der Leertaste.", "turn_passed"],
|
||||
["Grüne Rahmen zeigen angriffsbereite Wesen. Klicke ein Wesen und dann Fenn oder eines seiner Wesen. Wächter müssen zuerst fallen.", "attacked"],
|
||||
["Resonanz: Wesen mit gleichem Element geben sich gegenseitig +1 Angriff. Spiele ein zweites Feuer- oder Erde-Wesen.", "resonance"],
|
||||
["Feldkombo: Ein Wesen neben einem eigenen Wesen mit anderem Element löst einen Effekt aus. Lava macht 1 Schaden an alle gegnerischen Wesen.", "combo"],
|
||||
["Zauber wirken sofort. Funke oder Feuerball treffen auch Fenn direkt. Ziele wählst du per Klick, Rechtsklick bricht ab.", "spell"],
|
||||
["Fenn verliert bei 0 Leben. Tausche im ersten Zug einer Partie schwache Starthände (Mulligan). Viel Erfolg!", ""],
|
||||
]
|
||||
|
||||
|
||||
func _init(n: int = 2, campaign_stage: int = -1) -> void:
|
||||
n_opp = n
|
||||
@@ -124,9 +136,10 @@ static func online(seat: int) -> BattleScreen:
|
||||
return b
|
||||
|
||||
|
||||
static func guided() -> BattleScreen:
|
||||
static func guided(part: int = 1) -> BattleScreen:
|
||||
var b := BattleScreen.new(1, -1)
|
||||
b.tutorial = true
|
||||
b.tutorial_part = part
|
||||
return b
|
||||
|
||||
|
||||
@@ -162,6 +175,7 @@ func _ready() -> void:
|
||||
var s: Dictionary = app.profile.settings
|
||||
fx = BattleFx.new(self)
|
||||
fx.enabled = s.animations
|
||||
pad = BattleGamepad.new(self)
|
||||
ai_delay = 0.75 / float(s.ai_speed)
|
||||
_build_static()
|
||||
if is_online():
|
||||
@@ -177,10 +191,12 @@ func _ready() -> void:
|
||||
elif not hotseat_decks.is_empty():
|
||||
game = Game.new_hotseat(hotseat_decks, hotseat_ai)
|
||||
elif tutorial:
|
||||
guide = BattleTutorial.new(self, tutorial_part)
|
||||
var r := Game.make_rng()
|
||||
var opps := Campaign.opponents(0, r, Game.deck_rating(app.profile.deck_cards()))
|
||||
var opps := Campaign.opponents(guide.stage_index(), r, Game.deck_rating(app.profile.deck_cards()))
|
||||
opps[0].perks = []
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), opps, r, true)
|
||||
guide.inject_cards(game.human.hand)
|
||||
elif not challenge.is_empty():
|
||||
var rules: Dictionary = challenge.rules.duplicate()
|
||||
rules["target_rating"] = Game.deck_rating(app.profile.deck_cards())
|
||||
@@ -273,16 +289,11 @@ func _build_static() -> void:
|
||||
highlight_layer = _layer()
|
||||
zoom_layer = _layer()
|
||||
fx_layer = _layer()
|
||||
cursor_layer = _layer()
|
||||
# Mulligan
|
||||
mulligan_box = _overlay()
|
||||
Style.panel(mulligan_box, Rect2(140, 250, 1000, 320), Style.PANEL, 10, Style.GOLD, 2)
|
||||
Style.label(mulligan_box, "Hand tauschen?", 26, Style.TEXT, Rect2(140, 262, 1000, 40), "center", true)
|
||||
Style.label(mulligan_box, "Klicke Karten an die du zurücklegen willst. Einmal pro Partie.", 15, Style.MUTED, Rect2(140, 300, 1000, 24), "center")
|
||||
mulligan_layer = Control.new()
|
||||
mulligan_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
mulligan_box.add_child(mulligan_layer)
|
||||
b_keep = Style.button(mulligan_box, "Behalten", Rect2(430, 515, 200, 44), func(): do_mulligan(false), Color("285a3c"))
|
||||
b_swap = Style.button(mulligan_box, "Tauschen", Rect2(650, 515, 200, 44), func(): do_mulligan(true))
|
||||
mulligan = BattleMulligan.new()
|
||||
mulligan.decided.connect(do_mulligan)
|
||||
add_child(mulligan)
|
||||
# Spielende
|
||||
overlay = _overlay()
|
||||
result_label = Style.label(overlay, "", 56, Style.GREEN, Rect2(0, 250, 1280, 80), "center", true)
|
||||
@@ -327,8 +338,11 @@ func _overlay() -> Control:
|
||||
func leave() -> void:
|
||||
if is_online():
|
||||
net.leave()
|
||||
if tutorial and not app.profile.tutorial_done:
|
||||
app.profile.tutorial_done = true
|
||||
if tutorial:
|
||||
if tutorial_part == 1:
|
||||
app.profile.tutorial_done = true
|
||||
else:
|
||||
app.profile.tutorial2_done = true
|
||||
app.save()
|
||||
app.show_screen(MenuScreen.new())
|
||||
|
||||
@@ -415,7 +429,7 @@ func my_turn() -> bool:
|
||||
|
||||
|
||||
func any_overlay() -> bool:
|
||||
return confirm.visible or handover.visible or mulligan_box.visible or overlay.visible
|
||||
return confirm.visible or handover.visible or mulligan.visible or overlay.visible
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -424,6 +438,9 @@ func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
cancel_all()
|
||||
return
|
||||
if event is InputEventJoypadButton and event.pressed:
|
||||
pad.button(event.button_index)
|
||||
return
|
||||
if not (event is InputEventKey and event.pressed and not event.echo):
|
||||
return
|
||||
match event.keycode:
|
||||
@@ -445,8 +462,8 @@ func _input(event: InputEvent) -> void:
|
||||
KEY_ENTER, KEY_KP_ENTER:
|
||||
if handover.visible:
|
||||
take_over()
|
||||
elif mulligan_box.visible:
|
||||
do_mulligan(false)
|
||||
elif mulligan.visible:
|
||||
do_mulligan([])
|
||||
elif overlay.visible:
|
||||
leave()
|
||||
elif tutorial and b_tut_next.visible:
|
||||
@@ -494,7 +511,7 @@ func _process(delta: float) -> void:
|
||||
handover.visible = true
|
||||
hand_layer.visible = false
|
||||
trap_layer.visible = false
|
||||
elif cur == perspective and cur.turns == 1 and not cur.mulligan_done and not mulligan_box.visible and not handover.visible:
|
||||
elif cur == perspective and cur.turns == 1 and not cur.mulligan_done and not mulligan.visible and not handover.visible:
|
||||
show_mulligan()
|
||||
return
|
||||
if is_client():
|
||||
@@ -517,9 +534,12 @@ func _finish() -> void:
|
||||
var won := game.winner == "human"
|
||||
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
|
||||
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids, game.turn, challenge, factor)
|
||||
reward = app.profile.record_battle(won, n_opp, app.profile.deck_cards(), stage, played_ids, game.turn, challenge, factor, game.human.life)
|
||||
if tutorial:
|
||||
app.profile.tutorial_done = true
|
||||
if tutorial_part == 1:
|
||||
app.profile.tutorial_done = true
|
||||
else:
|
||||
app.profile.tutorial2_done = true
|
||||
app.save()
|
||||
app.sfx.play("win" if won else "lose")
|
||||
else:
|
||||
@@ -610,39 +630,11 @@ func end_turn() -> void:
|
||||
|
||||
|
||||
func show_mulligan() -> void:
|
||||
mulligan_selected = []
|
||||
mulligan_box.visible = true
|
||||
Style.clear(mulligan_layer)
|
||||
var hand: Array = perspective.hand
|
||||
for i in hand.size():
|
||||
var r := BattleLayout.hand_rect(hand.size(), i)
|
||||
var v := CardView.new(hand[i], r.size)
|
||||
v.position = Vector2(r.position.x, 322)
|
||||
var idx := i
|
||||
v.clicked.connect(func(_view): toggle_mulligan(idx))
|
||||
mulligan_layer.add_child(v)
|
||||
_mulligan_borders()
|
||||
mulligan.open(perspective.hand)
|
||||
|
||||
|
||||
func toggle_mulligan(idx: int) -> void:
|
||||
if idx in mulligan_selected:
|
||||
mulligan_selected.erase(idx)
|
||||
else:
|
||||
mulligan_selected.append(idx)
|
||||
_mulligan_borders()
|
||||
|
||||
|
||||
func _mulligan_borders() -> void:
|
||||
var views := mulligan_layer.get_children()
|
||||
for i in views.size():
|
||||
views[i].border = Style.YELLOW if i in mulligan_selected else null
|
||||
views[i].queue_redraw()
|
||||
b_swap.text = "Tauschen (%d)" % mulligan_selected.size() if not mulligan_selected.is_empty() else "Tauschen"
|
||||
|
||||
|
||||
func do_mulligan(swap: bool) -> void:
|
||||
mulligan_box.visible = false
|
||||
var idxs: Array = mulligan_selected.duplicate() if swap else []
|
||||
func do_mulligan(idxs: Array) -> void:
|
||||
mulligan.visible = false
|
||||
if is_client():
|
||||
net.send_action({"type": "mulligan", "hand": idxs})
|
||||
return
|
||||
@@ -826,69 +818,21 @@ func _note_events(evs: Array) -> void:
|
||||
|
||||
# --- Anleitung ------------------------------------------------------------------------------
|
||||
|
||||
func tutorial_condition(kind: String) -> bool:
|
||||
var h := game.human
|
||||
match kind:
|
||||
"played_creature":
|
||||
return game.log.any(func(l): return l.begins_with("Du spielt")) and h.board.size() > 0
|
||||
"turn_passed":
|
||||
return h.turns >= 2
|
||||
"attacked":
|
||||
return game.log.any(func(l): return l.ends_with("greift Fenn an.") or (" greift " in l and l.begins_with(h.board[0].name) if not h.board.is_empty() else false))
|
||||
"resonance":
|
||||
return h.board.any(func(c): return c.resonance > 0)
|
||||
"combo":
|
||||
return game.log.any(func(l): return l.begins_with("Feldkombo"))
|
||||
"spell":
|
||||
return game.log.any(func(l): return l == "Du spielt Funke." or l == "Du spielt Feuerball." or l == "Du spielt Verhärten.")
|
||||
return false
|
||||
|
||||
|
||||
func tutorial_next() -> void:
|
||||
if tutorial_step < TUTORIAL_STEPS.size() - 1:
|
||||
tutorial_step += 1
|
||||
if guide != null and guide.next():
|
||||
app.sfx.play("click")
|
||||
refresh()
|
||||
|
||||
|
||||
## Hebt hervor, was der aktuelle Schritt verlangt.
|
||||
func _tutorial_refresh() -> void:
|
||||
guide.advance()
|
||||
tutorial_text.text = guide.text()
|
||||
b_tut_next.visible = not guide.finished()
|
||||
|
||||
|
||||
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:
|
||||
for r in guide.highlight_rects(mine):
|
||||
var hl := Highlight.new()
|
||||
hl.position = r.position - Vector2(4, 4)
|
||||
hl.size = r.size + Vector2(8, 8)
|
||||
@@ -896,13 +840,6 @@ func _tutorial_highlights(mine: bool) -> void:
|
||||
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
|
||||
tutorial_text.text = "%d/%d %s" % [tutorial_step + 1, TUTORIAL_STEPS.size(), TUTORIAL_STEPS[tutorial_step][0]]
|
||||
b_tut_next.visible = tutorial_step < TUTORIAL_STEPS.size() - 1
|
||||
|
||||
|
||||
# --- Ansicht angleichen --------------------------------------------------------------------
|
||||
|
||||
func refresh() -> void:
|
||||
@@ -1053,6 +990,7 @@ func refresh() -> void:
|
||||
if tutorial:
|
||||
_tutorial_refresh()
|
||||
_tutorial_highlights(mine)
|
||||
pad.draw_cursor()
|
||||
|
||||
Style.clear(zoom_layer)
|
||||
if zoom_player != null and zoom_player.alive():
|
||||
@@ -1076,7 +1014,7 @@ func refresh() -> void:
|
||||
overlay.visible = true
|
||||
confirm.visible = false
|
||||
handover.visible = false
|
||||
mulligan_box.visible = false
|
||||
mulligan.visible = false
|
||||
|
||||
|
||||
func build_zoom(valid: Array) -> void:
|
||||
|
||||
Reference in New Issue
Block a user