champions-of-luneraria/Assets/Luneraria/UI/Model/LuneStats.cs
Martin Stickelbroeck 4dee9b18d2 U3A HUD-Grundlagen: UI Toolkit, Sichtmodell, menschlicher Sitz
Beginn des Ports des G3-HUD (Referenz luneraria/g3, 1b540f2).

- Entscheidung UI Toolkit statt uGUI nach kurzer Bewertung und einer Probe:
  Panel rendert im Batchmodus in eine 1920x1080-Textur (Skalierung wie Godot
  canvas_items/expand, 1600x960), gefaste Verlaufsplatten, alle 50 Symbole,
  Schriften aus den variablen Fonts der Referenz.
- Sichtmodell portiert (LuneHudModel, LuneLaneThreat, LuneStats) mit den
  Privatsphäre-Regeln der Referenz: Gold, Entwurf, Rückgängig und neue
  Buchungen nur für den eigenen Sitz; Lanes, Gebäude, Boss und Wellen aus
  öffentlichem Zustand.
- Menschlicher Sitz in LuneMatchRunner/LuneMatchDriver: Bots planen die
  übrigen fünf Sitze, die Planung wartet auf Bereit; Tempo 1x/2x/4x und Pause
  nur im Kampf. Bot-Partien unverändert.
- Theme-Grundlagen: Farben und Zahlenformate, Platten (forged_style.gd),
  Vektorsymbole (lune_glyphs.gd) mit Painter2D, statische SDF-Atlanten aus
  den benannten Instanzen von Cantarell und Cinzel (OFL).
