saumwacht-godot/scripts/luneraria/ui/lune_stats.gd
Martin Stickelbroeck 1b540f2a55 G3 (Zwischenstand): neues Ingame-HUD; Projekt wechselt nach Unity
Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of
Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das
HUD im Unity-Projekt (../luneraria-unity).

- HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen
  (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer
  Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse,
  Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails,
  Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben
- Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie
- Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715;
  verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern
- Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md
- HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:59:55 +02:00

82 lines
4.8 KiB
GDScript
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.

class_name LuneStats
extends RefCounted
## Display figures taken from the real unit definitions and combat rules: damage per hit,
## one attack per second times the attack tempo bonus, range, armour and damage type with
## the damage matrix (LuneUnit.DAMAGE_MATRIX). Nothing is invented; values are only formatted.
const ROLE_NAMES: Dictionary = {"front": "Tank", "melee": "Nahkampf", "ranged": "Fernkampf", "splash": "Flächenschaden", "support": "Support"}
const ROLE_GLYPHS: Dictionary = {"front": "tank", "melee": "melee", "ranged": "ranged", "splash": "splash", "support": "support"}
static func role_name(role: String) -> String:
return ROLE_NAMES.get(role, role)
static func role_glyph(role: String) -> String:
return ROLE_GLYPHS.get(role, "melee")
## Armour class the damage type hits for 130 % / 75 % (DAMAGE_MATRIX).
static func strong_against(attack_type: int) -> String:
var row: Array = LuneUnit.DAMAGE_MATRIX[clampi(attack_type, 0, 2)]
return DataCatalog.ARMOR_NAMES[row.find(1.30)]
static func weak_against(attack_type: int) -> String:
var row: Array = LuneUnit.DAMAGE_MATRIX[clampi(attack_type, 0, 2)]
return DataCatalog.ARMOR_NAMES[row.find(0.75)]
static func hit_damage(definition: Dictionary) -> float:
return float(definition.get("damage", 0.0)) * (1.0 + float(definition.get("damage_bonus", 0.0)))
static func attacks_per_second(definition: Dictionary) -> float:
return 1.0 + clampf(float(definition.get("attack_speed_bonus", 0.0)), -.25, .5)
## Stat rows [glyph, name, value] for tooltips and detail panels.
static func rows(definition: Dictionary, with_move: bool = false) -> Array:
var attack_type: int = int(definition.get("attack_type", 0))
var armor_type: int = int(definition.get("armor_type", 0))
var reduction: float = float(definition.get("reduction_bonus", 0.0))
var result: Array = [
["heart", "Leben", LuneUiTheme.number(float(definition.get("max_hp", 0.0)))],
["sword", "Schaden/Treffer", LuneUiTheme.decimal(hit_damage(definition))],
["tempo", "Angriffstempo", "%s /s" % LuneUiTheme.decimal(attacks_per_second(definition), 2)],
["range", "Reichweite", "%s m" % LuneUiTheme.decimal(float(definition.get("attack_range", 0.0)))],
["armor", "Rüstung", DataCatalog.ARMOR_NAMES[clampi(armor_type, 0, 2)] + (" · −%d %% Schaden" % int(round(reduction * 100.0)) if reduction > 0.0 else "")],
["splash", "Angriffsart", "%s · stark gegen %s" % [DataCatalog.DAMAGE_NAMES[clampi(attack_type, 0, 2)], strong_against(attack_type)]],
]
if with_move:
result.append(["move", "Lauftempo", "%s m/s" % LuneUiTheme.decimal(float(definition.get("speed", 0.0)))])
var regen: float = float(definition.get("regen", 0.0))
if regen > 0.0:
result.append(["heart", "Regeneration", "%s %% Leben/s" % LuneUiTheme.decimal(regen * 100.0)])
var splash: float = float(definition.get("item_splash", 0.0))
if splash > 0.0:
result.append(["splash", "Splitter", "%d %% an bis zu 2 Gegner" % int(round(splash * 100.0))])
return result
## Compact pairs [glyph, "Name Wert"] for narrow panels (two per row).
static func short_rows(definition: Dictionary) -> Array:
var attack_type: int = clampi(int(definition.get("attack_type", 0)), 0, 2)
return [
["heart", "Leben %s" % LuneUiTheme.number(float(definition.get("max_hp", 0.0)))],
["sword", "Schaden %s" % LuneUiTheme.decimal(hit_damage(definition))],
["tempo", "Tempo %s/s" % LuneUiTheme.decimal(attacks_per_second(definition), 2)],
["range", "Reichweite %s m" % LuneUiTheme.decimal(float(definition.get("attack_range", 0.0)))],
["armor", "Rüstung %s" % DataCatalog.ARMOR_NAMES[clampi(int(definition.get("armor_type", 0)), 0, 2)]],
["splash", "%s › %s" % [DataCatalog.DAMAGE_NAMES[attack_type], strong_against(attack_type)]],
]
## "Schaden pro Sekunde" from hit damage and tempo, without abilities or type modifiers.
static func dps(definition: Dictionary) -> float:
return hit_damage(definition) * attacks_per_second(definition)
## Tooltip content for a troop type (shop cards, army chips).
static func troop_tip(type_id: String, role: String, extra: String = "") -> Dictionary:
var data: Dictionary = DataCatalog.get_unit(type_id)
var upgrade: String = str(data.get("upgrade_id", ""))
var note: String = ""
if not upgrade.is_empty():
var next: Dictionary = DataCatalog.get_unit(upgrade)
note = "Aufwertung: %s für %d Gold (Leben %s → %s, Schaden %s → %s)" % [next.display_name, int(next.cost),
LuneUiTheme.number(data.max_hp), LuneUiTheme.number(next.max_hp), LuneUiTheme.decimal(data.damage), LuneUiTheme.decimal(next.damage)]
else:
note = "Höchste Stufe."
return {"title": str(data.display_name), "glyph": role_glyph(role), "subtitle": "%s · Stufe %s · %d Gold" % [role_name(role), "II" if int(type_id.substr(1)) % 2 == 0 else "I", int(data.cost)],
"stats": rows(data), "text": str(data.get("ability_description", "")), "note": note, "warn": extra}