216 lines
8.7 KiB
GDScript
216 lines
8.7 KiB
GDScript
extends RefCounted
|
|
class_name LibraryBrowser
|
|
## Objekt-Bibliothek des Editors (Karte t_3a998ae4): deutscher Name, Gruppen
|
|
## für den Bibliothek-Browser und Thumbnail-Laden (128-px-PNGs, einmalig per
|
|
## Headless-Render erzeugt — tools/gen_thumbs.gd).
|
|
##
|
|
## Bewusst OHNE UI: reine Daten/Funktionen, damit die Tests (tests/test_library.gd)
|
|
## headless alles prüfen können. Die IDs sind der Vertrag (Entscheidung 20) —
|
|
## Gruppen/Namen leben HIER im Editor (die objects.json bleibt 1:1 die Kopie
|
|
## der Server-Registry, damit ein Sync sie nicht verwüstet).
|
|
|
|
## Deutsche Anzeige-Namen für Bibliotheks-IDs (IDs sind der Vertrag, Entscheidung 20)
|
|
const NAMES := {
|
|
"crate_wood": "Holzkiste",
|
|
"crate_metal": "Metallkiste",
|
|
"wall_concrete": "Betonmauer",
|
|
"barrier_sandbag": "Sandsack-Sperre",
|
|
"bunker_concrete": "Bunker",
|
|
"watchtower_wood": "Wachturm",
|
|
"fence_metal": "Zaun",
|
|
"rock_large": "Großer Felsen",
|
|
"boulder_small": "Kleiner Felsen",
|
|
"tree_pine": "Nadelbaum",
|
|
"bush_small": "Strauch",
|
|
"bruecke_holz": "Brücke (Holz)",
|
|
"hoehleneingang": "Höhleneingang",
|
|
"felsbogen": "Felsbogen",
|
|
# Natur-Update (Karte t_7cf80244): kuratierte CC0-Packs (Quaternius
|
|
# MegaKit, Kyle's Rock/Mushroom) — Namen aus stage/kuration.json.
|
|
"baum_kiefer_1": "Kiefer (schlank)",
|
|
"baum_kiefer_2": "Kiefer (kräftig)",
|
|
"baum_kiefer_3": "Kiefer (mittel)",
|
|
"baum_kiefer_4": "Kiefer (hoch)",
|
|
"baum_laub_1": "Laubbaum (rund)",
|
|
"baum_laub_2": "Laubbaum (breit)",
|
|
"baum_laub_3": "Laubbaum (schlank)",
|
|
"baum_laub_4": "Laubbaum (klein)",
|
|
"baum_tot_1": "Totholz-Baum",
|
|
"fels_moos_block_gross": "Moosfelsen (groß)",
|
|
"fels_moos_block_hoch": "Moosfelsen (hoch)",
|
|
"fels_moos_platte": "Moosfelsen (Platte)",
|
|
"fels_moos_scharte": "Moosfelsen (klein)",
|
|
"fels_trocken_block": "Trockenfelsen (hoch)",
|
|
"fels_trocken_platte": "Trockenfelsen (Platte)",
|
|
"fels_trocken_klein": "Trockenfelsen (klein)",
|
|
"stein_mittel_1": "Großer Stein 1",
|
|
"stein_mittel_2": "Großer Stein 2",
|
|
"stein_kiesel_1": "Kieselstein",
|
|
"strauch_gruen": "Strauch",
|
|
"strauch_blumen": "Blühender Strauch",
|
|
"pflanze_farn": "Farn",
|
|
"pflanze_klee": "Klee-Pflanze",
|
|
"blume_gross_1": "Blume (weiß)",
|
|
"blume_gross_2": "Blume (hoch)",
|
|
"blumen_strauss": "Blumenstrauß",
|
|
"pflanze_niedrig": "Bodendecker",
|
|
"pflanze_teppich": "Moospolster",
|
|
"pflanze_teppich_gross": "Moospolster (groß)",
|
|
"gras_kurz": "Kurzes Gras",
|
|
"gras_hoch": "Hohes Gras",
|
|
"gras_bueschel": "Grasbüschel",
|
|
"gras_wispy_hoch": "Wispy-Gras (hoch)",
|
|
"pilz_fliegenpilz": "Fliegenpilz",
|
|
"pilz_pfifferling": "Pfifferling",
|
|
"pilz_steinpilz": "Steinpilz",
|
|
"pilz_shiitake": "Shiitake",
|
|
"pilz_morchel": "Morchel",
|
|
"pilz_austernseitling": "Austernseitling",
|
|
}
|
|
|
|
## Gruppen für den Bibliothek-Browser (Anzeige-Reihenfolge = Reihenfolge hier).
|
|
## Natur-Update (t_7cf80244): die alte Gruppe "natur" ist in thematische
|
|
## Gruppen aufgelöst (die 4 Alt-Objekte wanderten mit).
|
|
const GROUP_ORDER := ["kisten", "bauwerke", "bäume", "felsen", "pflanzen", "gras", "pilze", "sonstiges"]
|
|
const GROUP_LABELS := {
|
|
"kisten": "Kisten & Barrieren",
|
|
"bauwerke": "Bauwerke",
|
|
"bäume": "Bäume",
|
|
"felsen": "Felsen",
|
|
"pflanzen": "Pflanzen",
|
|
"gras": "Gras",
|
|
"pilze": "Pilze",
|
|
"sonstiges": "Sonstiges",
|
|
}
|
|
const GROUP_IDS := {
|
|
"kisten": ["crate_wood", "crate_metal", "barrier_sandbag", "wall_concrete"],
|
|
"bauwerke": ["bunker_concrete", "watchtower_wood", "fence_metal",
|
|
"bruecke_holz", "hoehleneingang", "felsbogen"],
|
|
"bäume": ["tree_pine",
|
|
"baum_kiefer_1", "baum_kiefer_2", "baum_kiefer_3", "baum_kiefer_4",
|
|
"baum_laub_1", "baum_laub_2", "baum_laub_3", "baum_laub_4", "baum_tot_1"],
|
|
"felsen": ["rock_large", "boulder_small",
|
|
"fels_moos_block_gross", "fels_moos_block_hoch", "fels_moos_platte",
|
|
"fels_moos_scharte", "fels_trocken_block", "fels_trocken_platte",
|
|
"fels_trocken_klein", "stein_mittel_1", "stein_mittel_2", "stein_kiesel_1"],
|
|
"pflanzen": ["bush_small",
|
|
"strauch_gruen", "strauch_blumen", "pflanze_farn", "pflanze_klee",
|
|
"blume_gross_1", "blume_gross_2", "blumen_strauss", "pflanze_niedrig",
|
|
"pflanze_teppich", "pflanze_teppich_gross"],
|
|
"gras": ["gras_kurz", "gras_hoch", "gras_bueschel", "gras_wispy_hoch"],
|
|
"pilze": ["pilz_fliegenpilz", "pilz_pfifferling", "pilz_steinpilz",
|
|
"pilz_shiitake", "pilz_morchel", "pilz_austernseitling"],
|
|
}
|
|
|
|
## Karten-Übersicht (t_59fc0f21): Die feste Gruppenliste oben deckt nur die
|
|
## kuratierten Alt-/Natur-Objekte ab — der 347-Objekt-Katalog bringt seine
|
|
## eigene Kategorie mit (Manifest-Feld `kategorie`, siehe library/objects.json).
|
|
## Diese Zuordnung zeigt Pack-/Pipeline-Objekte in sinnvollen Gruppen statt
|
|
## alles in einer Mega-„Sonstiges"-Gruppe abzulegen (das war der eigentliche
|
|
## Übersichts-Killer). Reihenfolge = Anzeige-Reihenfolge nach GROUP_ORDER.
|
|
const KAT_ORDER := ["mauern", "barrieren", "gebaeude", "requisiten", "fahrzeuge", "natur_uebrig"]
|
|
const KAT_GROUPS := {
|
|
"walls": {"key": "mauern", "label": "Mauern"},
|
|
"barriers": {"key": "barrieren", "label": "Barrieren"},
|
|
"buildings": {"key": "gebaeude", "label": "Gebäude"},
|
|
"props": {"key": "requisiten", "label": "Requisiten"},
|
|
"vehicles": {"key": "fahrzeuge", "label": "Fahrzeuge"},
|
|
"nature": {"key": "natur_uebrig", "label": "Natur (übrige)"},
|
|
}
|
|
|
|
## Anzeige-Größe der Vorschaubilder im Browser (Karte t_59fc0f21): vorher 40 px,
|
|
## jetzt Kacheln mit 96 px (128-px-PNGs werden auf die Anzeige-Größe skaliert).
|
|
const THUMB_DISPLAY_PX := 96
|
|
|
|
|
|
static func object_label(oid: String) -> String:
|
|
return NAMES.get(oid, oid.capitalize())
|
|
|
|
|
|
## Gruppe einer ID (Objekte, die keine festen Gruppe haben → "sonstiges").
|
|
static func group_of(oid: String) -> String:
|
|
for key in GROUP_IDS:
|
|
if (GROUP_IDS[key] as Array).has(oid):
|
|
return str(key)
|
|
return "sonstiges"
|
|
|
|
|
|
## Gruppe eines Katalog-Eintrags (t_59fc0f21): erst die festen Gruppen (oben),
|
|
## dann das Manifest-Feld `kategorie` (KAT_GROUPS), sonst "sonstiges".
|
|
## entry darf leer sein (z. B. fremde ID ohne Manifest-Eintrag).
|
|
static func group_of_entry(oid: String, entry: Dictionary) -> String:
|
|
var fixed := group_of(oid)
|
|
if fixed != "sonstiges":
|
|
return fixed
|
|
var kat := str(entry.get("kategorie", ""))
|
|
if KAT_GROUPS.has(kat):
|
|
return str(KAT_GROUPS[kat]["key"])
|
|
return "sonstiges"
|
|
|
|
|
|
## Deutscher Anzeigename: Manifest-Feld `name_de` (Pipeline-Objekte), sonst
|
|
## NAMES/fallback.ALLE Pipeline-Objekte zeigen damit ihren echten Namen statt
|
|
## des großgeschriebenen IDs (Karte t_59fc0f21).
|
|
static func entry_label(oid: String, entry: Dictionary) -> String:
|
|
var name_de := str(entry.get("name_de", ""))
|
|
if not name_de.is_empty():
|
|
return name_de
|
|
return object_label(oid)
|
|
|
|
|
|
## Gruppen für den Browser, geprüft gegen das Manifest: nur IDs, die es in
|
|
## der objects.json WIRKLICH gibt, landen in der Liste (und unbekannte,
|
|
## noch nicht gruppierte IDs wandern nach "Sonstiges" — neue Bibliotheks-
|
|
## Einträge brechen die UI also nicht).
|
|
static func groups_in_order(manifest: Dictionary) -> Array:
|
|
var lib: Dictionary = manifest.get("objects", {})
|
|
var by_group := {}
|
|
for oid in lib.keys():
|
|
var entry: Dictionary = lib.get(oid, {}) as Dictionary
|
|
var key := group_of_entry(str(oid), entry)
|
|
if not by_group.has(key):
|
|
by_group[key] = []
|
|
(by_group[key] as Array).append(str(oid))
|
|
var out: Array = []
|
|
# Reihenfolge (t_59fc0f21): Kategorie-Gruppen zuerst (347-Objekt-Katalog ist
|
|
# überwiegend Pipeline-Objekte — Mauern/Gebäude/Requisiten oben), danach die
|
|
# kuratierten Alt-/Natur-Gruppen, "Sonstiges" immer ganz hinten.
|
|
for key in KAT_ORDER + GROUP_ORDER:
|
|
var ids: Array = (by_group.get(key, []) as Array)
|
|
ids.sort()
|
|
if ids.is_empty() and key != "sonstiges":
|
|
continue
|
|
out.append({"key": key, "label": _group_label(key), "ids": ids})
|
|
return out
|
|
|
|
|
|
static func _group_label(key: String) -> String:
|
|
if GROUP_LABELS.has(key):
|
|
return str(GROUP_LABELS[key])
|
|
for kat in KAT_GROUPS.values():
|
|
if str(kat["key"]) == key:
|
|
return str(kat["label"])
|
|
return key
|
|
|
|
|
|
## Thumbnail-Pfad (128-px-PNG, tools/gen_thumbs.gd erzeugt sie einmalig).
|
|
static func thumb_path(oid: String) -> String:
|
|
return "res://library/thumbs/%s.png" % oid
|
|
|
|
|
|
## Thumbnail als Textur laden — direkt über Image (kein Import-System nötig,
|
|
## geht damit auch frisch nach git pull ohne Import), auf Anzeige-Größe
|
|
## runtergerechnet. size_px steuert die Anzeige-Größe (Default = THUMB_DISPLAY_PX
|
|
## = 96, Karte t_59fc0f21; vorher fest 40). null = noch kein Thumbnail →
|
|
## Button fällt aufs Text-Layout zurück (Fehler sieht man nicht, man sieht
|
|
## einfach nur kein Bild).
|
|
static func load_thumb(oid: String, size_px := THUMB_DISPLAY_PX) -> Texture2D:
|
|
var path := thumb_path(oid)
|
|
if not FileAccess.file_exists(path):
|
|
return null
|
|
var img := Image.load_from_file(path)
|
|
if img == null:
|
|
return null
|
|
if img.get_width() != size_px or img.get_height() != size_px:
|
|
img.resize(size_px, size_px, Image.INTERPOLATE_LANCZOS)
|
|
return ImageTexture.create_from_image(img)
|