saumwacht-godot/scripts/data/data_catalog.gd
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:00:36 +02:00

184 lines
18 KiB
GDScript
Raw Permalink 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.

extends Node
## Immutable design data. Callers receive fresh combat dictionaries.
const SHORT_WAVE_COUNT: int = 8
const EXPEDITION_WAVE_COUNT: int = 20
const BASE_IDS: Array[String] = ["g01", "g03", "g05", "g07", "g09"]
const DAMAGE_NAMES: Array[String] = ["Klinge", "Durchschlag", "Impuls"]
const ARMOR_NAMES: Array[String] = ["Leicht", "Schwer", "Ätherisch"]
const UNITS: Dictionary = {
"g01": {"id": "g01", "display_name": "Kohlenbeißer", "cost": 45, "upgrade_id": "g02", "max_hp": 260.0, "damage": 17.0, "attack_type": 0, "armor_type": 0, "attack_range": 1.1, "speed": 2.4, "ability": "g01", "leak_value": 0, "bounty": 0, "color": Color("d76e36"), "ability_description": "Jeder 3. Treffer: Brand, 4 Schaden/s für 3 s."},
"g02": {"id": "g02", "display_name": "Schlackenklaue", "cost": 90, "upgrade_id": "", "max_hp": 690.0, "damage": 44.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.2, "speed": 2.4, "ability": "g02", "leak_value": 0, "bounty": 0, "color": Color("ef9849"), "ability_description": "Jeder 3. Treffer: Brand, 12 Schaden/s für 3 s."},
"g03": {"id": "g03", "display_name": "Schlackenwacht", "cost": 70, "upgrade_id": "g04", "max_hp": 650.0, "damage": 11.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.2, "speed": 2.4, "ability": "g03", "leak_value": 0, "bounty": 0, "color": Color("8c5544"), "ability_description": "Erste 8 s: 15 % weniger erlittener Schaden."},
"g04": {"id": "g04", "display_name": "Basaltbastion", "cost": 150, "upgrade_id": "", "max_hp": 1650.0, "damage": 27.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.4, "speed": 2.4, "ability": "g04", "leak_value": 0, "bounty": 0, "color": Color("ba6c42"), "ability_description": "Erste 10 s: 25 % weniger Schaden, danach 10 %."},
"g05": {"id": "g05", "display_name": "Funkenlanze", "cost": 65, "upgrade_id": "g06", "max_hp": 240.0, "damage": 24.0, "attack_type": 1, "armor_type": 0, "attack_range": 5.5, "speed": 2.0, "ability": "g05", "leak_value": 0, "bounty": 0, "color": Color("edb354"), "ability_description": "Jeder 4. Treffer: 50 % zusätzlicher Grundschaden."},
"g06": {"id": "g06", "display_name": "Glasballiste", "cost": 140, "upgrade_id": "", "max_hp": 640.0, "damage": 65.0, "attack_type": 1, "armor_type": 0, "attack_range": 6.5, "speed": 2.0, "ability": "g06", "leak_value": 0, "bounty": 0, "color": Color("ffd579"), "ability_description": "Jeder 4. Treffer: 75 % zusätzlicher Grundschaden."},
"g07": {"id": "g07", "display_name": "Aschenwerfer", "cost": 95, "upgrade_id": "g08", "max_hp": 310.0, "damage": 23.0, "attack_type": 2, "armor_type": 0, "attack_range": 4.5, "speed": 2.0, "ability": "g07", "leak_value": 0, "bounty": 0, "color": Color("cb7867"), "ability_description": "Jeder Treffer: 30 % Flächenschaden an bis zu 3 weiteren Zielen in 1,5 m."},
"g08": {"id": "g08", "display_name": "Kraterrufer", "cost": 190, "upgrade_id": "", "max_hp": 820.0, "damage": 58.0, "attack_type": 2, "armor_type": 1, "attack_range": 5.0, "speed": 2.0, "ability": "g08", "leak_value": 0, "bounty": 0, "color": Color("f6956e"), "ability_description": "Jeder Treffer: 40 % Flächenschaden an bis zu 4 weiteren Zielen in 2 m."},
"g09": {"id": "g09", "display_name": "Herdhüter", "cost": 85, "upgrade_id": "g10", "max_hp": 420.0, "damage": 12.0, "attack_type": 2, "armor_type": 2, "attack_range": 3.5, "speed": 2.0, "ability": "g09", "leak_value": 0, "bounty": 0, "color": Color("e9ae80"), "ability_description": "Alle 6 s: bis zu 3 Verbündete in 3 m erhalten für 4 s +10 % Grundangriffsschaden."},
"g10": {"id": "g10", "display_name": "Sonnenesse", "cost": 170, "upgrade_id": "", "max_hp": 1080.0, "damage": 30.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.0, "speed": 2.0, "ability": "g10", "leak_value": 0, "bounty": 0, "color": Color("ffe0a4"), "ability_description": "Alle 6 s: bis zu 4 Verbündete in 4 m erhalten für 4 s +15 % Grundangriffsschaden und 45 Heilung."},
"w01": {"id": "w01", "display_name": "Dornhopser", "cost": 50, "upgrade_id": "w02", "max_hp": 290.0, "damage": 18.0, "attack_type": 0, "armor_type": 0, "attack_range": 1.1, "speed": 2.4, "ability": "w01", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 3. Treffer: −20 % Bewegung für 2 s.", "color": Color("a7ba6c")},
"w02": {"id": "w02", "display_name": "Rankenschnapper", "cost": 100, "upgrade_id": "", "max_hp": 800.0, "damage": 47.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.3, "speed": 2.4, "ability": "w02", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 3. Treffer: −30 % Bewegung und −10 % Angriffstempo für 3 s.", "color": Color("a7ba6c")},
"w03": {"id": "w03", "display_name": "Korkrücken", "cost": 75, "upgrade_id": "w04", "max_hp": 620.0, "damage": 12.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.2, "speed": 2.4, "ability": "w03", "leak_value": 0, "bounty": 0, "ability_description": "Heilt sich alle 5 s um 35 HP, sobald er verwundet ist.", "color": Color("a7ba6c")},
"w04": {"id": "w04", "display_name": "Ringborke", "cost": 155, "upgrade_id": "", "max_hp": 1560.0, "damage": 31.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.4, "speed": 2.4, "ability": "w04", "leak_value": 0, "bounty": 0, "ability_description": "Heilt sich alle 5 s um 100 HP, sobald er verwundet ist.", "color": Color("a7ba6c")},
"w05": {"id": "w05", "display_name": "Samenschleuder", "cost": 60, "upgrade_id": "w06", "max_hp": 230.0, "damage": 23.0, "attack_type": 1, "armor_type": 0, "attack_range": 5.5, "speed": 2.0, "ability": "w05", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 4. Treffer: Keimbrand, 3 Schaden/s für 4 s.", "color": Color("a7ba6c")},
"w06": {"id": "w06", "display_name": "Zapfenkrone", "cost": 135, "upgrade_id": "", "max_hp": 630.0, "damage": 61.0, "attack_type": 1, "armor_type": 0, "attack_range": 6.0, "speed": 2.0, "ability": "w06", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 4. Treffer: Keimbrand, 10 Schaden/s für 4 s.", "color": Color("a7ba6c")},
"w07": {"id": "w07", "display_name": "Sporenkelch", "cost": 100, "upgrade_id": "w08", "max_hp": 370.0, "damage": 22.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.0, "speed": 2.0, "ability": "w07", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 3. Treffer: 60 % Grundschaden an bis zu 3 weiteren Zielen in 2 m.", "color": Color("a7ba6c")},
"w08": {"id": "w08", "display_name": "Pilzkathedrale", "cost": 195, "upgrade_id": "", "max_hp": 940.0, "damage": 59.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.5, "speed": 2.0, "ability": "w08", "leak_value": 0, "bounty": 0, "ability_description": "Jeder 3. Treffer: 80 % Grundschaden an bis zu 4 weiteren Zielen in 2,5 m.", "color": Color("a7ba6c")},
"w09": {"id": "w09", "display_name": "Tauhüter", "cost": 90, "upgrade_id": "w10", "max_hp": 390.0, "damage": 10.0, "attack_type": 2, "armor_type": 0, "attack_range": 4.0, "speed": 2.0, "ability": "w09", "leak_value": 0, "bounty": 0, "ability_description": "Alle 4 s: heilt den am stärksten verwundeten Verbündeten in 4 m um 65 HP.", "color": Color("a7ba6c")},
"w10": {"id": "w10", "display_name": "Quellgeflecht", "cost": 180, "upgrade_id": "", "max_hp": 1000.0, "damage": 27.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.5, "speed": 2.0, "ability": "w10", "leak_value": 0, "bounty": 0, "ability_description": "Alle 4 s: heilt bis zu 3 verwundete Verbündete in 4,5 m um je 80 HP.", "color": Color("a7ba6c")},
}
const ENEMIES: Dictionary = {
"e01": {"id": "e01", "display_name": "Splitterling", "max_hp": 90.0, "damage": 6.0, "attack_type": 0, "armor_type": 0, "attack_range": 1.0, "speed": 3.0, "ability": "e01", "leak_value": 25, "bounty": 3},
"e02": {"id": "e02", "display_name": "Schalenläufer", "max_hp": 240.0, "damage": 9.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.2, "speed": 2.0, "ability": "e02", "leak_value": 55, "bounty": 6},
"e03": {"id": "e03", "display_name": "Nebelrest", "max_hp": 150.0, "damage": 11.0, "attack_type": 2, "armor_type": 2, "attack_range": 3.5, "speed": 2.2, "ability": "e03", "leak_value": 40, "bounty": 5},
"e04": {"id": "e04", "display_name": "Fadenschütze", "max_hp": 130.0, "damage": 12.0, "attack_type": 1, "armor_type": 0, "attack_range": 6.0, "speed": 2.4, "ability": "e04", "leak_value": 35, "bounty": 5},
"e05": {"id": "e05", "display_name": "Bruchstampfer", "max_hp": 480.0, "damage": 16.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.4, "speed": 1.6, "ability": "e05", "leak_value": 90, "bounty": 10},
"e06": {"id": "e06", "display_name": "Knotenrufer", "max_hp": 190.0, "damage": 7.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.0, "speed": 2.0, "ability": "e06", "leak_value": 45, "bounty": 6},
"e07": {"id": "e07", "display_name": "Bruchhirsch", "max_hp": 1100.0, "damage": 28.0, "attack_type": 0, "armor_type": 0, "attack_range": 1.4, "speed": 1.8, "ability": "e07", "leak_value": 250, "bounty": 30, "is_boss": true},
"e08": {"id": "e08", "display_name": "Riegelkoloss", "max_hp": 1400.0, "damage": 30.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.5, "speed": 1.5, "ability": "e08", "leak_value": 330, "bounty": 40, "is_boss": true},
"e09": {"id": "e09", "display_name": "Maskenrichter", "max_hp": 1800.0, "damage": 34.0, "attack_type": 2, "armor_type": 2, "attack_range": 5.0, "speed": 1.8, "ability": "e09", "leak_value": 400, "bounty": 50, "is_boss": true},
"e10": {"id": "e10", "display_name": "Schlundkrone", "max_hp": 3000.0, "damage": 50.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.5, "speed": 1.3, "ability": "e10", "leak_value": 600, "bounty": 70, "is_boss": true},
}
const SENDS: Dictionary = {
"s01": {"id": "s01", "display_name": "Nadelspäher", "cost": 20, "income_gain": 3, "max_hp": 100.0, "damage": 8.0, "attack_type": 1, "armor_type": 0, "attack_range": 4.5, "speed": 2.8, "ability": "s01", "leak_value": 30, "bounty": 3},
"s02": {"id": "s02", "display_name": "Rostschieber", "cost": 40, "income_gain": 5, "max_hp": 240.0, "damage": 8.0, "attack_type": 0, "armor_type": 1, "attack_range": 1.2, "speed": 1.8, "ability": "s02", "leak_value": 55, "bounty": 6},
"s03": {"id": "s03", "display_name": "Dunstsänger", "cost": 80, "income_gain": 9, "max_hp": 320.0, "damage": 10.0, "attack_type": 2, "armor_type": 2, "attack_range": 5.0, "speed": 2.0, "ability": "s03", "leak_value": 70, "bounty": 9},
"s04": {"id": "s04", "display_name": "Keilbrecher", "cost": 130, "income_gain": 12, "max_hp": 650.0, "damage": 20.0, "attack_type": 1, "armor_type": 1, "attack_range": 1.3, "speed": 1.6, "ability": "s04", "leak_value": 120, "bounty": 14},
"s05": {"id": "s05", "display_name": "Risszehrer", "cost": 200, "income_gain": 17, "max_hp": 1000.0, "damage": 30.0, "attack_type": 2, "armor_type": 2, "attack_range": 4.0, "speed": 1.7, "ability": "s05", "leak_value": 180, "bounty": 20},
}
# Glutpakt vertical slice: a readable eight-wave progression with two bosses.
const WAVES: Array[Dictionary] = [
{"e01": 8}, {"e01": 10}, {"e01": 6, "e02": 4},
{"e01": 8, "e04": 4}, {"e07": 1, "e01": 6},
{"e02": 6, "e01": 6}, {"e03": 8, "e01": 4},
{"e08": 1, "e02": 2, "e04": 3},
]
const WAVE_TITLES: Array[String] = [
"Erste Risse", "Splitterstrom", "Harte Schalen", "Fäden im Rücken",
"Der Bruchhirsch", "Gepanzerter Vorstoß", "Stimmen aus dem Nebel", "Der letzte Riegel"
]
const WAVE_HINTS: Array[String] = [
"Stelle Schlackenwacht und Kohlenbeißer nach vorn, die Funkenlanze dahinter.",
"Ergänze deine Verteidigung. Arbeiter produzieren erst ab der nächsten Bauphase.",
"Durchschlag wirkt gegen schwere Schalen. Söldner sind jetzt verfügbar.",
"Schützen stehen hinten. Flächenschaden hilft gegen die Splitterlinge davor.",
"BOSS · Der Bruchhirsch trifft bei jedem vierten Angriff mehrere Ziele. Werte deine Front auf.",
"Schwere Rüstung: Funkenlanze und Glasballiste verursachen 30 % mehr Schaden.",
"Ätherische Gegner: Aschenwerfer und Herdhüter nutzen Impulsschaden. Halte die Gruppe zusammen.",
"FINALE · Riegelkoloss mit Startschutz. Starke Front, Durchschlag und alle übrigen Funken einsetzen."
]
# Full expedition from the design dossier; the proven short match stays separate.
const EXPEDITION_WAVES: Array[Dictionary] = [
{"e01":8}, {"e01":10}, {"e01":6,"e02":4}, {"e01":8,"e04":4},
{"e07":1,"e01":6}, {"e02":6,"e01":6}, {"e03":8,"e01":4},
{"e02":6,"e04":6,"e01":4}, {"e05":4,"e02":6,"e06":4},
{"e08":1,"e03":8,"e04":4}, {"e01":16,"e04":6},
{"e02":8,"e05":4,"e06":4}, {"e03":12,"e04":6},
{"e05":6,"e02":6,"e01":6}, {"e09":1,"e03":6,"e06":4},
{"e01":18,"e04":6,"e03":4}, {"e05":8,"e02":6,"e06":4},
{"e03":10,"e04":8,"e06":4}, {"e05":6,"e02":8,"e01":8,"e04":4},
{"e10":1,"e05":6,"e03":6,"e06":4},
]
const EXPEDITION_TITLES: Array[String] = [
"Erste Risse", "Splitterstrom", "Harte Schalen", "Fäden im Rücken", "Der Bruchhirsch",
"Gepanzerter Vorstoß", "Stimmen aus dem Nebel", "Gedeckter Vormarsch", "Die Knotenrufer",
"Der Riegelkoloss", "Splitterflut", "Heilende Front", "Nebel und Fäden", "Bruchlinie",
"Der Maskenrichter", "Der große Schwarm", "Stampfende Reihen", "Rissgesang", "Vor dem Schlund", "Die Schlundkrone"
]
const EXPEDITION_HINTS: Array[String] = [
"", "", "", "", "", "", "",
"Eine breite Panzerfront schützt sechs Schützen. Kombiniere Durchschlag mit Flächenschaden.",
"NEU · Bruchstampfer treffen mehrere Ziele. Knotenrufer heilen verwundete Angreifer; konzentriere deinen Schaden.",
"BOSS · Riegelkoloss mit Startschild und ätherischer Begleitung. Durchschlag und Impuls ergänzen sich.",
"Ein großer Schwarm. Kraterrufer treffen mehrere Gegner; schütze deine Artillerie.",
"Schwere Front mit Heilern. Aufgewertete Glasballisten helfen gegen hohe Einzelziel-HP.",
"Nebelreste tragen ätherische Rüstung. Impulsschaden ist hier besonders wirksam.",
"Breite Nahkampfwelle: mehrere Frontkämpfer verteilen den Druck.",
"BOSS · Der Maskenrichter verlangsamt alle sechs Sekunden bis zu drei Wächter. Halte Schaden und Unterstützung bereit.",
"28 Angreifer: Flächenschaden und eine belastbare Front sind entscheidend.",
"Acht Bruchstampfer und vier Heiler. Werte deine Front und den Durchschlagschaden auf.",
"Ätherische Gegner und Fernkämpfer greifen gemeinsam an. Prüfe deine Schadensmischung.",
"Letzte Auszahlung vor dem Finale. Investiere Gold in die Formation und plane deine letzten Sends.",
"FINALE · Die Schlundkrone trifft bei jedem vierten Angriff mehrere Wächter. Nutze Gold und übrige Funken."
]
func wave_count() -> int:
return EXPEDITION_WAVE_COUNT if GameState.match_mode == GameState.MODE_EXPEDITION else SHORT_WAVE_COUNT
func get_unit(id: String) -> Dictionary:
return UNITS.get(id, {}).duplicate(true)
func get_wave(wave: int) -> Dictionary:
if wave < 1 or wave > wave_count():
return {}
var x: float = float(wave - 1)
var full: bool = GameState.match_mode == GameState.MODE_EXPEDITION
var groups: Dictionary = (EXPEDITION_WAVES if full else WAVES)[wave - 1].duplicate(true)
var count: int = 0
var names: PackedStringArray = []
for id: String in groups:
count += int(groups[id])
names.append("%d × %s" % [groups[id], ENEMIES[id]["display_name"]])
return {"number": wave, "groups": groups, "count": count,
"title": EXPEDITION_TITLES[wave - 1] if full else WAVE_TITLES[wave - 1],
"hint": EXPEDITION_HINTS[wave - 1] if full and wave > 7 else WAVE_HINTS[wave - 1],
"hp_factor": 1.0 + 0.10*x + 0.004*x*x if full else 1.0 + 0.14*x + 0.010*x*x,
"damage_factor": 1.0 + 0.06*x + 0.002*x*x if full else 1.0 + 0.08*x + 0.004*x*x,
"bounty_factor": 1.0 + 0.04 * x, "description": ", ".join(names)}
func get_attackers(wave: int) -> Array:
var result: Array = []
var info: Dictionary = get_wave(wave)
if info.is_empty():
return result
for id: String in info["groups"]:
for _index: int in range(int(info["groups"][id])):
result.append(_scaled(ENEMIES[id], info, true))
return result
func get_send(id: String, wave: int) -> Dictionary:
var info: Dictionary = get_wave(wave)
if not SENDS.has(id) or info.is_empty():
return {}
return _scaled(SENDS[id], info, false)
func _scaled(source: Dictionary, wave: Dictionary, natural: bool) -> Dictionary:
var result: Dictionary = source.duplicate(true)
result["max_hp"] = ceil(float(source["max_hp"]) * float(wave["hp_factor"]))
result["damage"] = float(source["damage"]) * float(wave["damage_factor"])
result["bounty"] = floori(float(source["bounty"]) * float(wave["bounty_factor"]))
result["hp_factor"] = wave["hp_factor"]
result["natural"] = natural
result["is_boss"] = source.get("is_boss", false)
result["cost"] = source.get("cost", 0)
result["upgrade_id"] = ""
result["color"] = Color("c4d2cd") if natural else Color("bc87bd")
return result
const FACTIONS: Array[String] = ["glutpakt", "wurzelbund"]
const FACTION_NAMES: Dictionary = {"glutpakt": "Glutpakt", "wurzelbund": "Wurzelbund"}
const ROLES: Array[String] = ["Nahkampf", "Front / Tank", "Fernkampf", "Flächenschaden", "Unterstützung"]
func base_ids(team: int = 0) -> Array[String]:
var prefix: String = "w" if GameState.factions[team] == "wurzelbund" else "g"
return [prefix+"01", prefix+"03", prefix+"05", prefix+"07", prefix+"09"]
func faction_ids(faction: String) -> Array[String]:
var prefix: String = "w" if faction == "wurzelbund" else "g"
return [prefix+"01", prefix+"03", prefix+"05", prefix+"07", prefix+"09"]
func for_team(id: String, team: int) -> String:
return ("w" + id.substr(1)) if id.begins_with("g") and GameState.factions[team] == "wurzelbund" else id
func starter_cost(team: int = 0) -> int:
var ids := base_ids(team)
return int(UNITS[ids[0]].cost + UNITS[ids[1]].cost + UNITS[ids[2]].cost)
func wave_hint(wave: int, team: int = 0) -> String:
var hint: String = str(get_wave(wave).get("hint", ""))
if GameState.factions[team] == "wurzelbund":
for number: int in range(1,11):
hint = hint.replace(UNITS["g%02d" % number].display_name, UNITS["w%02d" % number].display_name)
return hint