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)