class_name LuneUnitDetail extends PanelContainer ## The selected normal unit: portrait, name, stage, role, HP, damage, tempo, range, armour, ## ability, upgrade with cost and stat change, return value, and the lane order with TOP, ## MID, BOTTOM and GROTTE (only allowed options active). Normal units carry no items, so this ## panel has no item slots. In combat or after Ready it only informs. signal command_requested(command: Dictionary) signal closed const GROTTO_WARNING := "Diese Einheit fehlt deiner Lane in der nächsten Kampfphase." var uid: String = "" var _portrait: LunePortraitRect var _name: Label var _sub: Label var _live: LuneHpBar var _stats: GridContainer var _ability: Label var _types: Label var _upgrade_box: VBoxContainer var _upgrade_title: Label var _upgrade_delta: Label var _upgrade: Button var _sell: Button var _sell_note: Label var _lanes: Dictionary = {} var _lane_row: HBoxContainer var _warning: Label var _locked: Label var _signature: String = "" func _init() -> void: theme_type_variation = "HudPanel" mouse_filter = Control.MOUSE_FILTER_STOP var column := VBoxContainer.new() column.add_theme_constant_override("separation", 5) add_child(column) var head := HBoxContainer.new() head.add_theme_constant_override("separation", 10) column.add_child(head) _portrait = LunePortraitRect.new() _portrait.custom_minimum_size = Vector2(76, 76) head.add_child(_portrait) var titles := VBoxContainer.new() titles.size_flags_horizontal = Control.SIZE_EXPAND_FILL titles.add_theme_constant_override("separation", 2) head.add_child(titles) _name = LuneUiTheme.label("", "HudName", titles) _name.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _sub = LuneUiTheme.label("", "HudMuted", titles) _live = LuneHpBar.new() _live.custom_minimum_size = Vector2(0, 6) titles.add_child(_live) var close := LuneUiTheme.button("", "HudIconButton", head) close.custom_minimum_size = Vector2(32, 30) close.size_flags_vertical = Control.SIZE_SHRINK_BEGIN var cross := LuneGlyph.make("close", LuneUiTheme.MUTED, 12.0, close) cross.set_anchors_and_offsets_preset(Control.PRESET_CENTER) close.pressed.connect(func() -> void: closed.emit()) LuneTooltip.attach(close, func() -> Dictionary: return {"title": "Auswahl aufheben", "text": "Esc"}) _stats = GridContainer.new() _stats.columns = 4 _stats.add_theme_constant_override("h_separation", 6) _stats.add_theme_constant_override("v_separation", 2) column.add_child(_stats) LuneTooltip.attach(_stats, _stats_tip) _types = LuneUiTheme.label("", "HudMuted", column) _types.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _ability = LuneUiTheme.label("", "HudText", column) _ability.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _upgrade_box = VBoxContainer.new() _upgrade_box.add_theme_constant_override("separation", 2) column.add_child(_upgrade_box) var upgrade_row := HBoxContainer.new() upgrade_row.add_theme_constant_override("separation", 8) _upgrade_box.add_child(upgrade_row) _upgrade_title = LuneUiTheme.label("", "HudText", upgrade_row) _upgrade_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL _upgrade_title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _upgrade = LuneUiTheme.button("", "HudPrimary", upgrade_row) _upgrade.size_flags_vertical = Control.SIZE_SHRINK_CENTER _upgrade.pressed.connect(func() -> void: command_requested.emit({"type": "upgrade", "uid": uid})) _upgrade_delta = LuneUiTheme.label("", "HudMuted", _upgrade_box) _upgrade_delta.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _lane_row = HBoxContainer.new() _lane_row.add_theme_constant_override("separation", 4) column.add_child(_lane_row) for lane: String in LuneMap.LANES + [LuneMatch.GROTTO]: var title: String = "GROTTE" if lane == LuneMatch.GROTTO else LuneMap.LANE_NAMES[lane].to_upper() var button := LuneUiTheme.button(title, "HudToggle", _lane_row) button.toggle_mode = true button.size_flags_horizontal = Control.SIZE_EXPAND_FILL button.pressed.connect(func() -> void: command_requested.emit({"type": "assign", "uid": uid, "lane": lane})) _lanes[lane] = button LuneTooltip.attach(button, func() -> Dictionary: return {"title": "Einsatz: %s" % title, "text": GROTTO_WARNING if lane == LuneMatch.GROTTO else "Kämpft in der nächsten Welle auf der %s-Lane." % LuneMap.LANE_NAMES[lane]}) _warning = LuneUiTheme.label(GROTTO_WARNING, "HudText", column) _warning.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _warning.add_theme_color_override("font_color", LuneUiTheme.WARN) var sell_row := HBoxContainer.new() sell_row.add_theme_constant_override("separation", 8) column.add_child(sell_row) _sell = LuneUiTheme.button("", "HudButton", sell_row) _sell.pressed.connect(func() -> void: command_requested.emit({"type": "sell", "uid": uid})) _sell_note = LuneUiTheme.label("", "HudMuted", sell_row) _sell_note.size_flags_horizontal = Control.SIZE_EXPAND_FILL _sell_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _locked = LuneUiTheme.label("", "HudMuted", column) _locked.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART ## entry: own roster entry (uid, type_id, lane, invested, fresh); live: LuneUnit in combat or null. func apply(entry: Dictionary, model: LuneHudModel, live: LuneUnit) -> void: uid = str(entry.uid) var type_id: String = str(entry.type_id) var data: Dictionary = model.troop(type_id) var planning: bool = model.planning() var locked: bool = model.locked() var gold: int = model.own_gold() var signature: String = "%s|%s|%s|%d|%s|%s|%d" % [str(entry), planning, locked, gold, model.state.grotto_available(), live != null, model.state.round_number] if live != null: _live.fraction = live.hp / maxf(1.0, live.max_hp) if live.alive else 0.0 _live.fill = LuneHpBar.health_color(_live.fraction, LuneUiTheme.team_color(model.team())) _live.visible = live != null if signature == _signature: return _signature = signature _portrait.show_model(type_id) _name.text = str(data.display_name) var lane_text: String = "Grotte" if entry.lane == LuneMatch.GROTTO else LuneMap.LANE_NAMES[entry.lane] _sub.text = "%s · Stufe %s · %s" % [LuneStats.role_name(data.role), "II" if int(data.stage) == 2 else "I", lane_text] if live != null and not live.alive: _sub.text += " · gefallen" for child: Node in _stats.get_children(): child.free() var rows: Array = LuneStats.short_rows(data) for row: Array in rows.slice(0, 4): LuneGlyph.make(str(row[0]), LuneUiTheme.MUTED, 14.0, _stats) var cell := LuneUiTheme.label(str(row[1]), "HudText", _stats) cell.size_flags_horizontal = Control.SIZE_EXPAND_FILL cell.clip_text = true cell.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS var attack_type: int = int(data.attack_type) _types.text = "Rüstung %s · Angriff %s, stark gegen %s" % [DataCatalog.ARMOR_NAMES[int(data.armor_type)], DataCatalog.DAMAGE_NAMES[attack_type], LuneStats.strong_against(attack_type)] _ability.text = str(data.get("ability_description", "")) var upgrade_id: String = str(data.get("upgrade_id", "")) var can_act: bool = planning and not locked if upgrade_id.is_empty(): _upgrade_title.text = "Höchste Stufe erreicht." _upgrade.visible = false _upgrade_delta.text = "" else: var next: Dictionary = DataCatalog.get_unit(upgrade_id) var cost: int = int(next.cost) _upgrade_title.text = "Aufwerten → %s" % next.display_name _upgrade.visible = true _upgrade.text = "↑ %d" % cost _upgrade.disabled = not can_act or gold < cost var delta: String = "Leben %s → %s · Schaden %s → %s" % [LuneUiTheme.number(data.max_hp), LuneUiTheme.number(next.max_hp), LuneUiTheme.decimal(data.damage), LuneUiTheme.decimal(next.damage)] if not is_equal_approx(float(data.attack_range), float(next.attack_range)): delta += " · Reichweite %s → %s m" % [LuneUiTheme.decimal(data.attack_range), LuneUiTheme.decimal(next.attack_range)] if can_act and gold < cost: delta += " · es fehlen %d Gold" % (cost - gold) _upgrade_delta.text = delta var fresh: bool = bool(entry.get("fresh", false)) var refund: int = int(entry.invested) if fresh else floori(float(entry.invested) * model.state.balance.sell_refund) _sell.text = "Zurückgeben +%d" % refund _sell.disabled = not can_act _sell.visible = planning _sell_note.text = "voller Preis" if fresh else "%d %% von %d" % [int(round(model.state.balance.sell_refund * 100.0)), int(entry.invested)] _sell_note.visible = planning var grotto_open: bool = model.state.grotto_available() for lane: String in _lanes: var button: Button = _lanes[lane] button.set_pressed_no_signal(str(entry.lane) == lane) button.disabled = not can_act or (lane == LuneMatch.GROTTO and not grotto_open) _warning.visible = str(entry.lane) == LuneMatch.GROTTO if not planning: _locked.text = "Im Kampf gibt es keine Befehle; Aufwertung, Rückgabe und Einsatz wieder in der Planung." elif locked: _locked.text = "Bereit gemeldet: Die Planung ist verbindlich." elif not grotto_open: _locked.text = "Grotte gesperrt bis Welle %d." % int(model.state.boss.next_available_round) else: _locked.text = "" _locked.visible = not _locked.text.is_empty() func _stats_tip() -> Dictionary: var entry_type: String = "" if _portrait != null: entry_type = _portrait.model_id if entry_type.is_empty(): return {} var data: Dictionary = DataCatalog.get_unit(entry_type) return {"title": str(data.display_name), "stats": LuneStats.rows(data, true), "note": "Ein Angriff pro Sekunde mal Tempo; Schaden je Treffer vor Rüstung. Stark: 130 %, schwach: 75 % (Rüstungsmatrix)."}