Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das HUD im Unity-Projekt (../luneraria-unity). - HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse, Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails, Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben - Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie - Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715; verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern - Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md - HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
207 lines
8.8 KiB
GDScript
207 lines
8.8 KiB
GDScript
class_name LuneHeroCard
|
|
extends Button
|
|
## The hero in the bottom bar: portrait, name, level, experience progress, HP, role, the
|
|
## three item slots and the talent state. A click opens the hero window (details, items,
|
|
## talents) or, without a hero, the recruiting window. No MOBA action bar: heroes act alone.
|
|
|
|
var hero_id: String = ""
|
|
var _portrait: LunePortraitRect
|
|
var _name: Label
|
|
var _level: Label
|
|
var _role_glyph: LuneGlyph
|
|
var _role: Label
|
|
var _xp: LuneHpBar
|
|
var _xp_text: Label
|
|
var _hp_glyph: LuneGlyph
|
|
var _hp: Label
|
|
var _hp_bar: LuneHpBar
|
|
var _slots: Array[LuneGlyph] = []
|
|
var _slot_boxes: Array[PanelContainer] = []
|
|
var _talent: LuneStatusBadge
|
|
var _empty: VBoxContainer
|
|
var _filled: VBoxContainer
|
|
var _faces: Array[LunePortraitRect] = []
|
|
var data: Dictionary = {}
|
|
var _signature: String = ""
|
|
var _empty_title: Label
|
|
var _empty_price: Label
|
|
var _empty_coin: LuneGlyph
|
|
var _faces_row: HBoxContainer
|
|
|
|
func _init() -> void:
|
|
theme_type_variation = "HudCard"
|
|
focus_mode = Control.FOCUS_ALL
|
|
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
custom_minimum_size = Vector2(320, 132)
|
|
var row := HBoxContainer.new()
|
|
row.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
row.offset_left = 8
|
|
row.offset_right = -8
|
|
row.offset_top = 8
|
|
row.offset_bottom = -8
|
|
row.add_theme_constant_override("separation", 10)
|
|
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(row)
|
|
_portrait = LunePortraitRect.new()
|
|
_portrait.custom_minimum_size = Vector2(88, 0)
|
|
row.add_child(_portrait)
|
|
_filled = VBoxContainer.new()
|
|
_filled.add_theme_constant_override("separation", 2)
|
|
_filled.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_filled.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
row.add_child(_filled)
|
|
_name = LuneUiTheme.label("", "HudName", _filled)
|
|
_name.clip_text = true
|
|
_name.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
|
var role_row := HBoxContainer.new()
|
|
role_row.add_theme_constant_override("separation", 5)
|
|
role_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_filled.add_child(role_row)
|
|
LuneGlyph.make("star", LuneUiTheme.HERO, 13.0, role_row)
|
|
_level = LuneUiTheme.label("", "HudValue", role_row)
|
|
_role_glyph = LuneGlyph.make("tank", LuneUiTheme.MUTED, 14.0, role_row)
|
|
_role = LuneUiTheme.label("", "HudMuted", role_row)
|
|
var xp_row := HBoxContainer.new()
|
|
xp_row.add_theme_constant_override("separation", 6)
|
|
xp_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_filled.add_child(xp_row)
|
|
_xp = LuneHpBar.new()
|
|
_xp.custom_minimum_size = Vector2(60, 6)
|
|
_xp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_xp.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
_xp.fill = LuneUiTheme.BRONZE_LIGHT
|
|
xp_row.add_child(_xp)
|
|
_xp_text = LuneUiTheme.label("", "HudMuted", xp_row)
|
|
var hp_row := HBoxContainer.new()
|
|
hp_row.add_theme_constant_override("separation", 5)
|
|
hp_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_filled.add_child(hp_row)
|
|
_hp_glyph = LuneGlyph.make("heart", LuneUiTheme.DANGER.lightened(.2), 13.0, hp_row)
|
|
_hp = LuneUiTheme.label("", "HudText", hp_row)
|
|
_hp_bar = LuneHpBar.new()
|
|
_hp_bar.custom_minimum_size = Vector2(50, 5)
|
|
_hp_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_hp_bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
hp_row.add_child(_hp_bar)
|
|
var item_row := HBoxContainer.new()
|
|
item_row.add_theme_constant_override("separation", 4)
|
|
item_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_filled.add_child(item_row)
|
|
for index: int in range(LuneContent.ITEM_SLOTS):
|
|
var box := PanelContainer.new()
|
|
box.add_theme_stylebox_override("panel", LuneUiTheme.slot_style())
|
|
box.custom_minimum_size = Vector2(26, 26)
|
|
box.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
item_row.add_child(box)
|
|
var glyph := LuneGlyph.new()
|
|
glyph.custom_minimum_size = Vector2(18, 18)
|
|
box.add_child(glyph)
|
|
_slots.append(glyph)
|
|
_slot_boxes.append(box)
|
|
_talent = LuneStatusBadge.new()
|
|
item_row.add_child(_talent)
|
|
_empty = VBoxContainer.new()
|
|
_empty.add_theme_constant_override("separation", 3)
|
|
_empty.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_empty.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
_empty.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
row.add_child(_empty)
|
|
_empty_title = LuneUiTheme.label("HELD ANWERBEN", "HudName", _empty)
|
|
var faces := HBoxContainer.new()
|
|
_faces_row = faces
|
|
faces.add_theme_constant_override("separation", 4)
|
|
faces.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_empty.add_child(faces)
|
|
for hero: String in LuneContent.HEROES:
|
|
var face := LunePortraitRect.new()
|
|
face.custom_minimum_size = Vector2(38, 38)
|
|
faces.add_child(face)
|
|
_faces.append(face)
|
|
var price := HBoxContainer.new()
|
|
price.add_theme_constant_override("separation", 4)
|
|
price.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_empty.add_child(price)
|
|
_empty_coin = LuneGlyph.make("coin", LuneUiTheme.GOLD, 14.0, price)
|
|
_empty_price = LuneUiTheme.label("%d · ein Held pro Spieler" % int(LuneContent.HEROES.values()[0].cost), "HudMuted", price)
|
|
LuneTooltip.attach(self, _tip)
|
|
|
|
## hero: own hero dictionary (draft in planning) or {}; live: the combat unit or null.
|
|
func apply(hero: Dictionary, model: LuneHudModel, live: LuneUnit) -> void:
|
|
var has_hero: bool = not hero.is_empty()
|
|
if live != null and has_hero:
|
|
_hp.text = ("%s / %s" % [LuneUiTheme.number(live.hp), LuneUiTheme.number(live.max_hp)]) if live.alive else "gefallen"
|
|
_hp_bar.fraction = live.hp / maxf(1.0, live.max_hp) if live.alive else 0.0
|
|
_hp_bar.fill = LuneHpBar.health_color(_hp_bar.fraction, LuneUiTheme.GOOD)
|
|
_portrait.crossed = not live.alive
|
|
var signature: String = "%s|%s|%d|%s" % [str(hero), live != null, model.own_gold(), model.planning()]
|
|
if signature == _signature:
|
|
return
|
|
_signature = signature
|
|
_filled.visible = has_hero
|
|
_empty.visible = not has_hero
|
|
if not has_hero:
|
|
hero_id = ""
|
|
data = {}
|
|
_portrait.show_model("w04")
|
|
_portrait.dimmed = true
|
|
var index: int = 0
|
|
for id: String in LuneContent.HEROES:
|
|
_faces[index].show_model(str(LuneContent.HEROES[id].model))
|
|
index += 1
|
|
# In combat nobody can recruit: say so instead of offering it.
|
|
var planning: bool = model.planning()
|
|
_empty_title.text = "HELD ANWERBEN" if planning else "KEIN HELD"
|
|
_empty_price.text = ("%d · ein Held pro Spieler" % int(LuneContent.HEROES.values()[0].cost)) if planning else "Anwerben in der Planung"
|
|
_empty_coin.visible = planning
|
|
_faces_row.visible = planning
|
|
return
|
|
hero_id = str(hero.hero_id)
|
|
var spec: Dictionary = LuneContent.HEROES[hero_id]
|
|
var definition: Dictionary = model.hero_definition(hero)
|
|
data = {"hero": hero, "definition": definition}
|
|
_portrait.dimmed = false
|
|
_portrait.crossed = live != null and not live.alive
|
|
_portrait.show_model(str(spec.model))
|
|
_name.text = str(spec.name)
|
|
var xp: int = int(hero.xp)
|
|
var level: int = LuneContent.level_for(xp)
|
|
_level.text = "STUFE %d" % level
|
|
_role_glyph.glyph = LuneStats.role_glyph(str(definition.role))
|
|
_role.text = LuneStats.role_name(str(definition.role))
|
|
var low: int = LuneContent.LEVEL_XP[level - 1]
|
|
var top: bool = level >= LuneContent.LEVEL_XP.size()
|
|
var high: int = LuneContent.LEVEL_XP[mini(level, LuneContent.LEVEL_XP.size() - 1)]
|
|
_xp.fraction = 1.0 if top else float(xp - low) / maxf(1.0, float(high - low))
|
|
_xp_text.text = "EP %d / %d" % [xp, high] if not top else "EP %d · max" % xp
|
|
_hp_bar.visible = live != null
|
|
if live == null:
|
|
_hp.text = "%s Leben" % LuneUiTheme.number(definition.max_hp)
|
|
var items: Array = hero.items
|
|
for index: int in range(_slots.size()):
|
|
var item_id: String = str(items[index]) if index < items.size() else ""
|
|
_slots[index].glyph = item_id
|
|
_slots[index].color = LuneItemSlot.color_of(item_id)
|
|
var talents: Array = hero.talents
|
|
var open_tier: int = talents.size()
|
|
if open_tier < LuneContent.TALENT_LEVELS.size() and level >= LuneContent.TALENT_LEVELS[open_tier]:
|
|
_talent.set_state("TALENT!", LuneUiTheme.HERO, "star", true)
|
|
_talent.visible = model.planning()
|
|
elif open_tier < LuneContent.TALENT_LEVELS.size():
|
|
_talent.set_state("TALENT AB %d" % LuneContent.TALENT_LEVELS[open_tier], LuneUiTheme.MUTED)
|
|
_talent.visible = true
|
|
else:
|
|
_talent.visible = false
|
|
|
|
func _tip() -> Dictionary:
|
|
if data.is_empty():
|
|
return {"title": "Held anwerben", "glyph": "crown", "glyph_color": LuneUiTheme.HERO,
|
|
"text": "Drei Helden stehen zur Wahl; jeder Spieler führt einen. Helden sammeln Erfahrung, steigen bis Stufe 10 auf, tragen drei Items und wählen zwei Talente.",
|
|
"note": "Klick: Heldenauswahl öffnen"}
|
|
var hero: Dictionary = data.hero
|
|
var spec: Dictionary = LuneContent.HEROES[hero.hero_id]
|
|
var names: PackedStringArray = []
|
|
for item_id: String in hero.items:
|
|
names.append(LuneContent.ITEMS[item_id].name)
|
|
return {"title": "%s · Stufe %d" % [spec.name, LuneContent.level_for(int(hero.xp))], "glyph": "crown", "glyph_color": LuneUiTheme.HERO,
|
|
"subtitle": str(spec.title), "stats": LuneStats.rows(data.definition),
|
|
"text": "Items: " + (", ".join(names) if not names.is_empty() else "keine"), "note": "Klick: Werte, Items und Talente · H: Kamera zum Helden"}
|