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>
181 lines
8.1 KiB
C#
181 lines
8.1 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|