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