saumwacht-godot/tools/luneraria/build_asset_matrix.py
Martin Stickelbroeck f53859a298 M2: Helden, Items, Talente, Angriffschampions und Bossgrotte
Drei Helden mit Nähe-/Teilnahme-XP, Talenten und drei Itemplätzen,
drei Angriffschampions mit Buchung n -> n+1, Bossgrotte mit Streitfall,
ruhendem Boss, idempotenter Eroberung, einmaliger Entsendung für das
Siegerteam, hohem Kopfgeld und Sperre bis Sieg+4. HUD-Reiter, Porträt
aus echtem Modell, Grotten-Asset, Bots für alle Systeme. Regeln M2 73/73,
M1 91/91, App M2 15/15 und M1 38/38, Bossszenarien dokumentiert.

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

98 lines
8.4 KiB
Python
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.

#!/usr/bin/env python3
"""Asset reuse matrix for Champions of Luneraria, generated from the runtime manifests.
Run: python3 tools/luneraria/build_asset_matrix.py -> docs/luneraria/ASSETMATRIX.md
Roles marked "vorläufig" are provisional assignments of existing models, not new peoples.
"""
from pathlib import Path
import json
P = Path(__file__).resolve().parents[2]
phase4 = {a['id']: a for a in json.loads((P / 'assets/phase4_manifest.json').read_text())['assets']}
skeletal = {}
for name in ['phase11', 'phase12', 'phase15', 'phase16', 'phase18', 'phase19']:
for a in json.loads((P / f'assets/{name}_manifest.json').read_text())['assets']:
skeletal[a['id']] = (a, name)
# id -> (bisherige Rolle, neue Rolle, Volk/Bund-Zuordnung, Eingriff)
ROLES = {
'g01': ('Glutpakt Nahkampf', 'Truppe Nahkampf', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g02': ('Glutpakt Nahkampf+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g03': ('Glutpakt Tank', 'Truppe Front', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g04': ('Glutpakt Tank+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g05': ('Glutpakt Fernkampf', 'Truppe Fernkampf; zusätzlich Held „Funkenlanze“ (vorläufig; Goldring, Namensschild, ×1,25)', 'Glutkonstrukt / Glutpakt', 'umgesetzt (M2)'),
'g06': ('Glutpakt Fernkampf+', 'Truppenaufwertung (Belagerung)', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g07': ('Glutpakt Fläche', 'Truppe Fläche', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g08': ('Glutpakt Fläche+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g09': ('Glutpakt Unterstützung', 'Truppe Unterstützung', 'Glutkonstrukt / Glutpakt', 'keiner'),
'g10': ('Glutpakt Unterstützung+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'),
'w01': ('Wurzelbund Nahkampf', 'Truppe Nahkampf', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w02': ('Wurzelbund Nahkampf+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w03': ('Wurzelbund Tank', 'Truppe Front', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w04': ('Wurzelbund Tank+', 'Truppenaufwertung; zusätzlich Held „Ringborke“ (vorläufig)', 'Wurzelwesen / Wurzelbund', 'umgesetzt (M2)'),
'w05': ('Wurzelbund Fernkampf', 'Truppe Fernkampf', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w06': ('Wurzelbund Fernkampf+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w07': ('Wurzelbund Fläche', 'Truppe Fläche', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w08': ('Wurzelbund Fläche+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w09': ('Wurzelbund Heiler', 'Truppe Unterstützung', 'Wurzelwesen / Wurzelbund', 'keiner'),
'w10': ('Wurzelbund Heiler+', 'Truppenaufwertung; zusätzlich Held „Quellgeflecht“ (vorläufig)', 'Wurzelwesen / Wurzelbund', 'umgesetzt (M2)'),
'e01': ('Wellengegner', 'Monsterwelle (teamgebunden, rotierend)', 'Riftbrut (neutral, pro Team eingefärbt)', 'Teamkennzeichnung'),
'e02': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'),
'e03': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'),
'e04': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'),
'e05': ('Wellengegner', 'Monsterwelle (späte Variante)', 'Riftbrut', 'Teamkennzeichnung'),
'e06': ('Wellengegner', 'Monsterwelle (Unterstützer)', 'Riftbrut', 'Teamkennzeichnung'),
'e07': ('Boss Welle 5', 'Reserve (Wellen-Elite ab höheren Indizes)', 'Riftbrut', 'offen'),
'e08': ('Boss Welle 8', 'Reserve (Wellen-Elite)', 'Riftbrut', 'offen'),
'e09': ('Boss Welle 15', 'Reserve', 'Riftbrut', 'offen'),
'e10': ('Boss Welle 20', 'neutraler Grottenboss und entsandter Boss (×1,7)', 'Riftbrut / Grotte', 'umgesetzt (M2)'),
's01': ('Söldner', 'Reserve Angriffschampion', 'Söldner', 'offen'),
's02': ('Söldner', 'Angriffschampion Schutz (Rostschieber)', 'Söldner', 'umgesetzt (M2)'),
's03': ('Söldner', 'Angriffschampion Druck auf Unterstützer (Dunstsänger)', 'Söldner', 'umgesetzt (M2)'),
's04': ('Söldner', 'Angriffschampion Belagerung (Keilbrecher)', 'Söldner', 'umgesetzt (M2)'),
's05': ('Söldner', 'Reserve Angriffschampion', 'Söldner', 'offen'),
'core_team0': ('Saumkern Team 0', 'Ancient Blau (SW)', '—', 'keiner'),
'core_team1': ('Saumkern Team 1', 'Ancient Rot (NO)', '—', 'keiner'),
'worker': ('Arbeiter/Funken', 'nicht im neuen Modus (Arbeitsannahme E); Modell bleibt erhalten', '—', 'keiner'),
'map_lane_segment': ('Lane-Kachel alt', 'nicht verwendet; Lanes neu erzeugt', '—', 'keiner'),
}
for prefix, people in (('m', 'Nietenkonvent (nicht spielbar)'), ('n', 'Nebelchor (nicht spielbar)')):
for i in range(1, 11):
ROLES[f'{prefix}{i:02d}'] = ('nicht spielbar, niedrige Detailstufe', 'Reserve für spätere Völker', people, 'Neuaufbau nötig vor Nutzung')
rows = []
for asset_id in sorted(set(phase4) | set(skeletal)):
base = phase4.get(asset_id, {})
path = base.get('resource_path', '')
source = str(base.get('blend_path', '')).replace(str(P) + '/', '')
tris = base.get('triangles', '')
rig = 'starre Gelenkgruppen'
material = 'HD, starre Teile' if base.get('detail_level') == 'high' else 'niedrige Detailstufe'
if asset_id in skeletal:
record, manifest = skeletal[asset_id]
path = record['resource_path']
source = record.get('source', source)
tris = record['triangles']
rig = '%d Knochen · %d Clips' % (record['bones'], len(record['clips']))
material = 'gewichtetes Skelett (%s)' % manifest
old, new, people, work = ROLES.get(asset_id, ('?', 'offen', '?', 'Prüfung nötig'))
rows.append((asset_id, base.get('name') or base.get('display_name') or skeletal.get(asset_id, ({},))[0].get('name', ''), path, source, rig, tris, old, new, people, material, work))
extra = [
('menu-stage', 'Menü-Diorama', 'res://assets/environment/menu-stage.glb', 'assets-source/phase20/build_menu_stage.py', 'statisch', '48660', 'Hauptmenü Phase 20A', 'Hauptmenü (Titel angepasst)', '—', 'Kluft-Shader', 'keiner'),
('verdant-rift', 'Grüne Kluft (2-Bretter-Arena)', 'res://assets/environment/verdant-rift.glb', 'assets-source/phase10/verdant-rift.blend', 'statisch', '—', 'Spielfeld Saumwacht', 'Legacy-Modus; Materialien/Shader für die neue Karte wiederverwendet', '—', 'triplanare Kluft-Shader', 'keine neue Nutzung als Karte'),
('compare-rock / compare-tree', 'Fels, Baum', 'res://assets/compare/', 'assets-source/phase14/build_compare_props.py', 'statisch', '360 / 852', 'Vergleichsszene', 'Kartendekoration Dschungel', '—', 'einfach', 'keiner'),
('lane_tower', 'Laneturm', 'res://assets/luneraria/tower.glb', 'assets-source/luneraria/build_tower.py', 'statisch', '648', '—', '16 Lanetürme und Basistürme, Kristall zur Laufzeit in Teamfarbe', '—', 'ENV_*-Kluftshader, Kristall emissiv', 'neu (M1)'),
('boss_grotto', 'Bossgrotte', 'res://assets/luneraria/grotto.glb', 'assets-source/luneraria/build_grotto.py', 'statisch', '1888', '—', 'Felsinsel mit Höhle auf der Symmetrieachse, zwei gespiegelte Zugänge', '—', 'ENV_*-Kluftshader, violette Kristalle', 'neu (M2)'),
('luneraria-emblem', 'Emblem Luneraria', 'res://assets/ui/emblems/luneraria.svg', 'SVG', '—', '—', '—', 'Titel und Menü', '—', 'Vektor', 'neu (M1)'),
('Emblem/Theme', 'Saumwacht-Theme', 'scripts/ui/theme/, assets/ui/', 'Code / SVG', '—', '—', 'Menü 20A', 'Theme weiterverwendet, Titel/Emblem Luneraria', '—', 'Vektor', 'Titelschrift/Emblem ergänzen'),
]
lines = ['# Asset-Wiederverwendungsmatrix', '',
'Erzeugt mit `python3 tools/luneraria/build_asset_matrix.py` aus den Laufzeitmanifesten. „Volk/Bund“ ist die Datenzuordnung. Die sieben Zielvölker (Menschen, Elfen, Zwerge, Trolle, Feen, Orks, Biestmenschen) haben noch **keine eigenen Modelle**; bestehende Wesen bleiben als eigene Bünde Teil der Welt. Als „vorläufig“ markierte Rollen sind Zuordnungen vorhandener Modelle, keine neu erstellten Völker.', '',
'| ID | Name | Pfad | Quelle | Rig / Clips | Dreiecke | bisherige Rolle | neue Rolle | Volk / Bund | Materialzustand | Eingriff |',
'| --- | --- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |']
for row in rows + extra:
lines.append('| ' + ' | '.join(str(x) for x in row) + ' |')
(P / 'docs/luneraria').mkdir(parents=True, exist_ok=True)
(P / 'docs/luneraria/ASSETMATRIX.md').write_text('\n'.join(lines) + '\n')
print('ASSET_MATRIX', len(rows) + len(extra), 'rows')