Hinter MatchOptions.Extended (Standard im Spiel, Schalter im Startbildschirm): Söldner-Einkommen (Angriffschampion +Preis/12 Einkommen dauerhaft), Kasernen (gefallener innerer Turm: Gegnerwellen +25 % und ein Monster mehr), Erschöpfung und Rückkauf (60 % Leben, Rückkauf 60 + 20 x Stufe), Mondsegen (Grottensieg +4 % je Stapel, Mondseele bei 4). Bots wählen mit erweiterten Regeln auch Sylvara und kaufen Helden zurück. Netz überträgt Regeln und neue Zustände. HUD: Einkommen, Kasernen-Symbol, Rückkauf, Segen im Teamkopf. Kern 531/531 (s1 31), EditMode 31/31, PlayMode 16/16, Verify 718/0, Selbsttest 30/30, Golden 0 Abweichungen, Fingerabdruck unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
541 lines
27 KiB
C#
541 lines
27 KiB
C#
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;
|
||
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);
|
||
_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);
|
||
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");
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|