using System; using System.Collections.Generic; using System.Linq; using Luneraria.Core; using UnityEngine; using UnityEngine.UIElements; namespace Luneraria.UI { /// /// The hero in the bottom bar (Godot LuneHeroCard): 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. The command slot is reserved for the ultimates of the planned heroes /// (PlannedHeroes, trigger Command); it stays hidden while no such hero is playable. /// public sealed class LuneHeroCard : LuneButton { public string HeroId = ""; public event Action CommandRequested; readonly LunePortraitElement _portrait; readonly Label _name; readonly Label _level; readonly LuneGlyphElement _roleGlyph; readonly Label _role; readonly LuneHpBar _xp; readonly Label _xpText; readonly Label _hp; readonly LuneHpBar _hpBar; readonly LuneGlyphElement[] _slots = new LuneGlyphElement[LuneContent.ItemSlots]; readonly LuneStatusBadge _talent; readonly VisualElement _empty; readonly VisualElement _filled; readonly List _faces = new List(); readonly Label _emptyTitle; readonly Label _emptyPrice; readonly LuneGlyphElement _emptyCoin; readonly VisualElement _facesRow; /// Reserved: the ultimate of a planned hero (released through the command menu in combat). public readonly LuneButton CommandSlot; LuneHudModel _model; HeroState _hero; UnitDef _definition; string _signature = ""; public LuneHeroCard() : base(null, LuneButtons.HudCard) { style.minWidth = 320f; style.height = 132f; style.justifyContent = Justify.FlexStart; style.alignItems = Align.Stretch; style.paddingLeft = 8f; style.paddingRight = 8f; style.paddingTop = 8f; style.paddingBottom = 8f; _portrait = new LunePortraitElement(); _portrait.style.width = 88f; Add(_portrait); _filled = LuneUi.Box(true, 2f, this); _filled.style.marginLeft = 10f; _filled.style.flexGrow = 1f; _filled.style.flexShrink = 1f; _filled.style.minWidth = 0f; _name = LuneUi.Clip(LuneUi.Label("", "HudName", _filled)); VisualElement roleRow = LuneUi.Box(false, 5f, _filled); LuneUi.Glyph("star", LuneUiTheme.Hero, 13f, roleRow); _level = LuneUi.Label("", "HudValue", roleRow); _roleGlyph = LuneUi.Glyph("tank", LuneUiTheme.Muted, 14f, roleRow); _role = LuneUi.Label("", "HudMuted", roleRow); VisualElement xpRow = LuneUi.Box(false, 6f, _filled); _xp = new LuneHpBar(60f, 6f) { Fill = LuneUiTheme.BronzeLight }; _xp.style.flexGrow = 1f; xpRow.Add(_xp); _xpText = LuneUi.Label("", "HudMuted", xpRow); VisualElement hpRow = LuneUi.Box(false, 5f, _filled); LuneUi.Glyph("heart", LuneUiTheme.Lightened(LuneUiTheme.Danger, .2f), 13f, hpRow); _hp = LuneUi.Label("", "HudText", hpRow); _hpBar = new LuneHpBar(50f, 5f); _hpBar.style.flexGrow = 1f; hpRow.Add(_hpBar); VisualElement itemRow = LuneUi.Box(false, 4f, _filled); for (int index = 0; index < _slots.Length; index++) { var box = new VisualElement { pickingMode = PickingMode.Ignore }; box.style.width = 26f; box.style.height = 26f; box.style.backgroundColor = new Color(0f, 0f, 0f, .35f); box.style.borderTopLeftRadius = 3f; box.style.borderTopRightRadius = 3f; box.style.borderBottomLeftRadius = 3f; box.style.borderBottomRightRadius = 3f; box.style.borderLeftWidth = 1f; box.style.borderRightWidth = 1f; box.style.borderTopWidth = 1f; box.style.borderBottomWidth = 1f; Color edge = LuneUiTheme.Hex("33404d"); box.style.borderLeftColor = edge; box.style.borderRightColor = edge; box.style.borderTopColor = edge; box.style.borderBottomColor = edge; box.style.paddingLeft = 4f; box.style.paddingTop = 4f; itemRow.Add(box); _slots[index] = LuneUi.Glyph("", Color.white, 18f, box); } _talent = new LuneStatusBadge(); itemRow.Add(_talent); CommandSlot = new LuneButton("ULTIMATE", LuneButtons.HudPrimary, itemRow); CommandSlot.Clicked += () => CommandRequested?.Invoke(); LuneUi.Show(CommandSlot, false); _empty = LuneUi.Box(true, 3f, this); _empty.style.marginLeft = 10f; _empty.style.flexGrow = 1f; _empty.style.justifyContent = Justify.Center; _emptyTitle = LuneUi.Label("HELD ANWERBEN", "HudName", _empty); _facesRow = LuneUi.Box(false, 4f, _empty); foreach (string id in LuneContent.Heroes.Keys) { var face = new LunePortraitElement(); face.style.width = 38f; face.style.height = 38f; _facesRow.Add(face); _faces.Add(face); } VisualElement price = LuneUi.Box(false, 4f, _empty); _emptyCoin = LuneUi.Glyph("coin", LuneUiTheme.Gold, 14f, price); _emptyPrice = LuneUi.Label("", "HudMuted", price); LuneTooltip.Attach(this, Tip); } /// hero: the own hero (draft in planning) or null; live: the combat unit or null. public void Apply(HeroState hero, LuneHudModel model, LuneUnit live) { _model = model; bool hasHero = hero != null; if (live != null && hasHero) { _hp.text = live.Alive ? LuneUiTheme.Number(live.Hp) + " / " + LuneUiTheme.Number(live.MaxHp) : "gefallen"; _hpBar.Fraction = live.Alive ? (float)(live.Hp / Math.Max(1.0, live.MaxHp)) : 0f; _hpBar.Fill = LuneHpBar.HealthColor(_hpBar.Fraction, LuneUiTheme.Good); _portrait.Crossed = !live.Alive; } string signature = (hasHero ? hero.HeroId + ":" + hero.Xp + ":" + hero.Lane + ":" + string.Join(",", hero.Items) + ":" + string.Join(",", hero.Talents) : "-") + "|" + (live != null) + "|" + model.OwnGold + "|" + model.Planning; if (signature == _signature) return; _signature = signature; LuneUi.Show(_filled, hasHero); LuneUi.Show(_empty, !hasHero); _hero = hero; if (!hasHero) { HeroId = ""; _definition = null; _portrait.ShowModel("w04"); _portrait.Dimmed = true; _portrait.Crossed = false; int index = 0; foreach (HeroSpec spec in LuneContent.Heroes.Values) _faces[index++].ShowModel(spec.Model); // In combat nobody can recruit: say so instead of offering it. bool planning = model.Planning; _emptyTitle.text = planning ? "HELD ANWERBEN" : "KEIN HELD"; _emptyPrice.text = planning ? LuneContent.Heroes.Values[0].Cost + " · ein Held pro Spieler" : "Anwerben in der Planung"; LuneUi.Show(_emptyCoin, planning); LuneUi.Show(_facesRow, planning); LuneUi.Show(CommandSlot, false); return; } HeroId = hero.HeroId; HeroSpec heroSpec = LuneContent.Heroes[HeroId]; _definition = model.HeroDefinition(hero); _portrait.Dimmed = false; _portrait.Crossed = live != null && !live.Alive; _portrait.ShowModel(heroSpec.Model); _name.text = heroSpec.Name; int level = LuneContent.LevelFor(hero.Xp); _level.text = "STUFE " + level; _roleGlyph.Glyph = LuneStats.RoleGlyph(_definition.Role); _role.text = LuneStats.RoleName(_definition.Role); int[] levels = LuneContent.LevelXp; int low = levels[level - 1]; bool top = level >= levels.Length; int high = levels[Math.Min(level, levels.Length - 1)]; _xp.Fraction = top ? 1f : (float)(hero.Xp - low) / Math.Max(1, high - low); _xpText.text = top ? "EP " + hero.Xp + " · max" : "EP " + hero.Xp + " / " + high; LuneUi.Show(_hpBar, live != null); if (live == null) _hp.text = LuneUiTheme.Number(_definition.MaxHp) + " Leben"; for (int index = 0; index < _slots.Length; index++) { string itemId = index < hero.Items.Count ? hero.Items[index] : ""; _slots[index].Glyph = itemId; _slots[index].Color = LuneItemSlot.ColorOf(itemId); } int openTier = hero.Talents.Count; int[] talentLevels = LuneContent.TalentLevels; if (openTier < talentLevels.Length && level >= talentLevels[openTier]) { _talent.SetState("TALENT!", LuneUiTheme.Hero, "star", true); LuneUi.Show(_talent, model.Planning); } else if (openTier < talentLevels.Length) { _talent.SetState("TALENT AB " + talentLevels[openTier], LuneUiTheme.Muted); LuneUi.Show(_talent, true); } else { LuneUi.Show(_talent, false); } // The three reference heroes have no command ultimate; the planned heroes will. PlannedHero planned = PlannedHeroes.All.FirstOrDefault(p => p.Active && p.Id == HeroId); bool command = planned != null && planned.Ultimate.Trigger == PowerTrigger.Command; LuneUi.Show(CommandSlot, command); if (command) CommandSlot.Text = planned.Ultimate.Name.ToUpperInvariant(); } LuneTip Tip() { if (_hero == null || _definition == null) return new LuneTip { Title = "Held anwerben", Glyph = "crown", GlyphColor = 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", }; HeroSpec spec = LuneContent.Heroes[_hero.HeroId]; List names = _hero.Items.Select(id => LuneContent.Items[id].Name).ToList(); var tip = new LuneTip { Title = spec.Name + " · Stufe " + LuneContent.LevelFor(_hero.Xp), Glyph = "crown", GlyphColor = LuneUiTheme.Hero, Subtitle = spec.Title, Text = "Items: " + (names.Count > 0 ? string.Join(", ", names) : "keine"), Note = "Klick: Werte, Items und Talente · H: Kamera zum Helden", }; tip.Stats.AddRange(LuneStats.Rows(_definition)); return tip; } } /// /// Hero window content (Godot LuneHeroDetail). Without a hero: the three heroes with portrait, /// role, real stats and price to recruit one. With a hero: portrait, level and experience, lane /// order, and tabs for values and ability (from the rules' definitions, including level, items /// and talents), the three item slots with the item shop, and the two talent tiers. Only shows /// data; every change is a command through the game. /// public sealed class LuneHeroDetail : VisualElement { public const int TabValues = 0; public const int TabItems = 1; public const int TabTalents = 2; public event Action CommandRequested; public int Tab = TabValues; public readonly Dictionary ItemCards = new Dictionary(); public readonly Dictionary RecruitButtons = new Dictionary(); public readonly Dictionary LaneButtons = new Dictionary(); public readonly List TalentCards = new List(); public LuneTabBar Tabs; LuneHudModel _model; string _signature = ""; public LuneHeroDetail() { pickingMode = PickingMode.Ignore; style.flexGrow = 1f; } public void ShowTab(int index) { Tab = index; _signature = ""; if (_model != null) Apply(_model); } public string TitleText(LuneHudModel model) { HeroState hero = model.OwnHero; return hero == null ? "HELD ANWERBEN" : "HELD · " + LuneContent.Heroes[hero.HeroId].Name.ToUpperInvariant(); } public void Apply(LuneHudModel model) { _model = model; HeroState hero = model.OwnHero; string signature = (hero != null ? hero.HeroId + ":" + hero.Xp + ":" + hero.Lane + ":" + hero.Fresh + ":" + string.Join(",", hero.Items) + ":" + string.Join(",", hero.Talents) + ":" + string.Join(",", hero.FreshItems) : "-") + "|" + model.OwnGold + "|" + model.Planning + "|" + model.Locked + "|" + Tab + "|" + model.State.GrottoAvailable() + "|" + model.State.RoundNumber; if (signature == _signature) return; _signature = signature; Clear(); ItemCards.Clear(); RecruitButtons.Clear(); LaneButtons.Clear(); TalentCards.Clear(); Tabs = null; if (hero == null) Recruit(model); else ShowHero(model, hero); } static bool CanAct(LuneHudModel model) { return model.Planning && !model.Locked; } static string WhyNot(LuneHudModel model) { if (!model.Planning) return "Nur in der Planung."; if (model.Locked) return "Bereit gemeldet – die Planung ist verbindlich."; return ""; } void Recruit(LuneHudModel model) { LuneUi.Wrap(LuneUi.Label("Jeder Spieler führt einen Helden. Er kämpft auf seiner Lane mit, sammelt Erfahrung aus Gefechten in seiner Nähe und aus jeder Runde, trägt drei Items und wählt ab Stufe 3 und 6 je ein Talent.", "HudMuted", this)); VisualElement row = LuneUi.Box(false, 12f, this); row.style.alignItems = Align.Stretch; row.style.flexGrow = 1f; row.style.marginTop = 10f; int gold = model.OwnGold; foreach (KeyValuePair pair in LuneContent.Heroes) { string heroId = pair.Key; HeroSpec spec = pair.Value; var panel = new LunePanel(LunePanels.HudTight); panel.style.flexGrow = 1f; panel.style.flexBasis = 0f; row.Add(panel); VisualElement column = LuneUi.Box(true, 5f, panel); column.style.flexGrow = 1f; var portrait = new LunePortraitElement(); portrait.style.height = 170f; column.Add(portrait); portrait.ShowModel(spec.Model, true); LuneUi.Label(spec.Name, "HudTitle", column); LuneUi.Label(spec.Title, "HudMuted", column); UnitDef definition = model.HeroDefinition(new HeroState { HeroId = heroId }); VisualElement role = LuneUi.Box(false, 5f, column); LuneUi.Glyph(LuneStats.RoleGlyph(definition.Role), LuneUiTheme.BronzeLight, 15f, role); LuneUi.Label(spec.RoleText, "HudText", role); foreach (string[] entry in LuneStats.Rows(definition).Take(5)) { VisualElement line = LuneUi.Box(false, 6f, column); LuneUi.Glyph(entry[0], LuneUiTheme.Muted, 13f, line); LuneUi.Label(entry[1], "HudMuted", line).style.minWidth = 104f; LuneUi.Clip(LuneUi.Label(entry[2], "HudText", line)).style.flexGrow = 1f; } Label summary = LuneUi.Wrap(LuneUi.Label(spec.Summary, "HudText", column)); summary.style.flexGrow = 1f; int cost = spec.Cost; var buy = new LuneButton("ANWERBEN · " + cost + " G", LuneButtons.HudPrimary, column); string why = WhyNot(model); if (why.Length == 0 && gold < cost) why = "Es fehlen " + (cost - gold) + " Gold."; buy.Disabled = why.Length > 0; buy.Clicked += () => CommandRequested?.Invoke(Command.HeroBuy(heroId)); RecruitButtons[heroId] = buy; if (why.Length > 0) LuneUi.SetColor(LuneUi.Wrap(LuneUi.Label(why, "HudMuted", column)), gold < cost ? LuneUiTheme.Danger : LuneUiTheme.Muted); LuneUi.Label("Vorläufige Rolle des vorhandenen Modells.", "HudMuted", column); } } void ShowHero(LuneHudModel model, HeroState hero) { HeroSpec spec = LuneContent.Heroes[hero.HeroId]; UnitDef definition = model.HeroDefinition(hero); bool canAct = CanAct(model); VisualElement body = LuneUi.Box(false, 18f, this); body.style.alignItems = Align.Stretch; body.style.flexGrow = 1f; VisualElement left = LuneUi.Box(true, 6f, body); left.style.width = 290f; var portrait = new LunePortraitElement(); portrait.style.width = 290f; portrait.style.height = 230f; left.Add(portrait); portrait.ShowModel(spec.Model, true); LuneUi.Label(spec.Title, "HudMuted", left); VisualElement role = LuneUi.Box(false, 6f, left); LuneUi.Glyph(LuneStats.RoleGlyph(definition.Role), LuneUiTheme.BronzeLight, 16f, role); LuneUi.Label(spec.RoleText, "HudText", role); int level = LuneContent.LevelFor(hero.Xp); int[] levels = LuneContent.LevelXp; VisualElement levelRow = LuneUi.Box(false, 6f, left); LuneUi.Glyph("star", LuneUiTheme.Hero, 16f, levelRow); LuneUi.Label("STUFE " + level + " / " + levels.Length, "HudValue", levelRow); var bar = new LuneHpBar(0f, 8f) { Fill = LuneUiTheme.BronzeLight }; left.Add(bar); bool top = level >= levels.Length; int low = levels[level - 1]; int high = levels[Math.Min(level, levels.Length - 1)]; bar.Fraction = top ? 1f : (float)(hero.Xp - low) / Math.Max(1, high - low); LuneUi.Label(top ? hero.Xp + " EP · Höchststufe" : hero.Xp + " / " + high + " EP · noch " + (high - hero.Xp) + " bis Stufe " + (level + 1), "HudMuted", left); LuneUi.Label("EINSATZ", "HudCaps", left); VisualElement lanes = LuneUi.Box(false, 4f, left); foreach (string lane in LuneMap.Lanes.Concat(new[] { LuneMatch.Grotto })) { string title = lane == LuneMatch.Grotto ? "GROTTE" : LuneUi.Upper(LuneMap.LaneNames[lane]); var button = new LuneButton(title, LuneButtons.HudToggle, lanes); button.style.flexGrow = 1f; button.On = hero.Lane == lane; button.Disabled = !canAct || (lane == LuneMatch.Grotto && !model.State.GrottoAvailable()); string uid = hero.Uid; string captured = lane; button.Clicked += () => CommandRequested?.Invoke(Command.Assign(uid, captured)); LaneButtons[lane] = button; } if (hero.Lane == LuneMatch.Grotto) LuneUi.SetColor(LuneUi.Wrap(LuneUi.Label("Der Held fehlt seiner Lane in der nächsten Kampfphase.", "HudText", left)), LuneUiTheme.Warn); if (hero.Fresh && model.Planning) { int refund = hero.Invested + hero.Items.Sum(id => LuneContent.Items[id].Cost); var cancel = new LuneButton("Held zurückgeben +" + refund, LuneButtons.HudButton, left); cancel.style.alignSelf = Align.FlexStart; cancel.Disabled = !canAct; cancel.Clicked += () => CommandRequested?.Invoke(Command.HeroCancel()); } VisualElement right = LuneUi.Box(true, 8f, body); right.style.flexGrow = 1f; right.style.flexShrink = 1f; right.style.minWidth = 0f; Tabs = new LuneTabBar(); right.Add(Tabs); Tabs.AddTab("WERTE & FÄHIGKEIT"); Tabs.AddTab("ITEMS " + hero.Items.Count + "/" + LuneContent.ItemSlots); int openTier = hero.Talents.Count; int[] talentLevels = LuneContent.TalentLevels; bool talentReady = openTier < talentLevels.Length && level >= talentLevels[openTier]; LuneButton talents = Tabs.AddTab("TALENTE"); if (talentReady) LuneUi.Glyph("star", LuneUiTheme.Hero, 13f, talents).style.marginLeft = 5f; Tabs.Select(Tab); Tabs.TabChanged += ShowTab; VisualElement page = LuneUi.Box(true, 8f, right); page.style.flexGrow = 1f; if (Tab == TabItems) Items(model, hero, level, page); else if (Tab == TabTalents) Talents(model, hero, level, page); else Values(hero, definition, spec, page); } static void Values(HeroState hero, UnitDef definition, HeroSpec spec, VisualElement page) { List rows = LuneStats.Rows(definition, true); rows.Add(new[] { "sword", "Schaden/Sekunde", "≈ " + LuneUiTheme.Decimal(LuneStats.Dps(definition)) + " (ohne Fähigkeit)" }); VisualElement grid = LuneUi.Box(true, 3f, page); foreach (string[] entry in rows) { VisualElement line = LuneUi.Box(false, 10f, grid); LuneUi.Glyph(entry[0], LuneUiTheme.Muted, 15f, line); LuneUi.Label(entry[1], "HudMuted", line).style.minWidth = 130f; LuneUi.Wrap(LuneUi.Label(entry[2], "HudText", line)).style.flexGrow = 1f; } UnitDef baseDef = spec.Base; LuneUi.Wrap(LuneUi.Label("Grundwerte " + LuneUiTheme.Number(baseDef.MaxHp) + " Leben, " + LuneUiTheme.Decimal(baseDef.Damage) + " Schaden; je Stufe +" + LuneUiTheme.Round(LuneContent.HpPerLevel * 100.0) + " % Leben und +" + LuneUiTheme.Round(LuneContent.DamagePerLevel * 100.0) + " % Schaden (jetzt Stufe " + definition.Level + ").", "HudMuted", page)); LuneUi.Label("FÄHIGKEIT", "HudCaps", page); LuneUi.Wrap(LuneUi.Label(DataCatalog.GetUnit(spec.Model).AbilityDescription ?? "", "HudText", page)); var chosen = new List(); TalentOption[][] options = LuneContent.TalentsOf(hero.HeroId); for (int tier = 0; tier < hero.Talents.Count; tier++) { foreach (TalentOption option in options[tier]) { if (option.Id == hero.Talents[tier]) chosen.Add(option.Name + ": " + option.Text); } } if (chosen.Count > 0) { LuneUi.Label("TALENTE", "HudCaps", page); LuneUi.Wrap(LuneUi.Label(string.Join("\n", chosen), "HudText", page)); } LuneUi.Wrap(LuneUi.Label("Erfahrung: Tode von Gegnern in " + (int)LuneContent.XpRadius + " m (Monster 12 + Welle, Champion 30 + 2 × Welle, Gebäude " + LuneContent.BuildingXp + ", Boss " + LuneContent.BossXp + ") und je Kampf " + LuneContent.XpParticipation + " + " + LuneContent.XpPerRound + " × Welle.", "HudMuted", page)); } void Items(LuneHudModel model, HeroState hero, int level, VisualElement page) { bool canAct = CanAct(model); List items = hero.Items; VisualElement slots = LuneUi.Box(false, 10f, page); slots.style.alignItems = Align.FlexStart; for (int index = 0; index < LuneContent.ItemSlots; index++) { VisualElement box = LuneUi.Box(true, 3f, slots); box.style.flexGrow = 1f; box.style.flexBasis = 0f; var slot = new LuneItemSlot(true); slot.style.width = 46f; slot.style.height = 46f; box.Add(slot); if (index < items.Count) { string itemId = items[index]; ItemSpec item = LuneContent.Items[itemId]; slot.SetItem(itemId, LuneItemSlot.State.Equipped); LuneTooltip.Attach(slot, () => new LuneTip { Title = item.Name, Glyph = itemId, GlyphColor = LuneItemSlot.ColorOf(itemId), Text = item.Text }); LuneUi.Label(item.Name, "HudText", box); bool fresh = hero.FreshItems.Contains(itemId); int refund = fresh ? item.Cost : (int)Math.Floor(item.Cost * LuneContent.ItemRefund); var sell = new LuneButton("Verkaufen +" + refund, LuneButtons.HudButton, box); sell.style.alignSelf = Align.FlexStart; sell.Disabled = !canAct; sell.Clicked += () => CommandRequested?.Invoke(Command.ItemSell(itemId)); } else { slot.SetItem("", LuneItemSlot.State.Empty); LuneUi.Label("Platz " + (index + 1) + " frei", "HudMuted", box); } } LuneUi.Label("ITEMS · einzigartig je Held, bleiben dauerhaft, Rückgabe " + LuneUiTheme.Round(LuneContent.ItemRefund * 100.0) + " %", "HudCaps", page); VisualElement grid = LuneUi.Box(true, 8f, page); int gold = model.OwnGold; VisualElement line = null; int column = 0; foreach (KeyValuePair pair in LuneContent.Items) { string itemId = pair.Key; ItemSpec item = pair.Value; if (column % 2 == 0) line = LuneUi.Box(false, 8f, grid); column++; var card = new LuneItemSlot(false); card.style.flexGrow = 1f; card.style.flexBasis = 0f; line.Add(card); LuneItemSlot.State state = LuneItemSlot.State.Available; if (items.Contains(itemId)) state = LuneItemSlot.State.Unique; else if (!canAct) state = LuneItemSlot.State.Locked; else if (level < item.MinLevel) state = LuneItemSlot.State.Incompatible; else if (items.Count >= LuneContent.ItemSlots) state = LuneItemSlot.State.Full; else if (gold < item.Cost) state = LuneItemSlot.State.Unaffordable; card.SetItem(itemId, state); ItemCards[itemId] = card; string uid = hero.Uid; card.Clicked += () => { if (card.Current == LuneItemSlot.State.Available) CommandRequested?.Invoke(Command.ItemBuy(itemId, uid)); }; string why = state == LuneItemSlot.State.Unique ? "Dein Held trägt es bereits; Items sind einzigartig." : state == LuneItemSlot.State.Locked ? WhyNot(model) : state == LuneItemSlot.State.Incompatible ? "Erfordert Heldenstufe " + item.MinLevel + "." : state == LuneItemSlot.State.Full ? "Alle " + LuneContent.ItemSlots + " Plätze belegt; verkaufe zuerst ein Item." : state == LuneItemSlot.State.Unaffordable ? "Es fehlen " + (item.Cost - gold) + " Gold." : "Klick: kaufen und ausrüsten."; LuneTooltip.Attach(card, () => new LuneTip { Title = item.Name + " · " + item.Cost + " Gold", Glyph = itemId, GlyphColor = LuneItemSlot.ColorOf(itemId), Text = item.Text, Note = why }); } } void Talents(LuneHudModel model, HeroState hero, int level, VisualElement page) { bool canAct = CanAct(model); List talents = hero.Talents; int[] talentLevels = LuneContent.TalentLevels; TalentOption[][] options = LuneContent.TalentsOf(hero.HeroId); for (int tier = 0; tier < talentLevels.Length; tier++) { int required = talentLevels[tier]; string state = tier < talents.Count ? "gewählt" : level >= required && tier == talents.Count ? "jetzt wählbar" : "ab Stufe " + required; LuneUi.Label("TALENT " + (tier + 1) + " · STUFE " + required + " · " + state.ToUpperInvariant(), "HudCaps", page); VisualElement row = LuneUi.Box(false, 8f, page); row.style.alignItems = Align.Stretch; for (int choice = 0; choice < options[tier].Length; choice++) { TalentOption option = options[tier][choice]; var card = new LuneButton(null, LuneButtons.HudCard, row); card.style.flexGrow = 1f; card.style.flexBasis = 0f; card.style.minHeight = 96f; card.style.flexDirection = FlexDirection.Column; card.style.alignItems = Align.Stretch; card.style.justifyContent = Justify.FlexStart; card.style.paddingLeft = 10f; card.style.paddingRight = 10f; card.style.paddingTop = 8f; card.style.paddingBottom = 8f; LuneUi.Label(option.Name, "HudValue", card); LuneUi.Wrap(LuneUi.Label(option.Text, "HudMuted", card)); bool chosen = tier < talents.Count && talents[tier] == option.Id; card.On = chosen; card.Disabled = !(canAct && tier == talents.Count && level >= required) && !chosen; int capturedTier = tier; int capturedChoice = choice; card.Clicked += () => { if (!chosen) CommandRequested?.Invoke(Command.Talent(capturedTier, capturedChoice)); }; TalentCards.Add(card); } } LuneUi.Wrap(LuneUi.Label("Die Wahl ist endgültig; jede Stufe bietet zwei Richtungen (Einzelziel oder Fläche, Schutz oder Druck).", "HudMuted", page)); } } }