- Tests: EditMode 23/23 (12 neue: gleiches öffentliches Bild für alle Sitze,
  fremde Entwürfe privat, private Boss-Lane-Wahl, Lane-Bedrohung, Bosszustände,
  nächste Welle, Bereit-Sperre, Formate, menschlicher Sitz); PlayMode 3/3,
  Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Dokumentation: U3-UI (Bewertung, U3A), U5-VISUAL (Vorgaben des
  Projektinhabers: stilisierter Realismus für die ganze Welt, erst in U5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:57:45 +02:00

181 lines
8.1 KiB
C#
Raw 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.Collections.Generic;
using Luneraria.Core;
using UnityEngine;
namespace Luneraria.UI
{
/// <summary>Tooltip content (Godot: the dictionary with title, subtitle, stats, text, note, warn).</summary>
public sealed class LuneTip
{
public string Title = "";
public string Subtitle = "";
public string Glyph = "";
public Color? GlyphColor;
/// <summary>Rows [glyph, name, value].</summary>
public readonly List<string[]> Stats = new List<string[]>();
public string Text = "";
public string Note = "";
public string Warn = "";
public bool IsEmpty { get { return Title.Length == 0 && Subtitle.Length == 0 && Stats.Count == 0 && Text.Length == 0 && Note.Length == 0 && Warn.Length == 0; } }
public LuneTip Stat(string glyph, string name, string value)
{
Stats.Add(new[] { glyph, name, value });
return this;
}
/// <summary>Everything the tooltip shows, as one string (tests and change detection).</summary>
public override string ToString()
{
var parts = new List<string> { Title, Subtitle };
foreach (string[] row in Stats)
parts.Add(row[1] + ": " + row[2]);
parts.Add(Text);
parts.Add(Note);
parts.Add(Warn);
return string.Join("\n", parts.FindAll(p => p.Length > 0));
}
}
/// <summary>
/// Display figures from the real unit definitions and combat rules (port of
/// scripts/luneraria/ui/lune_stats.gd): damage per hit, one attack per second times the attack
/// tempo bonus, range, armour and damage type with the damage matrix (LuneUnit.DamageMatrix).
/// Nothing is invented; values are only formatted.
/// </summary>
public static class LuneStats
{
static readonly Dictionary<string, string> RoleNames = new Dictionary<string, string>
{ { "front", "Tank" }, { "melee", "Nahkampf" }, { "ranged", "Fernkampf" }, { "splash", "Flächenschaden" }, { "support", "Support" } };
static readonly Dictionary<string, string> RoleGlyphs = new Dictionary<string, string>
{ { "front", "tank" }, { "melee", "melee" }, { "ranged", "ranged" }, { "splash", "splash" }, { "support", "support" } };
public static string RoleName(string role)
{
return RoleNames.TryGetValue(role ?? "", out string name) ? name : role;
}
public static string RoleGlyph(string role)
{
return RoleGlyphs.TryGetValue(role ?? "", out string glyph) ? glyph : "melee";
}
static int Find(int attackType, double factor)
{
int row = GdMath.Clamp(attackType, 0, 2);
for (int armor = 0; armor < 3; armor++)
{
if (LuneUnit.DamageMatrix[row, armor] == factor)
return armor;
}
return -1;
}
/// <summary>Armour class the damage type hits for 130 % (DamageMatrix).</summary>
public static string StrongAgainst(int attackType)
{
return ContentData.ArmorNames[Find(attackType, 1.30)];
}
/// <summary>Armour class the damage type hits for 75 % (DamageMatrix).</summary>
public static string WeakAgainst(int attackType)
{
return ContentData.ArmorNames[Find(attackType, 0.75)];
}
public static double HitDamage(UnitDef definition)
{
return definition.Damage * (1.0 + definition.DamageBonus);
}
public static double AttacksPerSecond(UnitDef definition)
{
return 1.0 + GdMath.Clamp(definition.AttackSpeedBonus, -.25, .5);
}
public static string ArmorName(int armorType)
{
return ContentData.ArmorNames[GdMath.Clamp(armorType, 0, 2)];
}
public static string DamageName(int attackType)
{
return ContentData.DamageNames[GdMath.Clamp(attackType, 0, 2)];
}
/// <summary>Stat rows [glyph, name, value] for tooltips and detail panels.</summary>
public static List<string[]> Rows(UnitDef definition, bool withMove = false)
{
int attackType = definition.AttackType;
double reduction = definition.ReductionBonus;
var result = new List<string[]>
{
new[] { "heart", "Leben", LuneUiTheme.Number(definition.MaxHp) },
new[] { "sword", "Schaden/Treffer", LuneUiTheme.Decimal(HitDamage(definition)) },
new[] { "tempo", "Angriffstempo", LuneUiTheme.Decimal(AttacksPerSecond(definition), 2) + " /s" },
new[] { "range", "Reichweite", LuneUiTheme.Decimal(definition.AttackRange) + " m" },
new[] { "armor", "Rüstung", ArmorName(definition.ArmorType) + (reduction > 0.0 ? " · −" + LuneUiTheme.Round(reduction * 100.0) + " % Schaden" : "") },
new[] { "splash", "Angriffsart", DamageName(attackType) + " · stark gegen " + StrongAgainst(attackType) },
};
if (withMove)
result.Add(new[] { "move", "Lauftempo", LuneUiTheme.Decimal(definition.Speed) + " m/s" });
if (definition.Regen > 0.0)
result.Add(new[] { "heart", "Regeneration", LuneUiTheme.Decimal(definition.Regen * 100.0) + " % Leben/s" });
if (definition.ItemSplash > 0.0)
result.Add(new[] { "splash", "Splitter", LuneUiTheme.Round(definition.ItemSplash * 100.0) + " % an bis zu 2 Gegner" });
return result;
}
/// <summary>Compact pairs [glyph, "Name Wert"] for narrow panels (two per row).</summary>
public static List<string[]> ShortRows(UnitDef definition)
{
int attackType = GdMath.Clamp(definition.AttackType, 0, 2);
return new List<string[]>
{
new[] { "heart", "Leben " + LuneUiTheme.Number(definition.MaxHp) },
new[] { "sword", "Schaden " + LuneUiTheme.Decimal(HitDamage(definition)) },
new[] { "tempo", "Tempo " + LuneUiTheme.Decimal(AttacksPerSecond(definition), 2) + "/s" },
new[] { "range", "Reichweite " + LuneUiTheme.Decimal(definition.AttackRange) + " m" },
new[] { "armor", "Rüstung " + ArmorName(definition.ArmorType) },
new[] { "splash", DamageName(attackType) + " › " + StrongAgainst(attackType) },
};
}
/// <summary>"Schaden pro Sekunde" from hit damage and tempo, without abilities or type modifiers.</summary>
public static double Dps(UnitDef definition)
{
return HitDamage(definition) * AttacksPerSecond(definition);
}
/// <summary>Stage of a troop id: even numbers are the upgraded stage (g02, w04 …).</summary>
public static int StageOf(string typeId)
{
int number = int.Parse(typeId.Substring(1));
return number % 2 == 0 ? 2 : 1;
}
/// <summary>Tooltip content for a troop type (shop cards, army chips).</summary>
public static LuneTip TroopTip(string typeId, string role, string extra = "")
{
UnitDef data = DataCatalog.GetUnit(typeId);
string upgrade = data.UpgradeId ?? "";
string note;
if (upgrade.Length > 0)
{
UnitDef next = DataCatalog.GetUnit(upgrade);
note = string.Format("Aufwertung: {0} für {1} Gold (Leben {2} → {3}, Schaden {4} → {5})", next.DisplayName, next.Cost,
LuneUiTheme.Number(data.MaxHp), LuneUiTheme.Number(next.MaxHp), LuneUiTheme.Decimal(data.Damage), LuneUiTheme.Decimal(next.Damage));
}
else
{
note = "Höchste Stufe.";
}
var tip = new LuneTip { Title = data.DisplayName, Glyph = RoleGlyph(role),
Subtitle = string.Format("{0} · Stufe {1} · {2} Gold", RoleName(role), StageOf(typeId) == 2 ? "II" : "I", data.Cost),
Text = data.AbilityDescription ?? "", Note = note, Warn = extra ?? "" };
tip.Stats.AddRange(Rows(data));
return tip;
}
}
}