champions-of-luneraria/Assets/Luneraria/UI/Hud/LuneLanePanels.cs
Martin Stickelbroeck fb5c92dd37 S3 Neutrallager und Fog of War mit Spähern (LoL, Dota 2)
Erweiterte Regeln: in jeder dritten Runde ein neutraler Wächter in jeder
Lanemitte; wer beim Fall mehr Einheiten (dann mehr Leben) in 8 m hat, nimmt
das Lager: Glutsegen (+10 % Schaden), Mondschild (Schild 10 %), Beute
(50 Gold je Spieler). Fog of War (LuneFog, nur Darstellung): Gegner sichtbar
in 9 m um eigene Einheiten, 10 m um Gebäude, 12 m um Späher; Späher (40 Gold,
3 Runden, Teamwirkung) zeigen in der Planung die gegnerische Armee ihrer Lane.
HUD: Teampanel SPÄHER und LAGER, Lager-Sechseck, Nebel in Feld, Minimap,
Namensschildern und Lane-Zählung.
Kern 597/597 (s3 31), EditMode 33/33, PlayMode 18/18, Verify 718/0,
Selbsttest 30/30, Golden 0, Fingerabdruck unverändert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:30:57 +02:00

549 lines
27 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using Luneraria.Core;
using UnityEngine;
using UnityEngine.UIElements;
namespace Luneraria.UI
{
/// <summary>
/// One lane in the lane overview (Godot LuneLaneIndicator): name and hotkey, the threat level
/// (text, symbol and colour), the tower chain from the own base to the enemy base, the two lane
/// players with their heroes, announced champions or the boss, and in combat the unit count of
/// both sides. A click moves the camera. The threat rises at once but only falls after a short
/// hold, so it does not flicker.
/// </summary>
public sealed class LuneLaneIndicator : LuneButton
{
public const float Hold = 2.5f;
public readonly string Lane;
public readonly string Hotkey;
public int Level { get; private set; }
/// <summary>The threat badge pulses (a lane just turned critical; off with reduced motion).</summary>
public bool Pulsing { get { return _pulse > 0f; } }
public LuneHudModel.LaneInfo Data { get; private set; }
public readonly LuneTowerIndicator[] Towers = new LuneTowerIndicator[4];
readonly LuneStatusBadge _badge;
readonly Label _own;
readonly Label _enemy;
readonly LunePortraitElement _ownHero;
readonly LunePortraitElement _enemyHero;
readonly Label _army;
readonly VisualElement _dangers;
readonly LuneGlyphElement _mine;
readonly LuneGlyphElement _ownShape;
readonly LuneGlyphElement _enemyShape;
/// <summary>S1 barracks: crossed swords in the own team colour (own waves reinforced) or red (enemy waves).</summary>
public readonly LuneGlyphElement Barracks;
/// <summary>S3: neutral hexagon while a camp guardian stands on the lane this round.</summary>
public readonly LuneGlyphElement CampMark;
float _lowerHold;
float _pulse;
string _signature = "";
public LuneLaneIndicator(string lane, string hotkey) : base(null, LuneButtons.HudRow)
{
Lane = lane;
Hotkey = hotkey;
style.minHeight = 92f;
style.flexDirection = FlexDirection.Column;
style.alignItems = Align.Stretch;
style.justifyContent = Justify.FlexStart;
style.paddingLeft = 10f;
style.paddingRight = 9f;
style.paddingTop = 6f;
style.paddingBottom = 6f;
var column = (LuneBox)LuneUi.Box(true, 3f, this);
VisualElement head = LuneUi.Box(false, 6f, column);
LuneUi.Glyph("lane_" + lane, LuneUiTheme.BronzeLight, 17f, head);
LuneUi.Label(LuneUi.Upper(LuneMap.LaneNames[lane]), "HudLane", head);
_mine = LuneUi.Glyph("star", LuneUiTheme.BronzeLight, 12f, head);
LuneUi.Show(_mine, false);
LuneUi.Spacer(head);
Barracks = LuneUi.Glyph("melee", LuneUiTheme.Muted, 14f, head);
LuneUi.Show(Barracks, false);
CampMark = LuneUi.Glyph("emblem_2", LuneUiTheme.Team[2], 14f, head);
LuneUi.Show(CampMark, false);
_badge = new LuneStatusBadge();
head.Add(_badge);
VisualElement chain = LuneUi.Box(false, 3f, column);
_ownShape = LuneUi.Glyph("emblem_0", LuneUiTheme.Team[0], 13f, chain);
for (int index = 0; index < 4; index++)
{
var tower = new LuneTowerIndicator();
chain.Add(tower);
Towers[index] = tower;
int captured = index;
LuneTooltip.Attach(tower, () => TowerTip(captured));
if (index == 1)
{
VisualElement middle = LuneUi.Box(false, 4f, chain);
middle.style.flexGrow = 1f;
middle.style.justifyContent = Justify.Center;
_army = LuneUi.Label("", "HudValue", middle);
_dangers = LuneUi.Box(false, 2f, middle);
}
}
_enemyShape = LuneUi.Glyph("emblem_1", LuneUiTheme.Team[1], 13f, chain);
VisualElement names = LuneUi.Box(false, 5f, column);
// Heroes of both lane players (Dota-style "who stands where"), committed heroes only.
_ownHero = new LunePortraitElement();
_ownHero.style.width = 22f;
_ownHero.style.height = 22f;
names.Add(_ownHero);
_own = LuneUi.Clip(LuneUi.Label("", "HudMuted", names));
_own.style.flexGrow = 1f;
_own.style.flexBasis = 0f;
_enemy = LuneUi.Clip(LuneUi.Label("", "HudMuted", names));
_enemy.style.flexGrow = 1f;
_enemy.style.flexBasis = 0f;
_enemy.style.unityTextAlign = TextAnchor.MiddleRight;
_enemyHero = new LunePortraitElement();
_enemyHero.style.width = 22f;
_enemyHero.style.height = 22f;
names.Add(_enemyHero);
LuneTooltip.Attach(this, Tip);
}
/// <summary>Applies a lane from LuneHudModel.Lane.</summary>
public void Apply(LuneHudModel.LaneInfo entry, int ownTeam, float delta, bool reducedMotion)
{
Data = entry;
int threat = entry.Threat.Level;
if (threat >= Level)
{
if (threat == LuneLaneThreat.Critical && Level != threat && !reducedMotion)
_pulse = 1.6f;
Level = threat;
_lowerHold = Hold;
}
else
{
_lowerHold -= delta;
if (_lowerHold <= 0f)
{
Level = threat;
_lowerHold = Hold;
}
}
Color tint = LuneUiTheme.Threat[Level];
_badge.SetState(LuneLaneThreat.Names[Level], tint, Level >= 2 ? LuneLaneThreat.Glyphs[Level] : "", Level == LuneLaneThreat.Critical);
LuneUi.Show(Barracks, entry.OwnBarracks || entry.EnemyBarracks);
LuneUi.Show(CampMark, entry.Camp.Length > 0);
Barracks.Color = entry.EnemyBarracks ? LuneUiTheme.Danger : LuneUiTheme.TeamColor(ownTeam);
_ownShape.Glyph = "emblem_" + ownTeam;
_ownShape.Color = LuneUiTheme.TeamColor(ownTeam);
_enemyShape.Glyph = "emblem_" + (1 - ownTeam);
_enemyShape.Color = LuneUiTheme.TeamColor(1 - ownTeam);
for (int index = 0; index < 4; index++)
{
LuneHudModel.BuildingInfo tower = entry.Towers[index];
Towers[index].Team = tower.Team;
Towers[index].Tier = tower.Tier;
Towers[index].SetState(tower.Alive, (float)tower.Fraction, tower.Attackable);
}
_own.text = entry.OwnPlayer;
LuneUi.SetColor(_own, entry.OwnIsMe ? LuneUiTheme.BronzeLight : LuneUiTheme.Muted);
_enemy.text = entry.EnemyPlayer;
ShowHero(_ownHero, entry.OwnHero);
ShowHero(_enemyHero, entry.EnemyHero);
LuneUi.Show(_mine, entry.OwnIsMe);
_army.text = entry.HasCounts ? entry.OwnCount + " : " + entry.EnemyCount : "";
string signature = string.Join(",", entry.Dangers.Select(d => d.Kind + ":" + d.Team + ":" + d.ChampionId));
if (signature != _signature)
{
_signature = signature;
_dangers.Clear();
foreach (Announcement danger in entry.Dangers)
{
bool hostile = danger.Team != ownTeam;
LuneUi.Glyph(danger.Kind == "boss" ? "crown" : "champion", hostile ? LuneUiTheme.Danger : LuneUiTheme.TeamColor(danger.Team), 16f, _dangers);
}
}
if (_pulse > 0f)
{
_pulse = Mathf.Max(0f, _pulse - delta);
_badge.style.opacity = _pulse > 0f ? .55f + .45f * Mathf.Abs(Mathf.Cos(_pulse * Mathf.PI * 2f)) : 1f;
}
else if (_badge.style.opacity != 1f)
{
_badge.style.opacity = 1f;
}
LuneTooltip.Current?.Refresh(this);
}
static void ShowHero(LunePortraitElement portrait, LuneHudModel.HeroBrief hero)
{
LuneUi.Show(portrait, hero != null);
if (hero != null)
portrait.ShowModel(hero.Model);
}
LuneTip Tip()
{
if (Data == null)
return null;
var lines = new List<string>();
foreach (string reason in Data.Threat.Reasons)
lines.Add("• " + reason);
if (lines.Count == 0)
lines.Add("Keine Bedrohung aus Türmen, Kampflage oder Ankündigungen.");
var tip = new LuneTip
{
Title = Data.Title + "-LANE · " + LuneLaneThreat.Names[Level], Glyph = "lane_" + Lane,
Subtitle = PlayerText(Data.OwnPlayer, Data.OwnHero) + " gegen " + PlayerText(Data.EnemyPlayer, Data.EnemyHero),
Text = string.Join("\n", lines),
};
for (int index = 0; index < 2; index++)
tip.Stat("tower", "Eigene " + LuneHudModel.BuildingTitle(Data.Towers[index].Id), TowerValue(Data.Towers[index]));
for (int index = 2; index < 4; index++)
tip.Stat("tower", "Gegner " + LuneHudModel.BuildingTitle(Data.Towers[index].Id), TowerValue(Data.Towers[index]));
if (Data.HasCounts)
tip.Stat("melee", "Einheiten", Data.OwnCount + " eigene · " + Data.EnemyCount + " gegnerische");
// S1 barracks (Dota 2): a fallen inner tower reinforces the attackers' waves on this lane for good.
if (Data.OwnBarracks)
tip.Stat("melee", "Kaserne", "gegnerischer innerer Turm gefallen: eure Wellen +25 % und ein Monster mehr");
if (Data.EnemyBarracks)
tip.Stat("warning", "Kaserne", "euer innerer Turm ist gefallen: gegnerische Wellen +25 % und ein Monster mehr");
// S3 camps (League of Legends): a guardian in the middle of the lane this round.
if (Data.Camp.Length > 0)
tip.Stat("emblem_2", "Lager", Data.Camp);
var notes = new List<string>();
foreach (Announcement danger in Data.Dangers)
{
string side = LuneUiTheme.TeamName(danger.Team);
notes.Add(danger.Kind == "boss" ? "Schlundkrone (" + side + ") greift an" : LuneContent.Champions[danger.ChampionId].Name + " (" + side + ") angekündigt");
}
tip.Note = (notes.Count > 0 ? string.Join("; ", notes) + "\n" : "") + "Klick: Kamera auf die Lane" + (Hotkey.Length > 0 ? " (" + Hotkey + ")" : "");
return tip;
}
static string PlayerText(string player, LuneHudModel.HeroBrief hero)
{
return hero == null ? player : player + " (" + hero.Name + ", Stufe " + hero.Level + ")";
}
LuneTip TowerTip(int index)
{
if (Data == null)
return null;
LuneHudModel.BuildingInfo tower = Data.Towers[index];
string text = !tower.Alive ? "Zerstört – die Ruine blockiert nicht." : tower.Attackable ? "Angreifbar." : "Noch geschützt: erst fällt der Turm davor.";
return new LuneTip { Title = LuneHudModel.BuildingTitle(tower.Id) + " · " + LuneUiTheme.TeamName(tower.Team), Glyph = "tower", GlyphColor = LuneUiTheme.TeamColor(tower.Team), Text = text }
.Stat("heart", "Leben", TowerValue(tower));
}
public static string TowerValue(LuneHudModel.BuildingInfo tower)
{
if (!tower.Alive)
return "zerstört";
return LuneUiTheme.Number(tower.Hp) + " / " + LuneUiTheme.Number(tower.MaxHp) + " (" + LuneUiTheme.Round(tower.Fraction * 100.0) + " %)";
}
}
/// <summary>
/// The boss grotto and the Schlundkrone at a glance (Godot LuneBossIndicator): BOSS VERFÜGBAR,
/// BOSS UMKÄMPFT, BOSS EROBERT, EINSATZ NÄCHSTE WELLE, BOSS GREIFT AN, COOLDOWN – X RUNDEN. When
/// a team owns the boss the card names target lane, deployment wave and the bounty the defenders
/// earn; the player who steers the boss chooses its lane here. A click moves the camera.
/// </summary>
public sealed class LuneBossIndicator : LunePanel
{
public event Action FocusRequested;
public event Action<string> LaneChosen;
public LuneHudModel.BossInfo Data { get; private set; }
public readonly LunePortraitElement Portrait;
public bool Compact { get; private set; }
readonly LuneStatusBadge _badge;
readonly Label _line;
readonly Label _risk;
readonly LuneHpBar _bar;
readonly VisualElement _choice;
public readonly Dictionary<string, LuneButton> LaneButtons = new Dictionary<string, LuneButton>();
readonly LuneGlyphElement _owner;
string _signature = "";
int _ownTeam;
int _round;
bool _planning = true;
public LuneBossIndicator() : base(LunePanels.HudTight)
{
VisualElement row = LuneUi.Box(false, 10f, this);
row.style.alignItems = Align.FlexStart;
Portrait = new LunePortraitElement();
Portrait.style.width = 64f;
Portrait.style.height = 64f;
row.Add(Portrait);
_owner = LuneUi.Glyph("emblem_2", LuneUiTheme.Team[2], 18f, Portrait);
_owner.style.position = Position.Absolute;
_owner.style.left = 2f;
_owner.style.bottom = 2f;
VisualElement column = LuneUi.Box(true, 3f, row);
column.style.flexGrow = 1f;
column.style.flexShrink = 1f;
column.style.minWidth = 0f;
VisualElement head = LuneUi.Box(false, 6f, column);
LuneUi.Glyph("grotto", LuneUiTheme.Team[2], 15f, head);
LuneUi.Label("BOSSGROTTE · G", "HudCaps", head);
_badge = new LuneStatusBadge();
_badge.style.alignSelf = Align.FlexStart;
column.Add(_badge);
_line = LuneUi.Wrap(LuneUi.Label("", "HudText", column));
_risk = LuneUi.Wrap(LuneUi.Label("", "HudMuted", column));
_bar = new LuneHpBar(0f, 5f);
LuneUi.Show(_bar, false);
column.Add(_bar);
_choice = LuneUi.Box(false, 4f, column);
LuneUi.Show(_choice, false);
foreach (string lane in LuneMap.Lanes)
{
var button = new LuneButton(LuneUi.Upper(LuneMap.LaneNames[lane]), LuneButtons.HudToggle, _choice);
button.ToggleMode = true;
button.style.flexGrow = 1f;
string captured = lane;
button.Clicked += () => LaneChosen?.Invoke(captured);
LaneButtons[lane] = button;
}
RegisterCallback<PointerDownEvent>(evt =>
{
if (evt.button == 0)
FocusRequested?.Invoke();
});
LuneTooltip.Attach(this, Tip);
}
/// <summary>Short form while the right column shows a selected unit: badge only.</summary>
public void SetCompact(bool value)
{
if (value == Compact)
return;
Compact = value;
Portrait.style.width = value ? 40f : 64f;
Portrait.style.height = value ? 40f : 64f;
_signature = "";
if (Data != null)
Apply(Data, _ownTeam, _round, _planning);
}
public void Apply(LuneHudModel.BossInfo boss, int ownTeam, int round, bool planning)
{
string signature = boss + "|" + ownTeam + "|" + round + "|" + planning;
if (signature == _signature)
return;
_signature = signature;
Data = boss;
_ownTeam = ownTeam;
_round = round;
_planning = planning;
Portrait.ShowModel("schlundkrone");
int owner = boss.Team;
bool own = owner == ownTeam;
string lane = LuneUi.Upper(LuneMap.LaneNames.TryGetValue(boss.Lane, out string name) ? name : "?");
string side = LuneUiTheme.TeamName(owner);
bool showOwner = owner >= 0;
_owner.Glyph = "emblem_" + Mathf.Clamp(owner, 0, 2);
_owner.Color = LuneUiTheme.TeamColor(owner);
LuneUi.Show(_bar, false);
LuneUi.Show(_choice, false);
_risk.text = "";
switch (boss.State)
{
case "available":
_badge.SetState("BOSS VERFÜGBAR", LuneUiTheme.Team[2], "crown");
_line.text = "Einheiten mit „Grotte“ entsenden. Wer den Boss besiegt, schickt ihn in der Folgewelle gegen den Gegner.";
break;
case "contested":
_badge.SetState("BOSS UMKÄMPFT", LuneUiTheme.Warn, "melee");
_line.text = "Expeditionen kämpfen in der Grotte; sie fehlen ihren Lanes bis Kampfende.";
break;
case "conquered":
_badge.SetState("BOSS EROBERT · " + LuneUi.Upper(side), LuneUiTheme.TeamColor(owner), "crown", true);
_line.text = "Einsatz in Welle " + boss.DeployRound + " für " + (own ? "dein Team" : side) + ".";
break;
case "failed":
_badge.SetState("EXPEDITION GESCHEITERT", LuneUiTheme.Muted, "close");
_line.text = "Der Boss hat überlebt; niemand erhält ihn.";
break;
case "deploy_next":
_badge.SetState("EINSATZ NÄCHSTE WELLE", own ? LuneUiTheme.TeamColor(owner) : LuneUiTheme.Danger, "crown", true);
if (own)
{
_line.text = "Deine Schlundkrone greift in Welle " + boss.DeployRound + " auf " + lane + " an.";
_risk.text = "Fällt sie, erhält " + LuneUiTheme.TeamName(1 - ownTeam) + " " + LuneUiTheme.Number(boss.Bounty) + " Gold Kopfgeld.";
}
else
{
_line.text = side + ": Schlundkrone greift in Welle " + boss.DeployRound + " auf " + lane + " an.";
_risk.text = "Besiegt ihr sie, erhält dein Team " + LuneUiTheme.Number(boss.Bounty) + " Gold Kopfgeld.";
}
if (boss.Chooser && planning)
{
LuneUi.Show(_choice, true);
foreach (KeyValuePair<string, LuneButton> pair in LaneButtons)
pair.Value.On = pair.Key == boss.Lane;
_risk.text += " Du wählst die Lane:";
}
break;
case "deploying":
_badge.SetState("BOSS GREIFT AN · " + lane, own ? LuneUiTheme.TeamColor(owner) : LuneUiTheme.Danger, "warning", true);
_line.text = own ? "Deine Schlundkrone greift " + lane + " an." : "Gegnerische Schlundkrone auf " + lane + "!";
_risk.text = "Kopfgeld für die Verteidiger: " + LuneUiTheme.Number(boss.Bounty) + " Gold.";
if (boss.HasHp)
{
LuneUi.Show(_bar, true);
_bar.Fraction = (float)(boss.Hp / Math.Max(1.0, boss.MaxHp));
_bar.Fill = LuneUiTheme.TeamColor(owner);
}
break;
case "defeated":
_badge.SetState("SCHLUNDKRONE BESIEGT", !own ? LuneUiTheme.Good : LuneUiTheme.Muted, "check");
_line.text = "Das Kopfgeld von " + LuneUiTheme.Number(boss.Bounty) + " Gold geht an " + (own ? "den Gegner" : "dein Team") + ".";
break;
default:
int left = boss.RoundsLeft;
_badge.SetState("COOLDOWN – " + left + (left == 1 ? " RUNDE" : " RUNDEN"), LuneUiTheme.Muted, "clock");
_line.text = "Grotte wieder offen ab Welle " + boss.NextRound + ".";
break;
}
LuneUi.Show(_risk, _risk.text.Length > 0 && !Compact);
LuneUi.Show(_line, !Compact);
LuneUi.Show(_owner, showOwner && !Compact);
if (Compact)
LuneUi.Show(_choice, false);
LuneTooltip.Current?.Refresh(this);
}
/// <summary>Live boss HP during its attack, without rebuilding the texts.</summary>
public void SetBossHp(float fraction)
{
if (LuneUi.Visible(_bar))
_bar.Fraction = fraction;
}
public string BadgeText { get { return _badge.Text; } }
public string LineText { get { return _line.text; } }
static LuneTip Tip()
{
return new LuneTip
{
Title = "Bossgrotte · Schlundkrone", Glyph = "crown", GlyphColor = LuneUiTheme.Team[2],
Text = "Expeditionen kämpfen zuerst gegeneinander; der Boss erwacht, wenn höchstens ein Team übrig ist. Sieg: Der Boss greift in der Folgewelle für die Sieger an, die Verteidiger erhalten bei seinem Fall ein hohes Kopfgeld. Danach ist die Grotte "
+ LuneContent.BossLockRounds + " Runden gesperrt.",
Note = "Klick oder G: Kamera zur Grotte",
};
}
}
/// <summary>
/// What the coming wave brings (Godot LuneNextWave): monster types with counts (identical for
/// both sides and every lane), new types, the armour most of the wave wears and the damage type
/// that hits it for 130 %, announced attack champions and an announced boss. Dangers for the
/// own team are highlighted. In combat the panel shrinks to one line about the following wave.
/// </summary>
public sealed class LuneNextWave : LunePanel
{
readonly Label _title;
readonly Label _round;
readonly VisualElement _rows;
readonly VisualElement _extra;
readonly Label _note;
string _signature = "";
public bool Compact { get; private set; }
public LuneNextWave() : base(LunePanels.HudTight)
{
VisualElement column = LuneUi.Box(true, 4f, this);
VisualElement head = LuneUi.Box(false, 0f, column);
_title = LuneUi.Label("NÄCHSTE WELLE", "HudCaps", head);
_title.style.flexGrow = 1f;
_round = LuneUi.Label("", "HudValue", head);
_rows = LuneUi.Box(true, 2f, column);
_note = LuneUi.Wrap(LuneUi.Label("", "HudMuted", column));
_extra = LuneUi.Box(true, 3f, column);
}
public string NoteText { get { return _note.text; } }
public void Apply(LuneHudModel.WaveInfo wave, int ownTeam, bool fighting)
{
string signature = wave + "|" + ownTeam + "|" + fighting;
if (signature == _signature)
return;
_signature = signature;
Compact = fighting;
_title.text = fighting ? "DANACH" : "NÄCHSTE WELLE";
_round.text = "WELLE " + wave.Round;
_rows.Clear();
_extra.Clear();
if (fighting)
{
_note.text = string.Join(", ", wave.Monsters.Select(m => m.Count + "× " + m.Name + (m.New ? " (neu)" : ""))) + " je Lane";
return;
}
foreach (LuneHudModel.MonsterGroup monster in wave.Monsters)
{
VisualElement row = LuneUi.Box(false, 7f, _rows);
row.pickingMode = PickingMode.Position;
var portrait = new LunePortraitElement();
portrait.style.width = 30f;
portrait.style.height = 30f;
row.Add(portrait);
portrait.ShowModel(monster.Id);
LuneUi.Label(monster.Count + "×", "HudValue", row).style.minWidth = 30f;
Label title = LuneUi.Clip(LuneUi.Label(monster.Name, "HudText", row));
title.style.flexGrow = 1f;
LuneUi.Glyph("armor", LuneTypeColors.Armor(monster.Armor), 13f, row);
LuneUi.SetColor(LuneUi.Label(LuneStats.ArmorName(monster.Armor), "HudMuted", row), LuneTypeColors.Armor(monster.Armor));
if (monster.New)
{
var badge = new LuneStatusBadge();
badge.SetState("NEU", LuneUiTheme.Warn);
row.Add(badge);
}
LuneHudModel.MonsterGroup data = monster;
int round = wave.Round;
LuneTooltip.Attach(row, () => new LuneTip { Title = data.Name, Subtitle = "Monster der Welle " + round, Note = "Beide Teams erhalten auf jeder Lane dieselbe Welle." }
.Stat("heart", "Leben", LuneUiTheme.Number(data.Hp)).Stat("sword", "Schaden/Treffer", LuneUiTheme.Decimal(data.Damage))
.Stat("range", "Reichweite", LuneUiTheme.Decimal(data.Range) + " m").Stat("armor", "Rüstung", LuneStats.ArmorName(data.Armor)));
}
// Counter hint as in Legion TD 2: the armour most of the wave wears and the damage type
// that hits it for 130 % (DamageMatrix).
int[] armour = new int[3];
foreach (LuneHudModel.MonsterGroup monster in wave.Monsters)
armour[Mathf.Clamp(monster.Armor, 0, 2)] += monster.Count;
int main = Array.IndexOf(armour, armour.Max());
int counter = 0;
for (int attack = 0; attack < 3; attack++)
{
if (Math.Abs(LuneUnit.DamageMatrix[attack, main] - 1.30) < 1e-9)
counter = attack;
}
_note.text = wave.Count + " Monster je Lane · beide Seiten gleich\nMeist " + LuneStats.ArmorName(main) + ": " + LuneStats.DamageName(counter) + " trifft stark";
if (wave.Champions.Count == 0 && wave.Boss == null)
return;
LuneUi.Label("ANGEKÜNDIGT", "HudCaps", _extra);
foreach (LuneHudModel.ChampionNotice entry in wave.Champions)
{
bool hostile = !entry.Own;
VisualElement row = LuneUi.Box(false, 6f, _extra);
LuneUi.Glyph("champion", hostile ? LuneUiTheme.Danger : LuneUiTheme.Champion, 15f, row);
LuneUi.Clip(LuneUi.Label(entry.Name + " → " + LuneUi.Upper(LuneMap.LaneNames[entry.Lane]), "HudText", row)).style.flexGrow = 1f;
var badge = new LuneStatusBadge();
badge.SetState(hostile ? "GEGEN EUCH" : "FÜR EUCH", hostile ? LuneUiTheme.Danger : LuneUiTheme.Good);
row.Add(badge);
}
if (wave.Boss != null)
{
bool hostile = !wave.Boss.Own;
VisualElement row = LuneUi.Box(false, 6f, _extra);
LuneUi.Glyph("crown", hostile ? LuneUiTheme.Danger : LuneUiTheme.TeamColor(ownTeam), 16f, row);
LuneUi.Clip(LuneUi.Label("Schlundkrone → " + LuneUi.Upper(LuneMap.LaneNames[wave.Boss.Lane]), "HudText", row)).style.flexGrow = 1f;
var badge = new LuneStatusBadge();
badge.SetState(hostile ? "GEGEN EUCH" : "FÜR EUCH", hostile ? LuneUiTheme.Danger : LuneUiTheme.Good, "crown");
row.Add(badge);
}
}
}
}