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. H1: the command slot shows the ultimate of a hero that has one (its
/// planned trigger rule, in combat whether it fired) and opens the hero window on it.
///
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;
LuneUnit _live;
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;
_live = live;
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) + ":" + hero.Ultimate + ":" + hero.Skin : "-") + "|"
+ (live != null) + "|" + (live != null ? live.UltimateSerial : 0) + "|" + 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(string.IsNullOrEmpty(hero.Skin) ? heroSpec.Model : heroSpec.Model + "#" + hero.Skin);
_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);
}
// H1: heroes with an ultimate show it with its planned rule; the reference heroes have none.
bool command = LuneContent.HasUltimate(HeroId);
LuneUi.Show(CommandSlot, command);
if (command)
{
string rule = LuneContent.UltimateRuleFor(hero);
string ruleName = LuneContent.UltimateRulesOf(HeroId).First(r => r.Id == rule).Name;
bool fired = _live != null && _live.UltimateSerial > 0;
// Short: the slot shares the row with the item slots and the talent badge.
CommandSlot.Text = fired ? "ULT GENUTZT" : "ULT · " + ruleName.ToUpperInvariant();
CommandSlot.On = fired;
}
}
LuneTip Tip()
{
if (_hero == null || _definition == null)
return new LuneTip
{
Title = "Held anwerben", Glyph = "crown", GlyphColor = LuneUiTheme.Hero,
Text = LuneContent.Heroes.Count + " 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;
/// H1: ultimate rule and skin of heroes that have them.
public const int TabUltimate = 3;
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 readonly Dictionary RuleCards = new Dictionary();
public readonly Dictionary SkinCards = new Dictionary();
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) + ":" + hero.Ultimate + ":" + hero.Skin : "-")
+ "|" + 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();
RuleCards.Clear();
SkinCards.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);
if (spec.Provisional)
LuneUi.Wrap(LuneUi.Label("Vorläufige Rolle des vorhandenen Modells.", "HudMuted", column));
else if (spec.UltimateName.Length > 0)
LuneUi.SetColor(LuneUi.Label("Ultimate: " + spec.UltimateName, "HudText", column), LuneUiTheme.Hero);
}
}
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(string.IsNullOrEmpty(hero.Skin) ? spec.Model : spec.Model + "#" + hero.Skin, 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);
// Godot: a plain button in the column fills its width.
var cancel = new LuneButton("Held zurückgeben +" + refund, LuneButtons.HudButton, left);
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;
bool special = LuneContent.HasUltimate(hero.HeroId) || LuneContent.SkinsOf(hero.HeroId).Length > 0;
if (special)
Tabs.AddTab("ULTIMATE & SKIN");
if (Tab == TabUltimate && !special)
Tab = TabValues;
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 if (Tab == TabUltimate)
Ultimate(model, hero, spec, 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);
// H1 heroes bring their own kit text; the reference heroes use their troop model's ability.
string ability = spec.AbilityText.Length > 0 ? spec.AbilityText : DataCatalog.GetUnit(spec.Model).AbilityDescription ?? "";
if (spec.PassiveText.Length > 0)
ability = spec.PassiveText + "\n" + ability;
if (spec.UltimateName.Length > 0)
ability += "\nUltimate " + spec.UltimateName + ": " + spec.UltimateText;
LuneUi.Wrap(LuneUi.Label(ability, "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));
}
///
/// H1: the ultimate and its trigger rule (one charge per combat, released automatically
/// when the rule holds; the rule can change in every planning), and the cosmetic skins.
///
void Ultimate(LuneHudModel model, HeroState hero, HeroSpec spec, VisualElement page)
{
bool canAct = CanAct(model);
TalentOption[] rules = LuneContent.UltimateRulesOf(hero.HeroId);
if (rules.Length > 0)
{
LuneUi.Label("ULTIMATE · " + spec.UltimateName.ToUpperInvariant(), "HudCaps", page);
LuneUi.Wrap(LuneUi.Label(spec.UltimateText, "HudText", page));
LuneUi.Label("AUSLÖSER · jederzeit in der Planung änderbar, kostenlos", "HudCaps", page);
string current = LuneContent.UltimateRuleFor(hero);
VisualElement row = LuneUi.Box(false, 8f, page);
row.style.alignItems = Align.Stretch;
foreach (TalentOption rule in rules)
{
var card = new LuneButton(null, LuneButtons.HudCard, row);
card.style.flexGrow = 1f;
card.style.flexBasis = 0f;
card.style.minHeight = 84f;
card.style.flexDirection = FlexDirection.Column;
card.style.alignItems = Align.Stretch;
card.style.justifyContent = Justify.FlexStart;
card.style.paddingLeft = 8f;
card.style.paddingRight = 8f;
card.style.paddingTop = 6f;
card.style.paddingBottom = 6f;
LuneUi.Label(rule.Name + (rule == rules[0] ? " (Standard)" : ""), "HudValue", card);
LuneUi.Wrap(LuneUi.Label(rule.Text, "HudMuted", card));
bool on = rule.Id == current;
card.On = on;
card.Disabled = !canAct && !on;
string id = rule.Id;
card.Clicked += () =>
{
if (!on)
CommandRequested?.Invoke(Command.UltimateRule(id));
};
RuleCards[id] = card;
}
}
TalentOption[] skins = LuneContent.SkinsOf(hero.HeroId);
if (skins.Length == 0)
return;
LuneUi.Label("SKIN · nur das Aussehen, für alle sichtbar", "HudCaps", page);
VisualElement looks = LuneUi.Box(false, 8f, page);
looks.style.alignItems = Align.Stretch;
var options = new List { new TalentOption("", "Mondsilber", "Standard-Aussehen.") };
options.AddRange(skins);
foreach (TalentOption skin in options)
{
var card = new LuneButton(null, LuneButtons.HudCard, looks);
card.style.width = 170f;
card.style.flexDirection = FlexDirection.Column;
card.style.alignItems = Align.Stretch;
card.style.paddingLeft = 6f;
card.style.paddingRight = 6f;
card.style.paddingTop = 6f;
card.style.paddingBottom = 6f;
var face = new LunePortraitElement();
face.style.height = 120f;
face.pickingMode = PickingMode.Ignore;
card.Add(face);
face.ShowModel(skin.Id.Length == 0 ? spec.Model : spec.Model + "#" + skin.Id, true);
LuneUi.Label(skin.Name, "HudValue", card);
bool on = (hero.Skin ?? "") == skin.Id;
card.On = on;
card.Disabled = !canAct && !on;
string id = skin.Id;
card.Clicked += () =>
{
if (!on)
CommandRequested?.Invoke(Command.HeroSkin(id));
};
LuneTooltip.Attach(card, () => new LuneTip { Title = skin.Name, Glyph = "crown", GlyphColor = LuneUiTheme.Hero, Text = skin.Text });
SkinCards[id] = card;
}
}
}
}