Files
cards/scripts/ui/skill_screen.gd
T
team3 04fe9716e8
Tests / headless (push) Waiting to run
Tests / fenster-schnell (push) Waiting to run
Tests / fenster-voll (push) Skipped
Tests / balance (push) Skipped
update
2026-10-15 17:16:01 +02:00

140 lines
6.4 KiB
GDScript

class_name SkillScreen
extends Control
## Skilltree: oben Level, Erfahrungsbalken bis zum nächsten Level und freie Skillpunkte; darunter
## vier Äste (Skills.BRANCHES) als senkrechte Pfade: Knoten mit Name, Wirkung, Rangpunkten, „Jetzt“
## und „Nächste“ in Zahlen (Skills.effect), durch Linien verbunden. Der nächste Knoten öffnet, sobald
## der davor eine Stufe hat; „Lernen“ steht nur, wenn lernbar, fertige Knoten zeigen „Gelernt“.
const COL_W := 285
const COL_STEP := 300
const NODE_TOP := 218
const NODE_H := 150
const NODE_STEP := 172
var app
var level_label: Label
var xp_bar: ProgressBar
var xp_label: Label
var points_label: Label
var msg_label: Label
var nodes := {} # Skill-ID -> SkillNode
var learn_buttons := {} # Skill-ID -> Knopf
var now_labels := {} # Skill-ID -> Wirkung jetzt
var next_labels := {} # Skill-ID -> Wirkung der nächsten Stufe
var state_labels := {} # Skill-ID -> „Gelernt“, „Erst … lernen“ oder „Kein Skillpunkt frei“
var links := {} # Skill-ID -> Linie zum Knoten davor
## Ein Knoten des Baums: Rahmen und Rangpunkte zeichnet er selbst, Texte und Knopf sind Kinder.
class SkillNode extends Control:
var rank := 0
var max_rank := 1
var open := true # der Skill davor hat eine Stufe (oder es gibt keinen)
func _init(rect: Rect2, mx: int) -> void:
max_rank = mx
position = rect.position
size = rect.size
func _draw() -> void:
var done := rank >= max_rank
var bg: Color = Style.PANEL_LIGHT if open else Style.darker(Style.PANEL, 0.9)
var border: Color = Style.GREEN if done else (Style.GOLD if rank > 0 else (Style.MUTED if open else Style.darker(Style.MUTED, 0.5)))
draw_style_box(Style.box(bg, 8, border, 2), Rect2(Vector2.ZERO, size))
for i in max_rank: # Rangpunkte oben rechts
var c := Vector2(size.x - 16 - (max_rank - 1 - i) * 18, 21)
draw_circle(c, 6, Style.GOLD if i < rank else Style.darker(Style.MUTED, 0.6))
func _ready() -> void:
Style.header(self, "Skilltree", func(): app.show_screen(MenuScreen.new()))
level_label = Style.chip(self, "", Rect2(40, 86, 160, 38))
xp_bar = ProgressBar.new()
xp_bar.position = Vector2(216, 86)
xp_bar.size = Vector2(520, 38)
xp_bar.show_percentage = false
xp_bar.add_theme_stylebox_override("background", Style.box(Style.PANEL_LIGHT, 19))
xp_bar.add_theme_stylebox_override("fill", Style.box(Style.darker(Style.BLUE, 0.7), 19))
add_child(xp_bar)
xp_label = Style.label(self, "", 16, Style.TEXT, Rect2(216, 86, 520, 38), "center", true)
points_label = Style.chip(self, "", Rect2(752, 86, 260, 38), Style.GREEN)
Style.label(self, "Erfahrung sind deine verdienten Münzen. Jedes Level bringt einen Skillpunkt. Jeder Ast öffnet sich von oben nach unten.", 14, Style.MUTED, Rect2(40, 134, 1200, 22), "left")
for b in Skills.BRANCHES.size():
var branch: String = Skills.BRANCHES[b]
var x := 40 + b * COL_STEP
Style.panel(self, Rect2(x, 166, COL_W, 600))
Style.label(self, branch, 20, Style.GOLD, Rect2(x, 178, COL_W, 30), "center", true)
var path: Array = Skills.in_branch(branch)
for i in path.size():
var id: String = path[i]
var sk: Dictionary = Skills.SKILLS[id]
var y := NODE_TOP + i * NODE_STEP
if i > 0:
var line := ColorRect.new()
line.position = Vector2(x + COL_W / 2.0 - 2, y - NODE_STEP + NODE_H)
line.size = Vector2(4, NODE_STEP - NODE_H)
add_child(line)
links[id] = line
var node := SkillNode.new(Rect2(x + 14, y, COL_W - 28, NODE_H), int(sk.max))
add_child(node)
nodes[id] = node
var w := COL_W - 28 - 28
Style.label(node, sk.name, 17, Style.TEXT, Rect2(14, 8, w - 60, 26), "left", true)
var text := Style.label(node, sk.text, 12, Style.MUTED, Rect2(14, 34, w, 34), "left")
text.autowrap_mode = TextServer.AUTOWRAP_WORD
now_labels[id] = Style.label(node, "", 12, Style.TEXT, Rect2(14, 70, w, 20), "left")
next_labels[id] = Style.label(node, "", 12, Style.GREEN, Rect2(14, 88, w, 20), "left")
learn_buttons[id] = Style.button(node, "Lernen", Rect2(14, 110, w, 32), learn.bind(id), Style.PRIMARY)
state_labels[id] = Style.label(node, "", 14, Style.MUTED, Rect2(14, 112, w, 28), "center", true)
msg_label = Style.label(self, "", 15, Style.RED, Rect2(0, 770, 1280, 24), "center")
refresh()
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
app.show_screen(MenuScreen.new())
func learn(id: String) -> void:
var err: String = app.profile.learn(id)
msg_label.text = tr(err) if err != "" else ""
if err == "":
app.sfx.play("combo")
app.save()
refresh()
func refresh() -> void:
var p: Profile = app.profile
var lvl := p.player_level()
var base := Skills.xp_for_level(lvl)
level_label.text = tr("Level %d") % lvl
xp_bar.max_value = Skills.xp_for_level(lvl + 1) - base
xp_bar.value = p.xp - base
xp_label.text = tr("%d / %d EP bis Level %d") % [p.xp - base, Skills.xp_for_level(lvl + 1) - base, lvl + 1]
points_label.text = tr("Skillpunkte: %d") % p.skill_points()
for id in Skills.SKILLS:
var sk: Dictionary = Skills.SKILLS[id]
var node: SkillNode = nodes[id]
var prev := Skills.previous(id)
node.rank = p.skill(id)
node.open = prev == "" or p.skill(prev) > 0
node.modulate = Color(1, 1, 1, 1 if node.open else 0.55)
node.queue_redraw()
var done := node.rank >= int(sk.max)
now_labels[id].text = tr("Jetzt: %s") % Skills.effect(id, node.rank)
next_labels[id].text = "" if done else tr("Nächste: %s") % Skills.effect(id, node.rank + 1)
var learnable := node.open and not done and p.skill_points() > 0
learn_buttons[id].visible = learnable
var state := ""
if done:
state = tr("Gelernt")
elif not node.open:
state = tr("Erst %s lernen") % tr(Skills.SKILLS[prev].name)
elif not learnable:
state = tr("Kein Skillpunkt frei")
state_labels[id].text = state
state_labels[id].add_theme_color_override("font_color", Style.GREEN if done else Style.MUTED)
if links.has(id):
links[id].color = Style.GOLD if node.open else Style.darker(Style.MUTED, 0.5)