diff --git a/README.md b/README.md index e594b2a..6d4b957 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # ScopedVR Map-Kern -Gemeinsamer Code für den PC-Map-Editor und die Quest-App: .svmap lesen und -schreiben, Terrain-Daten, Objekte, Spawns, Zonen, Blickrichtung, Loadout, Undo. +Der Map-Editor selbst, als gemeinsamer Code für die PC-App und die Quest-App: +Oberfläche, Werkzeuge, Ansicht, .svmap, Undo. Beide Apps haben so dieselben Funktionen. Eingebunden als Git-Submodul unter `res://core/`. Regeln: -- Kein Code für Maus, Tastatur, Fenster oder Panels. Das gehört in die App. +- Eingaben kommen als PointerEvent an die Werkzeuge. Maus (mouse_pointer.gd) und + VR-Controller (Client) sind nur Übersetzer. +- Was nur am PC geht, steht hinter `main.vr_host`. - Keine App-spezifischen Pfade. Daten wie `res://library/objects.json` liefert die App. - Maps gehören nicht in dieses Repo. diff --git a/editor/bau_katalog.gd b/editor/bau_katalog.gd new file mode 100644 index 0000000..fe3fc92 --- /dev/null +++ b/editor/bau_katalog.gd @@ -0,0 +1,448 @@ +extends RefCounted +class_name BauKatalog +## Bauteil-Katalog des Bauen-Modus (Karte "Bauen sauber integrieren", 25.10.). +## +## Ein Bauteil ist ein GANZ NORMALES Bibliotheks-Objekt (`bau_*`, Kategorie +## "bauteile"): gesetzte Teile stehen in doc["objects"], tragen ein +## Gruppen-Feld (= ein Gebäude) und laufen damit durch dieselbe Kette wie jedes +## Objekt — Undo, Kopieren, Löschen, Symmetrie, Speichern, Server-Registry, +## Client-Loader, Trimesh-Kollision. Es gibt keinen Sonderblock "gebaeude" mehr. +## +## Geometrie: die CC0-Runtime des Modular Builder (addons/modular_builder, +## Michel Savadim, v0.2.0 — Lizenz siehe ADDON_LIZENZ.txt). Hier wird sie nur +## PARAMETRIERT (Wandhöhe 3 m, 2-m-Raster) und auf die Fußpunkt-Konvention der +## Bibliothek normalisiert: Unterkante y = 0, XZ um die Mitte zentriert +## (docs/objekt_fragmente.md). Die Farbe ist die Material-Grundfarbe (glTF +## baseColorFactor) — keine Texturen, keine Lizenzfragen, 1 Draw-Call je Teil; +## Editor, Client und Thumbnails laden sie dadurch identisch. +## +## Meshes entstehen aus diesem Katalog (tools/gen_bauteile.gd schreibt GLB + +## Trimesh-JSON + Registry-Fragment), der Editor zeigt die GLBs wie jedes andere +## Objekt. mesh() ist die Quelle für Generator UND Tests. + +const GEO_SCRIPT := "res://core/vendor/modular_builder/modular_piece_geometry.gd" +const DETAIL_SCRIPT := "res://core/vendor/modular_builder/modular_building_detail.gd" +const KATEGORIE := "bauteile" +const PREFIX := "bau_" + +const RASTER_M := 2.0 # Zellgröße (XZ) +const WAND_H := 3.0 # Wandhöhe eines Geschosses +const DICKE_M := 0.2 # Wand-/Plattendicke + +# Geometrie-Typen der Addon-Runtime (PieceType-Enum, Kopie der Werte) +const T_FUNDAMENT := 0 +const T_BODEN := 2 +const T_WAND := 3 +const T_GIEBEL := 5 +const T_DACH_GIEBEL := 6 +const T_DACH_PYRAMIDE := 9 +const T_TREPPE := 10 +const D_FENSTER := 0 +const D_TUER := 1 + +## Snap-Arten (build_snap.gd): +## "zelle" Mitte einer 2×2-Zelle (Fundament, Boden, Dächer) +## "kante" Mitte einer Zellkante (Wände) — Drehung ergibt sich aus der Kante +## "ecke" Zell-Ecke (Pfeiler) +## "treppe" 4×2-m-Fläche über zwei Zellen +const GRUPPEN := [ + {"key": "grund", "label": "Fundament & Boden", "oids": ["bau_fundament", "bau_boden"]}, + {"key": "waende", "label": "Wände", "oids": ["bau_wand", "bau_wand_tuer", "bau_wand_fenster", + "bau_wand_scharte", "bau_bruestung", "bau_pfeiler"]}, + {"key": "dach", "label": "Dach & Giebel (klein 2 m)", "oids": ["bau_giebelwand", "bau_dach_giebel", "bau_dach_pyramide"]}, + {"key": "dach_mittel", "label": "Dach & Giebel (mittel 4 m)", "oids": ["bau_giebelwand_4", "bau_dach_giebel_4", "bau_dach_pyramide_4"]}, + {"key": "dach_gross", "label": "Dach & Giebel (groß 6 m)", "oids": ["bau_giebelwand_6", "bau_dach_giebel_6", "bau_dach_pyramide_6"]}, + {"key": "dach_teile", "label": "Dach aus Einzelteilen (45°)", "oids": ["bau_dach_schraege", "bau_dach_ecke_aussen", + "bau_dach_ecke_innen", "bau_dach_plateau", "bau_dach_giebel_ende"]}, + {"key": "treppe", "label": "Treppe", "oids": ["bau_treppe"]}, +] + +const QUADRAT := [[-1.0, 0.0, -1.0], [1.0, 0.0, -1.0], [1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]] +const QUADRAT_4 := [[-2.0, 0.0, -2.0], [2.0, 0.0, -2.0], [2.0, 0.0, 2.0], [-2.0, 0.0, 2.0]] +const QUADRAT_6 := [[-3.0, 0.0, -3.0], [3.0, 0.0, -3.0], [3.0, 0.0, 3.0], [-3.0, 0.0, 3.0]] + +## Reihenfolge = Anzeige-Reihenfolge (GRUPPEN nutzt dieselben IDs). +## "traeger": darauf darf gestapelt werden (Oberkante zählt als Ebene). +const KINDS := { + "bau_fundament": { + "label": "Fundament", "info": "2×2 m, 20 cm dick — Basis für Wände", "snap": "zelle", "traeger": true, + "typ": T_FUNDAMENT, "punkte": QUADRAT, "farbe": Color(0.42, 0.42, 0.40), + }, + "bau_boden": { + "label": "Boden / Decke", "info": "2×2 m Platte — Fußboden oder Decke fürs nächste Geschoss", + "snap": "zelle", "traeger": true, + "typ": T_BODEN, "punkte": QUADRAT, "farbe": Color(0.50, 0.46, 0.40), + }, + "bau_wand": { + "label": "Wand", "info": "2 m breit, 3 m hoch", "snap": "kante", "traeger": true, + "typ": T_WAND, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "farbe": Color(0.62, 0.53, 0.38), + }, + "bau_wand_tuer": { + "label": "Wand mit Tür", "info": "Türöffnung 1,2 × 2,2 m — man läuft hindurch", "snap": "kante", "traeger": true, + "typ": T_WAND, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "farbe": Color(0.62, 0.53, 0.38), + "details": [{"typ": D_TUER, "pos": [1.0, 1.1, 0.0], "w": 1.2, "h": 2.2}], + }, + "bau_wand_fenster": { + "label": "Wand mit Fenster", "info": "Fensteröffnung 1,2 × 1,2 m, Brüstung 1,1 m", "snap": "kante", "traeger": true, + "typ": T_WAND, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "farbe": Color(0.62, 0.53, 0.38), + "details": [{"typ": D_FENSTER, "pos": [1.0, 1.7, 0.0], "w": 1.2, "h": 1.2}], + }, + "bau_wand_scharte": { + "label": "Wand mit Schießscharte", "info": "Schlitz 1,0 × 0,4 m auf Schulterhöhe", "snap": "kante", "traeger": true, + "typ": T_WAND, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "farbe": Color(0.62, 0.53, 0.38), + "details": [{"typ": D_FENSTER, "pos": [1.0, 1.5, 0.0], "w": 1.0, "h": 0.4}], + }, + "bau_bruestung": { + "label": "Brüstung", "info": "2 m breit, 1,1 m hoch — Deckung, Dachrand", "snap": "kante", "traeger": true, + "typ": T_WAND, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "farbe": Color(0.56, 0.48, 0.35), "hoehe": 1.1, + }, + "bau_pfeiler": { + "label": "Eckpfeiler", "info": "Schließt die Ecke, wo zwei Wände zusammenstoßen", "snap": "ecke", "traeger": true, + "box": Vector3(0.3, 3.0, 0.3), "farbe": Color(0.42, 0.42, 0.40), + }, + "bau_giebelwand": { + "label": "Giebelwand", "info": "Dreieck-Abschluss unter dem Satteldach (2 m breit, 1 m Spitze)", + "snap": "kante", "traeger": false, + "typ": T_GIEBEL, "punkte": [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], "rise": 1.0, "farbe": Color(0.62, 0.53, 0.38), + }, + "bau_dach_giebel": { + "label": "Satteldach", "info": "2×2 m, First in Richtung der Vorschau (R dreht)", "snap": "zelle", "traeger": false, + "typ": T_DACH_GIEBEL, "punkte": QUADRAT, "rise": 1.0, "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_pyramide": { + "label": "Pyramidendach", "info": "2×2 m, Spitze in der Mitte", "snap": "zelle", "traeger": false, + "typ": T_DACH_PYRAMIDE, "punkte": QUADRAT, "rise": 1.0, "farbe": Color(0.40, 0.25, 0.18), + }, + # Große Dächer (Test-Befund 25.10.: 2×2 m reichte nur für eine Zelle). Immer 45° + # (Spitze = halbe Breite); Giebelwand und Dach gleicher Größe passen zusammen. + "bau_giebelwand_4": { + "label": "Giebelwand 4 m", "info": "Dreieck-Abschluss, 4 m breit, 2 m Spitze — zum Satteldach 4 m", + "snap": "kante", "traeger": false, + "typ": T_GIEBEL, "punkte": [[0.0, 0.0, 0.0], [4.0, 0.0, 0.0]], "rise": 2.0, "farbe": Color(0.62, 0.53, 0.38), + }, + "bau_dach_giebel_4": { + "label": "Satteldach 4×4 m", "info": "4×4 m, First in Richtung der Vorschau (R dreht)", "snap": "zelle", "traeger": false, + "typ": T_DACH_GIEBEL, "punkte": QUADRAT_4, "rise": 2.0, "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_pyramide_4": { + "label": "Pyramidendach 4×4 m", "info": "4×4 m, Spitze in der Mitte", "snap": "zelle", "traeger": false, + "typ": T_DACH_PYRAMIDE, "punkte": QUADRAT_4, "rise": 2.0, "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_giebelwand_6": { + "label": "Giebelwand 6 m", "info": "Dreieck-Abschluss, 6 m breit, 3 m Spitze — zum Satteldach 6 m", + "snap": "kante", "traeger": false, + "typ": T_GIEBEL, "punkte": [[0.0, 0.0, 0.0], [6.0, 0.0, 0.0]], "rise": 3.0, "farbe": Color(0.62, 0.53, 0.38), + }, + "bau_dach_giebel_6": { + "label": "Satteldach 6×6 m", "info": "6×6 m, First in Richtung der Vorschau (R dreht)", "snap": "zelle", "traeger": false, + "typ": T_DACH_GIEBEL, "punkte": QUADRAT_6, "rise": 3.0, "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_pyramide_6": { + "label": "Pyramidendach 6×6 m", "info": "6×6 m, Spitze in der Mitte", "snap": "zelle", "traeger": false, + "typ": T_DACH_PYRAMIDE, "punkte": QUADRAT_6, "rise": 3.0, "farbe": Color(0.40, 0.25, 0.18), + }, + # Dach aus Einzelteilen: alles 2×2-Zellen mit 45° (2 m Lauf = 2 m Höhe), massiv + # (rundum geschlossen, keine Lücke von innen). "profil" → _dach_mesh(). Tiefe + # Kante = -Z lokal (R dreht), Ecken: tiefer Punkt bei (-X,-Z). + "bau_dach_schraege": { + "label": "Dachschräge 45°", "info": "2×2 m, 2 m hoch — eine Seite des Dachs; die tiefe Kante zeigt in Richtung der Vorschau (R dreht)", + "snap": "zelle", "traeger": false, "profil": "schraege", "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_ecke_aussen": { + "label": "Dachecke außen", "info": "Walm-Ecke (Grat): zwei Schrägen treffen sich an einer Hausecke, die tiefe Ecke zeigt nach außen", + "snap": "zelle", "traeger": false, "profil": "ecke_aussen", "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_ecke_innen": { + "label": "Dachecke innen", "info": "Kehle: zwei Schrägen laufen in einer einspringenden Ecke (L-/T-Dach) zusammen", + "snap": "zelle", "traeger": false, "profil": "ecke_innen", "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_plateau": { + "label": "Dachplateau", "info": "2×2 m Block, 2 m hoch, oben flach — Mittelstück zwischen zwei Schrägen (breite Häuser), Schornstein-Sockel", + "snap": "zelle", "traeger": true, "profil": "plateau", "farbe": Color(0.40, 0.25, 0.18), + }, + "bau_dach_giebel_ende": { + "label": "Dachstirn 45°", "info": "Senkrechte Dreieck-Stirn (2 m breit, 2 m hoch) — Giebel-Abschluss unter einer Schräge, wenn das Dach an der Seite offen bleibt", + "snap": "kante", "traeger": false, "profil": "stirn", "farbe": Color(0.62, 0.53, 0.38), + }, + "bau_treppe": { + "label": "Treppe", "info": "4 m lang, 2 m breit, 3 m hoch — führt in ein Geschoss darüber", + "snap": "treppe", "traeger": false, + "typ": T_TREPPE, "punkte": [[0.0, 0.0, 0.0], [4.0, 3.0, 0.0]], "stufen": 15, "breite": 2.0, + "farbe": Color(0.46, 0.45, 0.42), + }, +} + +static var _mesh_cache: Dictionary = {} + + +static func ist_bau(oid: String) -> bool: + return KINDS.has(oid) + + +static func oids() -> Array: + return KINDS.keys() + + +static func label(oid: String) -> String: + return str((KINDS.get(oid, {}) as Dictionary).get("label", oid)) + + +static func snap_art(oid: String) -> String: + return str((KINDS.get(oid, {}) as Dictionary).get("snap", "zelle")) + + +## Platte = flaches Zell-Teil, auf dem man steht (Fundament, Boden/Decke). +static func ist_platte(oid: String) -> bool: + return snap_art(oid) == "zelle" and ist_traeger(oid) + + +static func ist_traeger(oid: String) -> bool: + return bool((KINDS.get(oid, {}) as Dictionary).get("traeger", false)) + + +## Ausmaße (m) des normalisierten Meshes: x = Länge/Breite, y = Höhe, z = Tiefe. +static func groesse(oid: String) -> Vector3: + var m := mesh(oid) + return m.get_aabb().size if m != null else Vector3.ZERO + + +# ------------------------------------------------------------------- Mesh --- + +## Normalisiertes Mesh (Fußpunkt unten-Mitte, Vertex-Farben) oder null. +static func mesh(oid: String) -> ArrayMesh: + if _mesh_cache.has(oid): + return _mesh_cache[oid] + var kind: Dictionary = KINDS.get(oid, {}) + if kind.is_empty(): + return null + var roh: ArrayMesh + if kind.has("box"): + roh = _box_mesh(kind["box"]) + elif kind.has("profil"): + roh = _dach_mesh(str(kind["profil"])) + else: + roh = _addon_mesh(kind) + var fertig := _normalisieren(roh, kind["farbe"] as Color) + _mesh_cache[oid] = fertig + return fertig + + +static func _box_mesh(size: Vector3) -> ArrayMesh: + var bm := BoxMesh.new() + bm.size = size + var out := ArrayMesh.new() + out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, bm.get_mesh_arrays()) + return out + + +## Massive Dach-Teile aus Dreiecken (Zelle 2×2 m, lokal x,z ∈ [-1,1], 45°). +## Oberfläche h(x,z): Schräge = z+1, Ecke außen = min(x,z)+1, Ecke innen = max(x,z)+1, +## Plateau = 2. Dach-Stirn: dünnes Dreieck-Prisma. +static func _dach_mesh(profil: String) -> ArrayMesh: + var tris: Array = [] # je Dreieck [a, b, c] als Vector3 + var V := func(x: float, y: float, z: float) -> Vector3: return Vector3(x, y, z) + var quad := func(a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> void: + tris.append([a, b, c]) + tris.append([a, c, d]) + var tri := func(a: Vector3, b: Vector3, c: Vector3) -> void: + tris.append([a, b, c]) + var mitte := Vector3(0.0, 0.7, 0.0) + match profil: + "schraege": + quad.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 2, 1), V.call(-1, 2, 1)) # Hang + quad.call(V.call(-1, 0, 1), V.call(1, 0, 1), V.call(1, 2, 1), V.call(-1, 2, 1)) # Rückwand + tri.call(V.call(-1, 0, -1), V.call(-1, 0, 1), V.call(-1, 2, 1)) # Stirn links + tri.call(V.call(1, 0, -1), V.call(1, 0, 1), V.call(1, 2, 1)) # Stirn rechts + quad.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 0, 1), V.call(-1, 0, 1)) # Boden + "ecke_aussen": + tri.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 2, 1)) + tri.call(V.call(-1, 0, -1), V.call(1, 2, 1), V.call(-1, 0, 1)) + tri.call(V.call(1, 0, -1), V.call(1, 0, 1), V.call(1, 2, 1)) + tri.call(V.call(-1, 0, 1), V.call(1, 0, 1), V.call(1, 2, 1)) + quad.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 0, 1), V.call(-1, 0, 1)) + "ecke_innen": + tri.call(V.call(-1, 0, -1), V.call(1, 2, -1), V.call(1, 2, 1)) + tri.call(V.call(-1, 0, -1), V.call(1, 2, 1), V.call(-1, 2, 1)) + tri.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 2, -1)) + tri.call(V.call(-1, 0, -1), V.call(-1, 2, 1), V.call(-1, 0, 1)) + quad.call(V.call(1, 0, -1), V.call(1, 0, 1), V.call(1, 2, 1), V.call(1, 2, -1)) + quad.call(V.call(-1, 0, 1), V.call(1, 0, 1), V.call(1, 2, 1), V.call(-1, 2, 1)) + quad.call(V.call(-1, 0, -1), V.call(1, 0, -1), V.call(1, 0, 1), V.call(-1, 0, 1)) + "plateau": + var bm := BoxMesh.new() + bm.size = Vector3(2.0, 2.0, 2.0) + var arr := bm.get_mesh_arrays() + var out := ArrayMesh.new() + out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr) + return out + "stirn": + # Dünnes Dreieck-Prisma (Kante entlang X, 2 m breit, 2 m hoch, 0,2 m dick) + var d := 0.1 + tri.call(V.call(-1, 0, -d), V.call(1, 0, -d), V.call(-1, 2, -d)) + tri.call(V.call(-1, 0, d), V.call(-1, 2, d), V.call(1, 0, d)) + quad.call(V.call(-1, 0, -d), V.call(-1, 0, d), V.call(1, 0, d), V.call(1, 0, -d)) + quad.call(V.call(-1, 0, -d), V.call(-1, 2, -d), V.call(-1, 2, d), V.call(-1, 0, d)) + quad.call(V.call(1, 0, -d), V.call(1, 0, d), V.call(-1, 2, d), V.call(-1, 2, -d)) + mitte = Vector3(-0.33, 0.67, 0.0) + var pos := PackedVector3Array() + var nrm := PackedVector3Array() + for t in tris: + var a: Vector3 = t[0] + var b: Vector3 = t[1] + var c: Vector3 = t[2] + var n := (b - a).cross(c - a).normalized() + # nach außen drehen: Fläche muss vom Körper-Mittelpunkt wegzeigen + if n.dot((a + b + c) / 3.0 - mitte) < 0.0: + var tmp := b + b = c + c = tmp + n = -n + pos.append_array(PackedVector3Array([a, b, c])) + nrm.append_array(PackedVector3Array([n, n, n])) + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = pos + arrays[Mesh.ARRAY_NORMAL] = nrm + var m := ArrayMesh.new() + m.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + return m + + +static func _addon_mesh(kind: Dictionary) -> ArrayMesh: + var geo := Node3D.new() + geo.set_script(load(GEO_SCRIPT)) + geo.set("piece_type", int(kind["typ"])) + geo.set("unit_size", float(kind.get("hoehe", WAND_H))) + geo.set("thickness", DICKE_M) + if kind.has("rise"): + geo.set("rise", float(kind["rise"])) + if kind.has("stufen"): + geo.set("stair_count", int(kind["stufen"])) + geo.set("stair_width", float(kind["breite"])) + var pts := PackedVector3Array() + for p in (kind["punkte"] as Array): + pts.append(Vector3(float(p[0]), float(p[1]), float(p[2]))) + geo.set("custom_points", pts) + for d in (kind.get("details", []) as Array): + var detail := Node3D.new() + detail.set_script(load(DETAIL_SCRIPT)) + detail.set("detail_type", int(d["typ"])) + detail.set("width", float(d["w"])) + detail.set("height", float(d["h"])) + detail.position = Vector3(float(d["pos"][0]), float(d["pos"][1]), float(d["pos"][2])) + geo.add_child(detail) + var m: ArrayMesh = geo.call("_make_mesh") + geo.free() + return m + + +## Alle Flächen zu EINER Fläche verschmelzen, Fußpunkt-normalisieren, Material- +## Farbe setzen. +static func _normalisieren(roh: ArrayMesh, farbe: Color) -> ArrayMesh: + var aabb := roh.get_aabb() + var versatz := Vector3(aabb.position.x + aabb.size.x * 0.5, aabb.position.y, + aabb.position.z + aabb.size.z * 0.5) + var pos := PackedVector3Array() + var nrm := PackedVector3Array() + for s in roh.get_surface_count(): + var arr := roh.surface_get_arrays(s) + var v: PackedVector3Array = arr[Mesh.ARRAY_VERTEX] + var n: Variant = arr[Mesh.ARRAY_NORMAL] + var idx: Variant = arr[Mesh.ARRAY_INDEX] + var order: Array = [] + if idx is PackedInt32Array and not (idx as PackedInt32Array).is_empty(): + for i in (idx as PackedInt32Array): + order.append(i) + else: + for i in v.size(): + order.append(i) + for i in order: + pos.append(v[i] - versatz) + nrm.append((n as PackedVector3Array)[i] if n is PackedVector3Array else Vector3.UP) + # Die Addon-Geometrie hat Dreiecke gegen die Normalenrichtung gewickelt (sie + # rendert ohne Backface-Culling). Godot culled Rückseiten → Wicklung an die + # Normale anpassen, sonst sieht man Innenseiten und Dächer werden schwarz. + for t in range(0, pos.size() - 2, 3): + var gn := (pos[t + 2] - pos[t]).cross(pos[t + 1] - pos[t]) + if gn.dot(nrm[t]) < 0.0: + var tmp := pos[t + 1] + pos[t + 1] = pos[t + 2] + pos[t + 2] = tmp + var tn := nrm[t + 1] + nrm[t + 1] = nrm[t + 2] + nrm[t + 2] = tn + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = pos + arrays[Mesh.ARRAY_NORMAL] = nrm + var out := ArrayMesh.new() + out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var mat := StandardMaterial3D.new() + mat.albedo_color = farbe + mat.roughness = 0.9 + out.surface_set_material(0, mat) + return out + + +static func dreiecke(oid: String) -> int: + var m := mesh(oid) + return int(m.get_faces().size() / 3) if m != null else 0 + + +# ------------------------------------------------- Kollision / Registry ------ + +## Trimesh-Datei (library/kollisions/.json) — gleiche Felder wie die +## Pack-Dateien: Dreiecks-Suppe in Fußpunkt-Koordinaten. Türen, Fenster und +## Scharten sind echte Löcher (die Öffnung hat keine Dreiecke). +static func kollisions_json(oid: String) -> Dictionary: + var m := mesh(oid) + if m == null: + return {} + var verts: Array = [] + for v in m.get_faces(): + verts.append([snappedf(v.x, 0.0001), snappedf(v.y, 0.0001), snappedf(v.z, 0.0001)]) + var aabb := m.get_aabb() + return { + "oid": oid, + "quelle_glb": "res://assets/objekte/military_desert/%s/%s.glb" % [KATEGORIE, oid], + "dreiecke": int(verts.size() / 3), + "aabb_min": [snappedf(aabb.position.x, 0.001), snappedf(aabb.position.y, 0.001), snappedf(aabb.position.z, 0.001)], + "aabb_max": [snappedf(aabb.end.x, 0.001), snappedf(aabb.end.y, 0.001), snappedf(aabb.end.z, 0.001)], + "vertices": verts, + } + + +## Registry-Eintrag (Fragment library/objects.d/bauteile.json). +static func registry_eintrag(oid: String) -> Dictionary: + var g := groesse(oid) + return { + "poly": dreiecke(oid), + "collision": { + "quelle": "kollisions/%s.json" % oid, + "shape": "trimesh", + "size": [snappedf(g.x, 0.001), snappedf(g.y, 0.001), snappedf(g.z, 0.001)], + }, + "glb": "res://assets/objekte/military_desert/%s/%s.glb" % [KATEGORIE, oid], + "kategorie": KATEGORIE, + "name_de": label(oid), + } + + +## GLB schreiben (eigenes Mesh mit Vertex-Farben). Rückgabe: Godot-Fehlercode. +static func glb_schreiben(oid: String, pfad: String) -> int: + var m := mesh(oid) + if m == null: + return ERR_DOES_NOT_EXIST + var root := Node3D.new() + root.name = oid + var mi := MeshInstance3D.new() + mi.name = oid + mi.mesh = m + root.add_child(mi) + var gdoc := GLTFDocument.new() + var gstate := GLTFState.new() + var err := gdoc.append_from_scene(root, gstate) + if err == OK: + err = gdoc.write_to_filesystem(gstate, pfad) + root.free() + return err diff --git a/editor/bau_katalog.gd.uid b/editor/bau_katalog.gd.uid new file mode 100644 index 0000000..3ab1224 --- /dev/null +++ b/editor/bau_katalog.gd.uid @@ -0,0 +1 @@ +uid://defuinyvy5j8i diff --git a/editor/bau_ops.gd b/editor/bau_ops.gd new file mode 100644 index 0000000..3a86fe8 --- /dev/null +++ b/editor/bau_ops.gd @@ -0,0 +1,204 @@ +extends RefCounted +class_name BauOps +## Dokument-Operationen des Bauen-Modus (pure Funktionen, headless testbar). +## +## Ein Gebäude = eine Gruppe (GroupOps, Feld "group" + group_names) aus `bau_*`- +## Objekten. Alles Weitere — Verschieben, Drehen, Kopieren, Löschen, Symmetrie, +## Speichern — läuft über die normalen Objekt-Funktionen. Dieser Teil kennt nur, +## was speziell am Bauen ist: Teil setzen/ersetzen, Gebäude-Gruppen, Migration +## der alten "gebaeude"-Sektion (Karte t_97ea59c5 → Umbau 25.10.). + + +static func teile_zahl(doc: Dictionary) -> int: + var n := 0 + for o in (doc.get("objects", []) as Array): + if o is Dictionary and BauKatalog.ist_bau(str((o as Dictionary).get("id", ""))): + n += 1 + return n + + +## Dreiecke aller Bauteile (grobe Poly-Übersicht für die Statuszeile). +static func dreiecke_gesamt(doc: Dictionary) -> int: + var n := 0 + for o in (doc.get("objects", []) as Array): + if o is Dictionary: + n += BauKatalog.dreiecke(str((o as Dictionary).get("id", ""))) + return n + + +## Neue Gebäude-Gruppe anlegen und benennen. Rückgabe: Gruppen-ID. +static func gruppe_neu(doc: Dictionary) -> int: + var gid := GroupOps.next_group_id(doc) + GroupState.set_group_name(doc, gid, "Gebäude %d" % (gebaeude_zahl(doc) + 1)) + return gid + + +## Anzahl Gebäude-Gruppen (Gruppen, deren erstes Mitglied ein Bauteil ist). +static func gebaeude_zahl(doc: Dictionary) -> int: + return gebaeude_gruppen(doc).size() + + +## [{gid, name, indizes, dreiecke}] für alle Gebäude-Gruppen. +static func gebaeude_gruppen(doc: Dictionary) -> Array: + var out: Array = [] + var objects: Array = doc.get("objects", []) + for gid in GroupOps.active_group_ids(doc): + var members := GroupOps.members_of(doc, int(gid)) + if members.is_empty(): + continue + var bau := false + var tris := 0 + for i in members: + var oid := str((objects[i] as Dictionary).get("id", "")) + if BauKatalog.ist_bau(oid): + bau = true + tris += BauKatalog.dreiecke(oid) + if bau: + out.append({"gid": int(gid), "name": GroupState.display_name(doc, int(gid)), + "indizes": members, "dreiecke": tris}) + return out + + +## Teil setzen oder ersetzen (snap = BuildSnap.berechne-Ergebnis, ok=true). +## Mutiert doc["objects"]; der Aufrufer macht snapshot/commit. +## gid: Zielgruppe (Gebäude); bei Symmetrie bekommt der Zwilling ein eigenes +## Spiegel-Gebäude (spiegel_gid, vom Aufrufer gemerkt: 1 Original-Gebäude ↔ 1 Spiegel). +## Rückgabe: {index, twin, ersetzt}. +static func setze_teil(doc: Dictionary, oid: String, snap: Dictionary, gid: int, + sym_mode: int, spiegel_gid := -1) -> Dictionary: + var objects: Array = doc["objects"] + var pos: Vector3 = snap["pos"] + var rot := float(snap["rot"]) + var ersetze := int(snap.get("ersetze", -1)) + if ersetze >= 0 and ersetze < objects.size(): + var o: Dictionary = objects[ersetze] + o["id"] = oid + o["pos"] = [pos.x, pos.y, pos.z] + SvmapIO.rot_setzen(o, Vector3(0.0, rot, 0.0)) + # Symmetrie: der Zwilling bekommt dasselbe Teil an seiner (gespiegelten) Stelle. + var key := SymmetryCore.sym_of(o) + if sym_mode != SymmetryCore.Mode.OFF and not key.is_empty(): + var partner := SymmetryCore.find_partner_index(objects, key, ersetze) + if partner >= 0 and objects[partner] is Dictionary: + (objects[partner] as Dictionary)["id"] = oid + return {"index": ersetze, "twin": -1, "ersetzt": true} + var neu := {"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0, "rot_y_deg": rot, "group": gid} + objects.append(neu) + var index := objects.size() - 1 + var twin := -1 + if sym_mode != SymmetryCore.Mode.OFF: + var res: Dictionary = ObjectOps.symmetry_place_twin(doc, index, sym_mode) + twin = int(res.get("twin", -1)) + if twin >= 0 and spiegel_gid >= 0: + (objects[twin] as Dictionary)["group"] = spiegel_gid + return {"index": index, "twin": twin, "ersetzt": false} + + +## Eine ganze Gebäude-Gruppe als Indizes (für „Ganzes Gebäude wählen“). +static func gebaeude_von(doc: Dictionary, index: int) -> Array: + var objects: Array = doc.get("objects", []) + if index < 0 or index >= objects.size(): + return [] + var gid := GroupOps.group_id(objects[index]) + if gid == GroupOps.UNGROUPED: + return [index] + return GroupOps.members_of(doc, gid) + + +# ----------------------------------------------------------- Demo / Test ---- + +## Kleines Haus über die echte Klick-Logik setzen (Fundament, Wände, Tür, Fenster, +## Giebel, Dach, Treppe) — für Demo und Tests. Jeder Schritt = BuildSnap-Treffer +## wie im Editor. Rückgabe: Gruppen-ID. history kann null sein. +static func haus_demo(doc: Dictionary, history: Variant, mitte: Vector3) -> int: + var gid := gruppe_neu(doc) + var terrain := func(_x: float, _z: float) -> float: return mitte.y + var before: Dictionary = history.snapshot(["objects", "group_names"]) if history != null else {} + # 2×2 Zellen (4×4 m): Zellmitten bei (±1, ±1) relativ zur Mitte. + var ox := floorf(mitte.x / 2.0) * 2.0 + var oz := floorf(mitte.z / 2.0) * 2.0 + var schritte := [ + ["bau_fundament", Vector3(ox - 1, mitte.y, oz - 1), 0.0], + ["bau_fundament", Vector3(ox + 1, mitte.y, oz - 1), 0.0], + ["bau_fundament", Vector3(ox - 1, mitte.y, oz + 1), 0.0], + ["bau_fundament", Vector3(ox + 1, mitte.y, oz + 1), 0.0], + # Wände liegen GANZ in der Zelle, in der die Maus ist (Außenseite bündig mit + # der Kante) — deshalb zielen die Klicks 10 cm ins Hausinnere. + ["bau_wand", Vector3(ox - 1, mitte.y, oz - 1.9), 0.0], + ["bau_wand_fenster", Vector3(ox + 1, mitte.y, oz - 1.9), 0.0], + ["bau_wand_tuer", Vector3(ox - 1, mitte.y, oz + 1.9), 0.0], + ["bau_wand", Vector3(ox + 1, mitte.y, oz + 1.9), 0.0], + ["bau_wand", Vector3(ox - 1.9, mitte.y, oz - 1), 90.0], + ["bau_wand_scharte", Vector3(ox - 1.9, mitte.y, oz + 1), 90.0], + ["bau_wand", Vector3(ox + 1.9, mitte.y, oz - 1), 90.0], + ["bau_wand", Vector3(ox + 1.9, mitte.y, oz + 1), 90.0], + ] + for st in schritte: + _demo_setzen(doc, str(st[0]), st[1] as Vector3, float(st[2]), gid, terrain) + # Ein großes Satteldach (4×4 m) auf die Wandoberkante. + var oben := mitte.y + BauKatalog.WAND_H + _demo_setzen(doc, "bau_dach_giebel_4", Vector3(ox, oben, oz), 0.0, gid, terrain) + if history != null: + history.commit("Haus gebaut (Demo)", before, history.snapshot(["objects", "group_names"])) + return gid + + +static func _demo_setzen(doc: Dictionary, oid: String, hit: Vector3, dreh: float, gid: int, + gelaende: Callable) -> void: + var teile := BuildSnap.sammle_teile(doc) + var snap := BuildSnap.berechne(oid, hit, dreh, teile, gelaende) + if bool(snap["ok"]): + setze_teil(doc, oid, snap, gid, SymmetryCore.Mode.OFF) + + +# --------------------------------------------------------------- Migration -- + +## Alte Sektion doc["gebaeude"] (Karte t_97ea59c5, 10 prozedurale Teile ohne +## Registry) → bau_*-Objekte in EINER Gruppe, dann Sektion entfernen. Beste +## Näherung: Teil-Ursprünge der alten Teile lagen an Wandanfang/Zellmitte, die +## neuen Bauteile an der Mitte der Grundfläche; Höhen (2 m → 3 m) ändern sich. +## Rückgabe: Anzahl übernommener Teile. +const _ALT := { + "fundament": "bau_fundament", "boden": "bau_boden", "wand": "bau_wand", + "tuer": "bau_wand_tuer", "fenster": "bau_wand_fenster", + "giebelwand": "bau_giebelwand", "giebelwand_rechts": "bau_giebelwand", + "dach_giebel": "bau_dach_giebel", "dach_pyramide": "bau_dach_pyramide", + "treppe": "bau_treppe", +} + +static func migriere_gebaeude(doc: Dictionary) -> int: + if not doc.has("gebaeude"): + return 0 + var alt: Variant = doc["gebaeude"] + doc.erase("gebaeude") + if not (alt is Array) or (alt as Array).is_empty(): + return 0 + if not doc.has("objects") or not (doc["objects"] is Array): + doc["objects"] = [] + var objects: Array = doc["objects"] + var gid := gruppe_neu(doc) + var n := 0 + for teil in alt: + if not (teil is Dictionary): + continue + var oid: String = _ALT.get(str((teil as Dictionary).get("id", "")), "") + if oid.is_empty(): + continue + var p: Array = (teil as Dictionary).get("pos", [0.0, 0.0, 0.0]) + var rot := float((teil as Dictionary).get("rot", 0.0)) + var pos := Vector3(float(p[0]), float(p[1]), float(p[2])) + var local := Vector3.ZERO + match str((teil as Dictionary).get("id", "")): + "wand", "tuer", "fenster", "giebelwand", "giebelwand_rechts": + local = Vector3(1.0, 0.0, 0.0) # alt: Ursprung am Wandanfang + "treppe": + local = Vector3(2.0, 0.0, 0.0) # alt: 2 m lang ab Ursprung + "boden": + pos.y -= BauKatalog.DICKE_M # alt: Oberkante am Ursprung + pos += Basis(Vector3.UP, deg_to_rad(rot)) * local + objects.append({"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0, + "rot_y_deg": fposmod(rot, 360.0), "group": gid}) + n += 1 + if n == 0: + GroupState.set_group_name(doc, gid, "") + return n diff --git a/editor/bau_ops.gd.uid b/editor/bau_ops.gd.uid new file mode 100644 index 0000000..1bd8d65 --- /dev/null +++ b/editor/bau_ops.gd.uid @@ -0,0 +1 @@ +uid://bh4sybddgu3mt diff --git a/editor/build_snap.gd b/editor/build_snap.gd new file mode 100644 index 0000000..36997a3 --- /dev/null +++ b/editor/build_snap.gd @@ -0,0 +1,256 @@ +extends RefCounted +class_name BuildSnap +## Raster- und Ebenen-Logik des Bauen-Modus (pure Funktionen, headless testbar). +## +## Warum neu: die alte Logik rastete jedes Teil NUR auf eine Zellecke und nahm +## als Höhe die Klickhöhe — ein Klick auf die Seite einer Wand setzte das neue +## Teil mitten in die Luft, Wände ließen sich nur über Rotationsraten an die +## richtige Kante bringen. Jetzt: +## • XZ: Zellen-Teile rasten in die Zellmitte, Wände auf die NÄCHSTE Zellkante +## (Drehung ergibt sich aus der Kante), Pfeiler auf die nächste Ecke, +## Treppen auf eine 4×2-m-Fläche über zwei Zellen. +## • Y: nie die Klickhöhe, sondern eine EBENE: Gelände (höchster Punkt unter +## der Grundfläche) oder Oberkante eines tragenden Bauteils darunter. Gewählt +## wird die höchste Ebene, die nicht mehr als TOL_M über dem Klickpunkt liegt. +## • Ein gleichartiges Teil auf demselben Platz wird ERSETZT (Tür in eine +## vorhandene Wand einsetzen); Klick auf die Oberkante stapelt stattdessen. +## +## Position eines Teils = Fußpunkt (Unterkante, Mitte der Grundfläche) — wie +## jedes Bibliotheks-Objekt (BauKatalog.mesh ist so normalisiert). + +const TOL_M := 0.35 # so weit darf eine Ebene ÜBER dem Klickpunkt liegen +const SHRINK_M := 0.05 # Grundflächen-Schnitt: minimale Überlappung ignorieren +const SAME_M := 0.08 # Toleranz für "gleiche Stelle" +const UEBERSTAND_M := 0.01 # Wand-Außenseite steht 1 cm vor der Plattenkante: keine deckungsgleichen + # Flächen (Z-Fighting/Flackern an Boden↔Wand) + + +## Ergebnis-Wörterbuch: +## ok false = hier nicht setzbar (grund erklärt warum) +## pos Vector3 Fußpunkt +## rot Y-Drehung in Grad +## ersetze Index des Objekts, das ersetzt wird (-1 = neues Teil) +## ebene gewählte Höhe (= pos.y) +## grund Klartext (bei ok=false) bzw. Hinweis +## +## teile: Array von {"i": Index, "oid": String, "pos": Vector3, "rot": float} +## (nur Bauteile, siehe sammle_teile). +## gelaende: Callable(x: float, z: float) -> float +## dreh: Nutzerdrehung in Grad (wird auf 90° gerastet) +## kamera_dir: Blickrichtung des Klickstrahls (für Zell-Wahl an Wandkanten), optional +## ebene_fix: feste Geschosshöhe (Geschoss-Wahl im Bauen-Tab); NAN = automatisch. +## Dann hängt die Höhe NICHT mehr an Wänden/Böden darunter — Decken und Böden +## lassen sich auch mitten im großen Raum setzen. +static func berechne(oid: String, hit: Vector3, dreh: float, teile: Array, + gelaende: Callable, hit_ist_objekt := false, kamera_dir := Vector3.ZERO, + ebene_fix := NAN) -> Dictionary: + var art := BauKatalog.snap_art(oid) + var gr := BauKatalog.groesse(oid) + var q := int(roundf(dreh / 90.0)) + var viertel := ((q % 4) + 4) % 4 + var p := Vector2(hit.x, hit.z) + # Treffer auf einem Objekt: Wände liegen GANZ in einer Zelle — die Quer-Achse + # kommt dann von der Wand (Decke/Dach landet in der Zelle der Wand). Bei anderen + # Objekten die Zelle auf der Kameraseite wählen (Klick liegt auf einer Grenze). + var wand_getroffen := {} + if hit_ist_objekt: + wand_getroffen = _wand_unter_klick(teile, p, hit.y) + if hit_ist_objekt and art != "kante": + if not wand_getroffen.is_empty(): + var wp: Vector3 = wand_getroffen["pos"] + if absf(sin(deg_to_rad(float(wand_getroffen["rot"])))) > 0.5: + p.x = wp.x + else: + p.y = wp.z + elif kamera_dir != Vector3.ZERO: + var d2 := Vector2(kamera_dir.x, kamera_dir.z) + if d2.length() > 0.01: + p -= d2.normalized() * 0.2 + var mitte := Vector2.ZERO + var rot := 0.0 + var r := BauKatalog.RASTER_M + match art: + "zelle": + # Teile über mehrere Zellen (große Dächer): gerade Zellzahl → Mitte auf + # einer Zellecke, ungerade → Mitte einer Zelle. + mitte = Vector2(_raster_mitte(p.x, gr.x), _raster_mitte(p.y, gr.z)) + rot = viertel * 90.0 + "ecke": + mitte = Vector2(roundf(p.x / r) * r, roundf(p.y / r) * r) + "treppe": + rot = viertel * 90.0 + if viertel % 2 == 0: # Lauf entlang X: Mitte auf gerader X-, ungerader Z-Linie + mitte = Vector2(roundf(p.x / r) * r, floorf(p.y / r) * r + r * 0.5) + else: + mitte = Vector2(floorf(p.x / r) * r + r * 0.5, roundf(p.y / r) * r) + _: # "kante" + var vorhanden := wand_getroffen + if not vorhanden.is_empty(): + # Klick auf eine vorhandene Wand (Seite = ersetzen, Oberkante = stapeln): + # GENAU ihre Lage übernehmen, nicht neu aus der Klickstelle raten. + var tp: Vector3 = vorhanden["pos"] + mitte = Vector2(tp.x, tp.z) + rot = 90.0 if absf(sin(deg_to_rad(float(vorhanden["rot"])))) > 0.5 else 0.0 + else: + # Wand liegt GANZ IN der Zelle, in der der Mauszeiger ist: Außenseite + # bündig mit der Zellkante (Boden/Fundament), nicht halb in der Luft. + # Dadurch schließen sich auch Ecken ohne Lücke (Test-Befund 25.10.). + var laenge := gr.x + var halbe_dicke := gr.z * 0.5 + var fx := p.x / r + var fz := p.y / r + var h_mitte := Vector2(_raster_mitte(p.x, laenge), roundf(fz) * r) # Kante entlang X + var v_mitte := Vector2(roundf(fx) * r, _raster_mitte(p.y, laenge)) # Kante entlang Z + if p.distance_squared_to(h_mitte) <= p.distance_squared_to(v_mitte): + mitte = h_mitte + mitte.y += (halbe_dicke - UEBERSTAND_M) * (1.0 if p.y >= h_mitte.y else -1.0) + rot = 0.0 + else: + mitte = v_mitte + mitte.x += (halbe_dicke - UEBERSTAND_M) * (1.0 if p.x >= v_mitte.x else -1.0) + rot = 90.0 + rot += 180.0 * float(viertel % 2) # R dreht die Wand um (Blickseite der Teile) + rot = fposmod(rot, 360.0) + var quer := absf(sin(deg_to_rad(rot))) > 0.5 # 90°/270°: X- und Z-Ausdehnung getauscht + var half := Vector2(gr.z, gr.x) * 0.5 if quer else Vector2(gr.x, gr.z) * 0.5 + var rect := Rect2(mitte - half, half * 2.0) + + # Ebenen sammeln: Gelände + Oberkanten tragender Teile unter der Grundfläche. + var gel_max := -INF + for sx in [0.0, 0.5, 1.0]: + for sz in [0.0, 0.5, 1.0]: + gel_max = maxf(gel_max, float(gelaende.call(rect.position.x + rect.size.x * sx, + rect.position.y + rect.size.y * sz))) + var ebenen: Array = [gel_max] + var kern := rect.grow(-SHRINK_M) + for t in teile: + var troid := str(t["oid"]) + var tr := teil_rect(t) + if not tr.intersects(kern): + continue + var top: float = (t["pos"] as Vector3).y + BauKatalog.groesse(troid).y + if not BauKatalog.ist_traeger(troid): + continue + if (art == "kante" or art == "ecke") and BauKatalog.ist_platte(troid): + # Wände/Pfeiler zählen von der UNTERKANTE einer Platte (Boden/Decke/ + # Fundament): jedes Geschoss liegt dadurch auf Vielfachen der Wandhöhe — + # mit oder ohne Decke darunter sitzen obere Wände gleich hoch + # (Test-Befund 25.10.). Die Platte selbst liegt auf der Wandoberkante. + ebenen.append((t["pos"] as Vector3).y) + else: + ebenen.append(top) + var ebene := ebene_fix if not is_nan(ebene_fix) else _waehle_ebene(ebenen, hit.y) + + # Platz belegt? Gleiche Art + gleiche Stelle + gleiche Ebene → ersetzen/ablehnen. + var ersetze := -1 + for t in teile: + var troid := str(t["oid"]) + if BauKatalog.snap_art(troid) != art: + continue + var tp: Vector3 = t["pos"] + if absf(tp.y - ebene) > SAME_M: + continue + if Vector2(tp.x, tp.z).distance_to(mitte) > SAME_M: + continue + if art == "kante" and absf(fposmod(float(t["rot"]), 180.0) - fposmod(rot, 180.0)) > 1.0: + continue + if art == "treppe" and absf(fposmod(float(t["rot"]), 360.0) - rot) > 1.0: + continue + if troid == oid and absf(fposmod(float(t["rot"]) - rot, 360.0)) < 1.0: + return {"ok": false, "pos": Vector3(mitte.x, ebene, mitte.y), "rot": rot, "ersetze": -1, + "ebene": ebene, "grund": "Hier steht schon %s." % BauKatalog.label(oid)} + ersetze = int(t["i"]) + break + return {"ok": true, "pos": Vector3(mitte.x, ebene, mitte.y), "rot": rot, "ersetze": ersetze, + "ebene": ebene, "grund": ""} + + +## Mitte eines Teils der Länge `laenge` entlang einer Achse: gerade Zellzahl → +## auf der nächsten Zellgrenze, ungerade → Mitte der Zelle. +static func _raster_mitte(wert: float, laenge: float) -> float: + var r := BauKatalog.RASTER_M + var zellen := maxi(1, int(roundf(laenge / r))) + if zellen % 2 == 0: + return roundf(wert / r) * r + return floorf(wert / r) * r + r * 0.5 + + +## Vorhandene Wand, auf die der Klick trifft (Seite oder Oberkante): Rückgabe das +## Teil-Wörterbuch oder {}. `p` = Klickpunkt in XZ, `y` = Klickhöhe. +static func _wand_unter_klick(teile: Array, p: Vector2, y: float) -> Dictionary: + var bester := {} + var bester_d := INF + for t in teile: + var troid := str(t["oid"]) + if BauKatalog.snap_art(troid) != "kante": + continue + var tr := teil_rect(t).grow(0.06) + if not tr.has_point(p): + continue + var unten: float = (t["pos"] as Vector3).y + var oben := unten + BauKatalog.groesse(troid).y + if y < unten - 0.1 or y > oben + 0.1: + continue + var d := p.distance_squared_to(tr.get_center()) + if d < bester_d: + bester_d = d + bester = t + return bester + + +## Höchste Ebene ≤ hit_y + TOL; gibt es keine (Klick tief unter allen), die tiefste. +static func _waehle_ebene(ebenen: Array, hit_y: float) -> float: + var best := -INF + var tiefste := INF + for e in ebenen: + var ev := float(e) + tiefste = minf(tiefste, ev) + if ev <= hit_y + TOL_M and ev > best: + best = ev + return best if best > -INF else tiefste + + +## Grundfläche (XZ) eines gesetzten Bauteils in Weltkoordinaten. +static func teil_rect(t: Dictionary) -> Rect2: + var gr := BauKatalog.groesse(str(t["oid"])) + var rot := float(t["rot"]) + var quer := absf(sin(deg_to_rad(rot))) > 0.5 + var half := Vector2(gr.z, gr.x) * 0.5 if quer else Vector2(gr.x, gr.z) * 0.5 + var pos: Vector3 = t["pos"] + return Rect2(Vector2(pos.x, pos.z) - half, half * 2.0) + + +## Alle Bauteile aus doc["objects"] (Index, oid, pos, rot) — nur `bau_*`. +static func sammle_teile(doc: Dictionary) -> Array: + var out: Array = [] + var objects: Array = doc.get("objects", []) + for i in objects.size(): + var o: Variant = objects[i] + if not (o is Dictionary): + continue + var oid := str((o as Dictionary).get("id", "")) + if not BauKatalog.ist_bau(oid): + continue + var pos: Array = (o as Dictionary).get("pos", [0.0, 0.0, 0.0]) + if pos.size() != 3: + continue + out.append({ + "i": i, "oid": oid, + "pos": Vector3(float(pos[0]), float(pos[1]), float(pos[2])), + "rot": float(SvmapIO.objekt_rotation(o as Dictionary).y), + }) + return out + + +## Höhe des untersten Geschosses (= Fußpunkt der tiefsten Platte/Wand im Umkreis von +## `radius` um `p`), sonst das Gelände bei `p`. Geschoss k liegt bei Basis + k·WAND_H. +static func geschoss_basis(teile: Array, p: Vector2, gelaende: Callable, radius := 40.0) -> float: + var tiefste := INF + for t in teile: + var tp: Vector3 = t["pos"] + if Vector2(tp.x, tp.z).distance_to(p) > radius: + continue + var oid := str(t["oid"]) + if BauKatalog.ist_platte(oid) or BauKatalog.snap_art(oid) == "kante": + tiefste = minf(tiefste, tp.y) + return tiefste if tiefste < INF else float(gelaende.call(p.x, p.y)) diff --git a/editor/build_snap.gd.uid b/editor/build_snap.gd.uid new file mode 100644 index 0000000..1d8709e --- /dev/null +++ b/editor/build_snap.gd.uid @@ -0,0 +1 @@ +uid://irnbwhk0eijq diff --git a/editor/fly_math.gd b/editor/fly_math.gd new file mode 100644 index 0000000..22276b5 --- /dev/null +++ b/editor/fly_math.gd @@ -0,0 +1,71 @@ +extends RefCounted +class_name FlyMath +## Freelook-Flug-Mathe (Karte t_c46d31c2, Karte 1/3) — bewusst pure Funktionen +## (kein Knoten, keine Kamera): speed_for/wheel_speed/move_delta/forward- und +## Euler-Umrechnung. tests/test_shortcuts.gd prüft headless; orbit_camera.gd +## ist nur der dünne Anwender. +## +## Euler-Konvention identisch zur Orbit-Kamera (_apply in orbit_camera.gd): +## Blickrichtung forward(yaw, pitch) = +## (−sin(yaw)·cos(pitch), −sin(pitch), −cos(yaw)·cos(pitch)) +## pitch positiv = Kamera schaut von OBEN herab (Orbit-Pitch 5…85°), +## im Flug erlaubt: −85…+85° (auch von unten hochschauen). + +const BASE_SPEED_MPS := 30.0 # Grundgeschwindigkeit (Mausrad ändert sie dauerhaft) +const FAST_MULT := 3.0 # Strg halten = schneller (Shooter-Konvention) +const SLOW_MULT := 0.3 # Alt halten = langsamer +const WHEEL_MULT := 1.15 # Mausrad-Tick: +15 % / −13 % +const SPEED_MIN_MPS := 2.0 +const SPEED_MAX_MPS := 300.0 + +const PITCH_MAX_DEG := 85.0 # Flug-Blick grenzwertig (±) — kein Gimbal-Kippen + + +## Flug-Tempo: Strg dominiert, wenn (unüblicherweise) beide Modifier unten sind. +static func speed_for(base_mps: float, fast: bool, slow: bool) -> float: + if fast: + return base_mps * FAST_MULT + if slow: + return base_mps * SLOW_MULT + return base_mps + + +## Mausrad-Tick: Grundgeschwindigkeit DAUERHAFT ändern (nicht nur solange +## gedrückt) — up=true schneller, down=false langsamer, immer geklemmt. +static func wheel_speed(base_mps: float, up: bool) -> float: + var next := base_mps * WHEEL_MULT if up else base_mps / WHEEL_MULT + return clampf(next, SPEED_MIN_MPS, SPEED_MAX_MPS) + + +## Bewegungs-Delta für einen Frame. forward = BLICKRICHTUNG (3D, mit Pitch — +## W/S fliegen dorthin, wo man hinsieht), right = horizontal-rechts, und +## vertikal ist Welt-UP (Leertaste/Shift heben/senken unabhängig vom Blick — +## Shooter-Konvention, Sly-Wunsch 17.10.). +## Diagonal-Eingaben werden normalisiert (kein √2-Tempo). Rückgabe: Meter. +static func move_delta(forward: Vector3, right: Vector3, fwd_input: float, + side_input: float, vert_input: float, speed_mps: float, delta_s: float) -> Vector3: + var dir := forward * fwd_input + right * side_input + Vector3.UP * vert_input + if dir.length_squared() < 0.0000001: + return Vector3.ZERO + return dir.normalized() * speed_mps * delta_s + + +## Blickrichtung → (yaw_deg, pitch_deg) in der Orbit-Konvention (siehe oben). +static func euler_from_forward(forward: Vector3) -> Vector2: + var f := forward + if f.length_squared() < 0.0000001: + return Vector2(0.0, 0.0) + f = f.normalized() + var pitch := rad_to_deg(asin(clampf(-f.y, -1.0, 1.0))) + var yaw := rad_to_deg(atan2(-f.x, -f.z)) + return Vector2(yaw, pitch) + + +## (yaw_deg, pitch_deg) → Blickrichtung (Inverse von euler_from_forward). +static func forward_from_euler(yaw_deg: float, pitch_deg: float) -> Vector3: + var yaw := deg_to_rad(yaw_deg) + var pitch := deg_to_rad(clampf(pitch_deg, -PITCH_MAX_DEG, PITCH_MAX_DEG)) + return Vector3( + -sin(yaw) * cos(pitch), + -sin(pitch), + -cos(yaw) * cos(pitch)) \ No newline at end of file diff --git a/editor/fly_math.gd.uid b/editor/fly_math.gd.uid new file mode 100644 index 0000000..9885ab3 --- /dev/null +++ b/editor/fly_math.gd.uid @@ -0,0 +1 @@ +uid://btbsxedv3fvw diff --git a/editor/interaction.gd b/editor/interaction.gd new file mode 100644 index 0000000..266078f --- /dev/null +++ b/editor/interaction.gd @@ -0,0 +1,1729 @@ +extends Node3D +## Maus-Interaktion im Editor-Viewport (M2 UX-Fundament, Karte t_40d855d0; +## Auswahl-System Erweiterung: Karte t_f6f29d86): +## - Ghost-Preview: beim Platzieren schwebt eine transparente Vorschau am +## Cursor, Klick setzt das Objekt (kein "überraschtes" Ergebnis). +## - Auswahl: angeklickte Objekte färben sich orange, Ziehen verschiebt, +## R-Taste ODER Mausrad dreht (15°-Raster), Entf löscht. +## - Mehrfachauswahl (Karte t_f6f29d86, erweitert t_c46d31c2): Umschalt+Klick +## ODER Strg+Klick fügt hinzu/entfernt, Rahmen-Aufziehen über leeren Boden +## wählt alles im Rahmen. Löschen/Duplizieren wirken auf die GANZE Auswahl. +## - Gruppen: Objekte mit gleicher "group"-ID ziehen/drehen gemeinsam +## (GroupOps), Gruppieren/Lösen über main-Buttons (Strg+G / Strg+Umschalt+G). +## - Undo: Jede Aktion = EINE ActionHistory-Stufe (auch Gruppen-Ziehen). +## Alle Meldungen laufen über main._set_status — Klartext + nächsten Schritt +## vorschlagen (Chef-Grundsatz, map-editor-README). +## +## Lebt im selben SubViewport-Welt-Knoten wie Kamera + Viewport-Bauer; +## main.gd verdrahtet Referenzen per setup(). + +const ROT_STEP_DEG := 15.0 +const TILT_STEP_DEG := 15.0 # Kipp-Schritt (Karte t_e166e656: Strg+R / Alt+R) +const DRAG_THRESHOLD_PX := 4.0 +const GRID_M := 1.0 # Raster-Snap-Weite (Objekt-Werkzeuge links, Karte t_3a998ae4) + +# Höhe beim Ziehen merken (Karte t_7567a06f): das Objekt behält seinen +# Abstand zum Terrain — ein angehobenes Objekt bleibt beim Verschieben oben. + +var main # Hauptfenster (script-erweitert, Variant — Referenzen per setup()) +var camera: Camera3D +var view_builder # script-erweitert, Variant + +# Platzier-Modus +var placing := false +var place_oid := "" +var place_rot_deg := 0.0 +var place_tilt_x := 0.0 # Kippen X beim Platzieren (Karte t_e166e656, Strg+R) +var place_tilt_z := 0.0 # Kippen Z beim Platzieren (Karte t_e166e656, Alt+R) +# U5 (t_5e3e97c9): Shift beim Setz-Klick = Serienmodus — weiter setzen wie +# bisher (Ghost bleibt an, jeder Klick setzt). Ohne Shift wird EIN Objekt +# gesetzt und das Werkzeug beendet sich selbst; das neue Objekt ist ausgewählt. +var _place_series := false +var _ghost: Node3D # Ghost-ROOT: Position = Objekt-Fußpunkt — + # identisch zur platzierten Marker-Position + # (Karte t_4092e3a0: Ghost == Platzierung). +var _ghost_mi: MeshInstance3D # Kind-Mesh, gehoben um half_height — + # exakt wie der Viewport-Bauer (map_view_builder). +var _ghost_active := false # Ghost sichtbar weil Cursor über der Map ist + +# Bauen-Modus (Bauteile bau_*): der Ghost rastet über BuildSnap (Kante/Zelle/ +# Ebene), jeder Klick setzt ein Teil in das laufende Gebäude (= Gruppe). +var build_group_id := -1 # laufendes Gebäude; -1 = nächstes Teil beginnt ein neues +var _build_last: Dictionary = {} # letztes BuildSnap-Ergebnis (pos/rot/ersetze/ok/grund) +var _build_hit: Dictionary = {} # letzter Treffer {punkt, obj, dir} +var _build_hint := "" # letzte Statuszeile des Ghosts (kein Spam) +var build_geschoss := -1 # -1 = Höhe automatisch, sonst festes Geschoss (0 = Boden, 1 = erste Decke …) +const BAU_GESCHOSS_MAX := 8 + +# Terrain-Höhen-Cache (Karte t_4092e3a0): der Objekt-Cursor trifft das GELÄNDE +# (TerrainField.raycast, wie Terrain-/Bauen-/Spawn-Werkzeug), nicht mehr nur +# die flache Basis-Ebene. Dekodiert einmal pro Map-Änderung, nicht pro Mausbewegung. +var _heights := PackedFloat32Array() +var _res := 0 +var _sx := 0.0 +var _sz := 0.0 + +# Auswahl + Greifen +var selected_index := -1 # PRIMÄR-Objekt (letzter Klick) — Grab/Status-Anker +var selected_indices: Array = [] # komplette Auswahl in Klick-Reihenfolge (int-Indizes) +var _dragging := false +var _drag_moved := false +var _press_pos := Vector2.ZERO +var _grab_offset := Vector3.ZERO # Einzel-Greifen (Rückwärtskompatibilität) +var _drag_lift := 0.0 # Terrain-Abstand des Objekts beim Anfassen (Höhe hebt sich mit) + +# Gruppen-/Mehrfach-Ziehen (Karte t_f6f29d86) +var _drag_members: Array = [] # Indizes, die GEMEINSAM ziehen +var _grab_offsets: Dictionary = {} # index (int) → Vector3 (y = 0) +var _drag_lifts: Dictionary = {} # index (int) → float (Terrain-Abstand beim Anfassen, t_7567a06f) +var _drag_primary := -1 # Anker-Objekt (Raster-Snap & Delta-Herkunft) +var _drag_is_group := false # Zieh-Menge = komplette svmap-Gruppe + +# Rahmen-Ausziehen (Rubberband) über leeren Boden +var _rubber_pending := false # Press auf leerer Fläche, noch nicht geschwenkt +var _rubber_active := false # Rahmen wird gerade aufgezogen +var _rubber_start := Vector2.ZERO +var _rubber_shift := false # Umschalt: Rahmen zur Auswahl HINZUFÜGEN + +# Strg halten = temporärer Snap-Zwang (Karte t_55257792): egal was der +# Raster-Snap-Schalter sagt — solange Strg unten ist, rastet Bewegen/Platzieren. +# Aus JEDEM durchgelassenen Input-Event aktualisiert (Maus- UND Taste). +var _ctrl_held := false + +# Greifen (Karte t_55257792, G — Blender-Semantik): die Auswahl hängt an der +# Maus, Linksklick setzt die neue Position (EIN Undo-Schritt), Esc bricht ab +# und stellt die EXAKTEN alten Positionen wieder her. +var _grabbing := false +var _grab_members: Array = [] # Indizes der Greif-Menge (inkl. Gruppe) +var _grab_primary := -1 +var _grab_originals: Dictionary = {} # idx (int) → [x, y, z] exakt (Rückabwicklung) +var _grab_lifts: Dictionary = {} # idx (int) → Terrain-Abstand beim Anfassen +var _grab_before: Dictionary = {} # History-Snapshot VOR dem Greifen +# Symmetrie-Zwillinge beim Greifen (M3, t_34908425): Mitglied → Partner-Index; +# Originale der Partner, die NICHT selbst greifen (für die Esc-Rückabwicklung). +var _grab_twin_map: Dictionary = {} # idx (int) → partner index (int) +var _grab_twin_originals: Dictionary = {} # partner idx (int) → [x, y, z] exakt + + +func setup(p_main, p_camera: Camera3D, p_builder) -> void: + main = p_main + camera = p_camera + view_builder = p_builder + refresh_terrain_cache() # t_4092e3a0: Cursor trifft das Gelände ab der ersten Mausbewegung + + +## Symmetrie-Modus aus dem Hauptfenster lesen (M3, t_34908425). Fehlt das Feld +## (ältere Test-Doppel, Setup-Reihenfolgen), gilt Aus — nie ein harter Fehler. +func _sym_mode() -> int: + var v: Variant = main.get("sym_mode") if main != null else null + return int(v) if v is int else SymmetryCore.Mode.OFF + + +## Kamera fragt: dreht das Mausrad gerade ein Objekt/eine Vorschau +## (dann zoomt das Rad NICHT)? Im Freelook-Flug (t_c46d31c2) blockt das Rad +## zusätzlich ALLE Objekt-Reaktionen — dort ändert es die Grundgeschwindigkeit. +func wheel_blocked() -> bool: + if _camera_flying(): + return true + return placing or selected_index >= 0 + + +func _camera_flying() -> bool: + return camera != null and camera.has_method("fly_active") \ + and bool(camera.call("fly_active")) + + +## Höhen-Cache auffrischen (Karte t_4092e3a0): NACH Terrain-Edits, Undo/Redo, +## Laden/Neu — main ruft das in _after_structure_change. Ein Decode pro +## Änderung statt pro Mausbewegung (129×129-Quantisierung bleibt billig). +func refresh_terrain_cache() -> void: + var terrain: Dictionary = main.doc.get("terrain", {}) + _res = int(terrain.get("resolution", 0)) + _sx = float(terrain.get("size_x_m", 0.0)) + _sz = float(terrain.get("size_z_m", 0.0)) + if _res >= 2 and _sx > 0.0 and _sz > 0.0: + _heights = SvmapIO.decode_heights(terrain) + else: + _heights = PackedFloat32Array() + + +func _ground_point(viewport_pos: Vector2) -> Variant: + # Cursor-Strahl → erster Treffer AUF DEM GELÄNDE (Karte t_4092e3a0): die + # Höhenform zählt, nicht die flache Basis-Ebene — sonst landet ein Objekt + # auf einem Hügel versetzt hinter dem Cursor. Gleicher Pfad wie Terrain-, + # Bauen- und Spawn-Werkzeug (TerrainField.raycast). Rückgabe null = kein + # Treffer (Strahl ins Leere / außerhalb der Map). + var terrain: Dictionary = main.doc.get("terrain", {}) + if terrain.is_empty(): + return null + var origin := camera.project_ray_origin(viewport_pos) + var dir := camera.project_ray_normal(viewport_pos) + if _res >= 2 and _heights.size() >= _res * _res: + return TerrainField.raycast(_heights, _res, _sx, _sz, origin, dir) + # Fallback (Map ohne brauchbares Höhenfeld): Basis-Ebene wie bisher. + var plane_y := float(terrain.get("min_height_m", 0.0)) + if absf(dir.y) < 0.0001: + return null + var t := (plane_y - origin.y) / dir.y + if t <= 0.0: + return null + return origin + dir * t + + +func _terrain_half() -> Vector2: + var terrain: Dictionary = main.doc.get("terrain", {}) + return Vector2(float(terrain.get("size_x_m", 1024.0)), float(terrain.get("size_z_m", 1024.0))) * 0.5 + + +## Cursor-Strahl → getroffenes Objekt. Rückgabe: Index in doc.objects oder -1. +## Zwei Stufen (Mauer-Klick-Fix 24.10.): Die Kugel-Näherung der Marker ist nur +## noch Vorfilter — entschieden wird über den EXAKTEN Treffer auf den echten +## Mesh-Dreiecken (nächstes getroffenes Objekt gewinnt). Nur wenn der Strahl +## kein Mesh trifft, zählt wie bisher die nächste Kugel (kleine Objekte und +## Platzhalter bleiben leicht anklickbar). +func _pick_object(viewport_pos: Vector2) -> int: + var origin := camera.project_ray_origin(viewport_pos) + var dir := camera.project_ray_normal(viewport_pos) + var ground: Variant = _ground_point(viewport_pos) + var ground_t := INF + if ground != null: + ground_t = (ground as Vector3).distance_to(origin) + var exact := -1 + var exact_t := INF + var best := -1 + var best_t := INF + for marker in view_builder.object_markers: + if marker == null or not is_instance_valid(marker): + continue + var center: Vector3 = marker.global_position + Vector3.UP * float(marker.get_meta("half_height", 1.0)) + var to_center := center - origin + var tca := to_center.dot(dir) + if tca <= 0.0: + continue + var d2 := to_center.length_squared() - tca * tca + var radius := float(marker.get_meta("pick_radius", 1.0)) + if d2 > radius * radius: + continue + var t := tca - sqrt(radius * radius - d2) + if t < best_t and t < ground_t + 0.01: + best = int(marker.get_meta("obj_index", -1)) + best_t = t + var mi := SelectionMath.marker_mesh(marker) + if mi != null and mi.mesh != null: + var mt := SelectionMath.ray_mesh_distance(mi.global_transform, + SelectionMath.mesh_faces(mi.mesh), origin, dir) + if mt >= 0.0 and mt < exact_t and mt < ground_t + 0.01: + exact = int(marker.get_meta("obj_index", -1)) + exact_t = mt + return exact if exact >= 0 else best + + +# ------------------------------------------------------------ Platzieren ---- + +func start_placement(oid: String) -> void: + place_oid = oid + placing = true + place_rot_deg = 0.0 + place_tilt_x = 0.0 + place_tilt_z = 0.0 + _place_series = BauKatalog.ist_bau(oid) # U5: Einmal-Modus; Bauteile = Serie (Esc beendet) + _ghost_active = false + _build_last = {} + _build_hit = {} + _build_hint = "" + refresh_terrain_cache() # Cursor trifft das GELÄNDE — Cache muss frisch sein + _ensure_ghost() + _ensure_ghost_visible() + var label: String = main.object_label(oid) + if BauKatalog.ist_bau(oid): + main.call("_set_status", "%s: Maus über die Karte — die Vorschau rastet auf Zelle/Kante ein. Klick setzt, R dreht, Esc beendet. Klick auf die Wandseite ersetzt das Teil (z. B. Tür), Klick auf die Oberkante stapelt." % label) + main.call("_highlight_library", oid) + return + var hinweis := "" if not GlbMeshStore.fehlt(oid) \ + else " (Platzhalter — echte GLB fehlt für %s)" % oid + main.call("_set_status", "%s%s: führe die Maus über die Karte — Klick setzt es. Mausrad dreht vorher, ESC bricht ab (Rechte Maustaste = Flug)." % [label, hinweis]) + main.call("_highlight_library", oid) + + +func cancel_placement() -> void: + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + placing = false + place_oid = "" + _place_series = false + _ghost_active = false + _build_last = {} + _build_hit = {} + build_geschoss = -1 + main.call("_highlight_library", "") + main.call("_base_tool_status") + + +## U5 (t_5e3e97c9): Ein Setz-Klick beendet das Platzier-Werkzeug und wählt +## GENAU das neue Objekt aus (Baum-Markierung + Inspektor folgen über +## select_object). Ghost aus, Kachel-Hervorhebung im Assets-Panel aus +## (_highlight_library("")), das Objekt-Werkzeug bleibt das aktive Werkzeug. +## from_tree=true: der Baum wird bereits von _apply_selection_view markiert — +## kein zweites Auswahl-Ereignis (Auswahl-Stabilität, Karten-Test). +## Aufruf VOR der Klartext-Statusmeldung: die letzte Meldung gewinnt. +func finish_placement_and_select(new_index: int) -> void: + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + placing = false + place_oid = "" + _place_series = false + _ghost_active = false + main.call("_highlight_library", "") + select_object(new_index, true) + + +func _ensure_ghost() -> void: + if _ghost != null and is_instance_valid(_ghost): + return + # Struktur WIE der Viewport-Bauer (map_view_builder._build_objects): + # Root auf dem Fußpunkt, Kind-Mesh um half_height gehoben. So kann Ghost + # und platziertes Objekt nie auseinanderfallen (Karte t_4092e3a0). + _ghost = Node3D.new() + _ghost_mi = MeshInstance3D.new() + var mat := StandardMaterial3D.new() + mat.albedo_color = Color(0.6, 0.9, 1.0, 0.45) + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.emission_enabled = true + mat.emission = Color(0.2, 0.5, 0.8) * 0.4 + _ghost_mi.material_override = mat + _ghost.add_child(_ghost_mi) + add_child(_ghost) + _ghost.visible = false + + +func _ghost_refresh_mesh() -> void: + var lib: Dictionary = main.manifest.get("objects", {}) + var entry: Dictionary = lib.get(place_oid, {}) + var col: Dictionary = entry.get("collision", {}) + # Echtes GLB vorziehen (Karte t_9b50b231) — die Vorschau zeigt das + # Modell, das landet, nicht nur die Kollisionsform. Ohne GLB: Platzhalter. + var mesh := GlbMeshStore.mesh_fuer(place_oid) + if mesh == null: + mesh = MeshShapes.make_placeholder(str(col.get("shape", "box")), col, 1.0) + _ghost_mi.mesh = mesh + # Gleicher Lift wie beim platzierten Objekt: Mesh-Unterseite = Fußpunkt. + _ghost_mi.position.y = MeshShapes.half_height(mesh) + + +func _ensure_ghost_visible() -> void: + _ghost_refresh_mesh() + _ghost_rotation_anwenden() + _ghost.visible = true + + +## Ghost-Rotation inkl. Kippen (Karte t_e166e656): Euler (X-Kippen, Y-Drehung, +## Z-Kippen) — derselbe Weg wie beim platzierten Objekt (rot_setzen schreibt +## genau diese drei Werte). +func _ghost_rotation_anwenden() -> void: + if _ghost == null or not is_instance_valid(_ghost): + return + if BauKatalog.ist_bau(place_oid): + _bau_ghost_neu() # Drehung ergibt sich aus Kante/Zelle + R + return + _ghost.rotation_degrees = Vector3(place_tilt_x, place_rot_deg, place_tilt_z) + + +## Raster-Snap (wenn in main angeschaltet ODER Strg gehalten wird — temporärer +## Snap-Zwang, Karte t_55257792): X/Z auf ganze Meter rasten — Objekte passen +## dann nebeneinander wie Bauklötze. Höhe bleibt Terrain. +func _apply_grid(pos: Vector3) -> Vector3: + if not (bool(main.get("grid_snap")) or _ctrl_held): + return pos + pos.x = ObjectOps.snap_coord(pos.x, true, GRID_M) + pos.z = ObjectOps.snap_coord(pos.z, true, GRID_M) + return pos + + +## Raster-Snap-Erzwingung fürs Platzieren: Schalter an ODER Strg gehalten. +func _placement_grid_enabled() -> bool: + return bool(main.get("grid_snap")) or _ctrl_held + + +func _ghost_update(viewport_pos: Vector2) -> void: + if BauKatalog.ist_bau(place_oid): + _bau_ghost_update(viewport_pos) + return + var p: Variant = _ground_point(viewport_pos) + if p == null: + _ghost_active = false + _ghost.visible = false + return + _ghost_active = true + # EIN Pfad für Ghost UND Platzierung (Karte t_4092e3a0): Fußpunkt = + # Terrain-Höhe am gerasteten Punkt. Der sichtbare Lift hängt am Kind-Mesh. + _ghost.position = ObjectOps.placement_foot( + main.doc, p as Vector3, _placement_grid_enabled(), _terrain_half(), true) + _ghost.visible = true + + +func _place_object(viewport_pos: Vector2, mb_shift_held := false) -> void: + if BauKatalog.ist_bau(place_oid): + _place_bau(viewport_pos) + return + var p: Variant = _ground_point(viewport_pos) + if p == null: + main.call("_set_status", "Da war kein Boden unter dem Mauszeiger — klicke IN die Karte (die grüne Fläche).", true) + return + var half := _terrain_half() + var foot: Vector3 = ObjectOps.placement_foot( + main.doc, p as Vector3, _placement_grid_enabled(), half, false) + if absf(foot.x) > half.x or absf(foot.z) > half.y: + main.call("_set_status", "Das wäre außerhalb der Map gelandet — klicke innerhalb der grünen Fläche.", true) + return + + # Poly-Budget prüfen, BEVOR etwas passiert (hilfreiche Meldung statt Fehler). + # Symmetrie-Modus (M3, t_34908425): der Zwilling kostet Dasselbe nochmal — + # außer das Objekt liegt auf der Mitte/Achse (wird nicht gedoppelt). + var sym_mode: int = _sym_mode() + var sym_center := SymmetryCore.map_center(main.doc) + var sym_factor := 1.0 + if sym_mode != SymmetryCore.Mode.OFF \ + and not SymmetryCore.maps_to_self(sym_mode, foot, sym_center): + sym_factor = 2.0 + var lib: Dictionary = main.manifest.get("objects", {}) + var entry: Dictionary = lib.get(place_oid, {}) + var poly := float(entry.get("poly", 0)) + var used := SvmapDocument.poly_usage(main.doc, main.manifest) + var budget := float(SvmapIO.MAX_TOTAL_POLY) + if used + poly * sym_factor > budget: + main.call("_set_status", + "Kein Platz mehr im Poly-Budget (%s von %s belegt) — lass ein Objekt weg oder drück Strg+Z, um etwas zurückzunehmen." + % [main._fmt_int(int(used)), main._fmt_int(int(budget))], true) + return + + var before: Dictionary = main.history.snapshot(["objects"]) + # EIN Pfad für Ghost UND Platzierung (Karte t_4092e3a0): derselbe Fußpunkt + # wie die Vorschau — Raster-Snap + Terrain-Höhe am gerasteten Punkt. + var pos_f := [foot.x, foot.y, foot.z] + var neues_obj := { + "id": place_oid, + "pos": pos_f, + "scale": 1.0, # Karte t_e166e656: Scale ist FIX — immer Originalgröße + "rot_y_deg": float(place_rot_deg), + } + # Karte t_e166e656: gekippt platziert → rot-Feld (Euler xyz) in die Datei. + if absf(place_tilt_x) > 0.0001 or absf(place_tilt_z) > 0.0001: + SvmapIO.rot_setzen(neues_obj, Vector3(place_tilt_x, float(place_rot_deg), place_tilt_z)) + (main.doc["objects"] as Array).append(neues_obj) + var new_index: int = (main.doc["objects"] as Array).size() - 1 + # Symmetrie (M3, t_34908425): der Zwilling wandert in denselben Undo-Schritt + # (ein Commit für Quelle + Zwilling) und bekommt dieselbe sym-Kennung. + var twin_index := -1 + var on_axis := false + if sym_mode != SymmetryCore.Mode.OFF: + var symres: Dictionary = ObjectOps.symmetry_place_twin(main.doc, new_index, sym_mode) + twin_index = int(symres.get("twin", -1)) + on_axis = bool(symres.get("on_axis", false)) + main.history.commit("%s platzieren" % main.object_label(place_oid), + before, main.history.snapshot(["objects"])) + main.call("_after_structure_change") + # U5 (t_5e3e97c9): Shift gehalten = Serienmodus (weiter setzen wie bisher, + # Ghost bleibt an) — sonst beendet das Werkzeug sich selbst und DAS NEUE + # Objekt ist ausgewählt (Baum markiert, Inspektor gefüllt). + if mb_shift_held: + _place_series = true + select_object(new_index, true) + if twin_index >= 0: + main.call("_set_status", "Serienmodus (Shift) — ESC beendet. %s platziert, Zwilling jeweils mitgesetzt; Strg+Z nimmt BEIDE je Schritt zurück. Weitere Klicks setzen erneut." % main.object_label(place_oid)) + else: + main.call("_set_status", "Serienmodus (Shift) — ESC beendet. Weitere Klicks setzen erneut; Strg+Z nimmt je Schritt zurück.") + else: + var placed_oid := place_oid # U5: finish leert place_oid — Label vorher sichern + finish_placement_and_select(new_index) + if twin_index >= 0: + main.call("_set_status", "%s gesetzt und ausgewählt — der Spiegel-Zwilling wurde mitgesetzt; Strg+Z nimmt BEIDE zurück. Ziehen verschiebt, R oder Mausrad dreht, Entf löscht." % main.object_label(placed_oid)) + elif on_axis: + main.call("_set_status", "%s gesetzt und ausgewählt — liegt auf der Symmetrie-Mitte und wird NICHT gedoppelt. Ziehen verschiebt, R oder Mausrad dreht, Entf löscht." % main.object_label(placed_oid)) + else: + main.call("_set_status", "%s gesetzt und ausgewählt. Ziehen verschiebt, R oder Mausrad dreht, Entf löscht, Strg+Z nimmt es zurück." % main.object_label(placed_oid)) + + +# ----------------------------------------------------- Bauen (bau_*) ----- + +## Strahl → Treffer auf Gelände ODER einem gesetzten Objekt (Bauteile müssen +## sich auf Wänden/Böden stapeln lassen). Rückgabe {punkt, obj (-1 = Gelände), dir} +## oder {} bei Leerlauf. +func _build_ray_hit(viewport_pos: Vector2) -> Dictionary: + var origin := camera.project_ray_origin(viewport_pos) + var dir := camera.project_ray_normal(viewport_pos) + var ground: Variant = _ground_point(viewport_pos) + if build_geschoss >= 0: + return _build_ray_geschoss(origin, dir, ground) + var best_t := INF + var out := {} + if ground != null: + best_t = (ground as Vector3).distance_to(origin) + out = {"punkt": ground, "obj": -1, "dir": dir} + for marker in view_builder.object_markers: + if marker == null or not is_instance_valid(marker): + continue + var center: Vector3 = marker.global_position + Vector3.UP * float(marker.get_meta("half_height", 1.0)) + var to_center := center - origin + var tca := to_center.dot(dir) + if tca <= 0.0: + continue + var radius := float(marker.get_meta("pick_radius", 1.0)) + if to_center.length_squared() - tca * tca > radius * radius: + continue + var mi := SelectionMath.marker_mesh(marker) + if mi == null or mi.mesh == null: + continue + var mt := SelectionMath.ray_mesh_distance(mi.global_transform, + SelectionMath.mesh_faces(mi.mesh), origin, dir) + if mt >= 0.0 and mt < best_t: + best_t = mt + out = {"punkt": origin + dir * mt, "obj": int(marker.get_meta("obj_index", -1)), "dir": dir} + return out + + +## Feste Geschosshöhe: der Strahl trifft die waagerechte Ebene dieses Geschosses, +## egal ob darunter Wand, Boden oder nur Gelände ist (Decke mitten im Raum). +func _build_ray_geschoss(origin: Vector3, dir: Vector3, ground: Variant) -> Dictionary: + var xz := Vector2(origin.x, origin.z) + if ground != null: + xz = Vector2((ground as Vector3).x, (ground as Vector3).z) + var y := _bau_geschoss_hoehe(xz) + if absf(dir.y) < 0.001: + return {} + var t := (y - origin.y) / dir.y + if t <= 0.0: + return {} + return {"punkt": origin + dir * t, "obj": -1, "dir": dir, "ebene": y} + + +func _bau_gelaende_fn() -> Callable: + return func(x: float, z: float) -> float: + if _res >= 2 and _heights.size() >= _res * _res: + return TerrainField.height_at(_heights, _res, _sx, _sz, x, z) + return float(main.doc.get("terrain", {}).get("min_height_m", 0.0)) + + +func _bau_geschoss_hoehe(xz: Vector2) -> float: + var basis := BuildSnap.geschoss_basis(BuildSnap.sammle_teile(main.doc), xz, _bau_gelaende_fn()) + return basis + float(build_geschoss) * BauKatalog.WAND_H + + +## Bild↑/Bild↓ bzw. Knöpfe im Bauen-Tab: Geschoss wechseln (0 → zurück auf Automatik). +func bau_geschoss_aendern(delta: int) -> void: + var neu := build_geschoss + if delta > 0: + neu = 1 if build_geschoss < 0 else mini(build_geschoss + 1, BAU_GESCHOSS_MAX) + elif build_geschoss >= 0: + neu = build_geschoss - 1 # 0 → -1 (Automatik) + build_geschoss = neu + _build_hint = "" + if placing and not _build_hit.is_empty(): + _bau_ghost_neu() + main.call("_set_status", bau_geschoss_text()) + main.call("_refresh_bau_info") + + +func bau_geschoss_text() -> String: + if build_geschoss < 0: + return "Höhe: automatisch (Gelände bzw. Oberkante des Teils darunter). Bild↑ wählt ein festes Geschoss — dann sitzen Böden/Decken überall im Raum." + return "Höhe: festes Geschoss %d (%s). Bild↑/Bild↓ wechselt, bis „automatisch“ erreicht ist." % [ + build_geschoss, "Erdgeschoss-Boden" if build_geschoss == 0 else "%d × %d m über dem Boden" % [build_geschoss, int(BauKatalog.WAND_H)]] + + +func _bau_ghost_update(viewport_pos: Vector2) -> void: + _build_hit = _build_ray_hit(viewport_pos) + if _build_hit.is_empty(): + _ghost_active = false + _ghost.visible = false + _build_last = {} + return + _bau_ghost_neu() + + +## Snap neu berechnen (Mausbewegung ODER R-Taste) und den Ghost setzen. +func _bau_snap() -> Dictionary: + if _build_hit.is_empty(): + return {} + return BuildSnap.berechne(place_oid, _build_hit["punkt"] as Vector3, place_rot_deg, + BuildSnap.sammle_teile(main.doc), _bau_gelaende_fn(), + int(_build_hit.get("obj", -1)) >= 0, _build_hit.get("dir", Vector3.ZERO) as Vector3, + float(_build_hit.get("ebene", NAN))) + + +func _bau_ghost_neu() -> void: + _build_last = _bau_snap() + if _build_last.is_empty(): + return + var pos: Vector3 = _build_last["pos"] + var half := _terrain_half() + var innerhalb := absf(pos.x) <= half.x and absf(pos.z) <= half.y + _ghost.position = pos + _ghost.rotation_degrees = Vector3(0.0, float(_build_last["rot"]), 0.0) + _ghost_active = true + _ghost.visible = true + var mat := _ghost_mi.material_override as StandardMaterial3D + var hinweis := "" + if not bool(_build_last["ok"]) or not innerhalb: + mat.albedo_color = Color(1.0, 0.25, 0.2, 0.5) + hinweis = str(_build_last["grund"]) if not bool(_build_last["ok"]) else "Außerhalb der Map." + elif int(_build_last["ersetze"]) >= 0: + mat.albedo_color = Color(1.0, 0.7, 0.2, 0.55) + hinweis = "Klick ersetzt das vorhandene Teil durch %s (Strg+Z holt es zurück)." % main.object_label(place_oid) + else: + mat.albedo_color = Color(0.6, 0.9, 1.0, 0.45) + if hinweis != _build_hint: + _build_hint = hinweis + if not hinweis.is_empty(): + main.call("_set_status", hinweis) + + +## Klick im Bauen-Modus: Teil setzen/ersetzen — ein Undo-Schritt, Teil landet im +## laufenden Gebäude (Gruppe), Serie bleibt aktiv (Esc beendet). +func _place_bau(viewport_pos: Vector2) -> void: + _build_hit = _build_ray_hit(viewport_pos) + if _build_hit.is_empty(): + main.call("_set_status", "Da war nichts unter dem Mauszeiger — klicke IN die Karte (die grüne Fläche).", true) + return + var snap := _bau_snap() + if snap.is_empty(): + return + var pos: Vector3 = snap["pos"] + var half := _terrain_half() + if absf(pos.x) > half.x or absf(pos.z) > half.y: + main.call("_set_status", "Das wäre außerhalb der Map gelandet — klicke innerhalb der grünen Fläche.", true) + return + if not bool(snap["ok"]): + main.call("_set_status", str(snap["grund"]), true) + return + var objects: Array = main.doc["objects"] + var ersetzt := int(snap["ersetze"]) >= 0 + var lib: Dictionary = main.manifest.get("objects", {}) + var poly := float((lib.get(place_oid, {}) as Dictionary).get("poly", 0)) + var sym_mode: int = _sym_mode() + var sym_factor := 1.0 + if sym_mode != SymmetryCore.Mode.OFF and not SymmetryCore.maps_to_self( + sym_mode, pos, SymmetryCore.map_center(main.doc)): + sym_factor = 2.0 + if not ersetzt and objects.size() + int(sym_factor) > SvmapIO.MAX_OBJECTS: + main.call("_set_status", "Limit erreicht: In eine Map passen maximal %d Objekte — das Löschen von Objekten macht wieder Platz." % SvmapIO.MAX_OBJECTS, true) + return + var used := SvmapDocument.poly_usage(main.doc, main.manifest) + var budget := float(SvmapIO.MAX_TOTAL_POLY) + if used + poly * sym_factor > budget: + main.call("_set_status", + "Kein Platz mehr im Poly-Budget (%s von %s belegt) — lass ein Teil weg oder drück Strg+Z." + % [main._fmt_int(int(used)), main._fmt_int(int(budget))], true) + return + var keys := ["objects", "group_names"] + var before: Dictionary = main.history.snapshot(keys) + if not before.has("group_names"): + before["group_names"] = null # Undo entfernt die neu angelegten Gebäude-Namen wieder + var label: String = main.object_label(place_oid) + var gid := _bau_gruppe() + var spiegel := -1 + if sym_mode != SymmetryCore.Mode.OFF and sym_factor > 1.0: + spiegel = _bau_spiegel_gruppe(gid) + BauOps.setze_teil(main.doc, place_oid, snap, gid, sym_mode, spiegel) + main.history.commit("%s bauen" % label if not ersetzt else "%s einsetzen" % label, + before, main.history.snapshot(keys)) + main.call("_after_structure_change") + _build_hit = _build_ray_hit(viewport_pos) + _bau_ghost_neu() + main.call("_set_status", "%s %s (%s, %d Teile). Weiter klicken — R dreht, Esc beendet, Strg+Z nimmt das letzte Teil zurück." % [ + label, "eingesetzt" if ersetzt else "gesetzt", GroupState.display_name(main.doc, gid), + GroupOps.members_of(main.doc, gid).size()]) + main.call("_refresh_bau_info") + + +## Laufendes Gebäude (Gruppe): bestehende Gruppe weiterführen oder neue anlegen +## und benennen. Rückgabe: Gruppen-ID. +func _bau_gruppe() -> int: + if build_group_id >= 0 and not GroupOps.members_of(main.doc, build_group_id).is_empty(): + return build_group_id + build_group_id = BauOps.gruppe_neu(main.doc) + return build_group_id + + +## Gruppe für den Spiegel-Zwilling des laufenden Gebäudes (1 Gebäude ↔ 1 Spiegel). +var _bau_spiegel: Dictionary = {} # laufende ID → Spiegel-ID + +func _bau_spiegel_gruppe(gid: int) -> int: + var sp := int(_bau_spiegel.get(gid, -1)) + if sp >= 0 and not GroupOps.members_of(main.doc, sp).is_empty(): + return sp + sp = maxi(GroupOps.next_group_id(main.doc), gid + 1) + GroupState.set_group_name(main.doc, sp, "%s (Spiegel)" % GroupState.display_name(main.doc, gid)) + _bau_spiegel[gid] = sp + return sp + + +## Nächstes Teil beginnt ein NEUES Gebäude (Button „Neues Gebäude“). +func neues_gebaeude() -> void: + build_group_id = -1 + _bau_spiegel.clear() + + +## Demo-Hook: Ghost an einem Punkt parken (Screenshots ohne Maus). +func demo_park_bau(punkt: Vector3) -> void: + _build_hit = {"punkt": punkt, "obj": -1, "dir": Vector3.ZERO} + _bau_ghost_neu() + + +# --------------------------------------------------------------- Auswahl ---- + +## Einzel-Auswahl (Klick ins Objekt / Klick im Baum): ersetzt die Auswahl. +func select_object(idx: int, from_tree := false) -> void: + selected_indices = [idx] if idx >= 0 else [] + selected_index = idx + _apply_selection_view() + if not from_tree: + main.call("_mark_tree_selection", idx) + main.call("_selection_status", idx) + + +## Mehrere auf einmal (Rahmen-Ausziehen, Demo) — Reihenfolge bleibt erhalten. +func select_many(indices: Array, silent := false) -> void: + var objects: Array = main.doc.get("objects", []) + selected_indices = [] + for i in indices: + if i is int and i >= 0 and i < objects.size(): + selected_indices.append(i) + selected_index = selected_indices.back() if not selected_indices.is_empty() else -1 + _apply_selection_view() + if not silent: + main.call("_selection_status", selected_index) + + +## Umschalt+Klick: Objekt zur Auswahl HINZUFÜGEN (oder wieder rausnehmen). +func select_toggle(idx: int) -> void: + var objects: Array = main.doc.get("objects", []) + if idx < 0 or idx >= objects.size(): + return + var label: String = main.object_label(str(objects[idx].get("id", ""))) + if selected_indices.has(idx): + selected_indices.erase(idx) + if selected_index == idx: + selected_index = selected_indices.back() if not selected_indices.is_empty() else -1 + main.call("_set_status", "%s aus der Auswahl genommen — noch %d Objekte markiert. Umschalt-/Strg+Klick fügt wieder hinzu." % [label, selected_indices.size()]) + else: + selected_indices.append(idx) + selected_index = idx + main.call("_set_status", "%s zur Auswahl dazugeholt — jetzt %d Objekte. Mehr umschalten (Umschalt-/Strg+Klick), Rahmen aufziehen oder Strg+G zum Gruppieren." % [label, selected_indices.size()]) + _apply_selection_view() + main.call("_sync_selection_panel") + + +func clear_selection(silent := false) -> void: + if selected_index != -1 or not selected_indices.is_empty(): + selected_indices = [] + selected_index = -1 + view_builder.call("set_selected_multi", []) + if not silent: + main.call("_set_status", "Auswahl aufgehoben.") + main.call("_sync_selection_panel") + + +## Auswahl-Anzeige (Viewport-Färbung + Baum + Panel) aus dem aktuellen Stand. +func _apply_selection_view() -> void: + view_builder.call("set_selected_multi", selected_indices) + # Baum GANZ synchron (Karte t_c46d31c2): auch Abwählen per Strg+Klick im + # Viewport entfernt die Baum-Markierung wieder. + main.call("_mark_tree_selection_multi", selected_indices) + if selected_index >= 0: + main.call("_mark_tree_selection", selected_index) + + +## Ausgewählte Objekte zu EINER Gruppe machen (main-Button/Strg+G). +func group_selection() -> void: + var objects: Array = main.doc.get("objects", []) + var valid: Array = [] + for i in selected_indices: + if i is int and i >= 0 and i < objects.size(): + valid.append(i) + if valid.size() < 2: + main.call("_set_status", "Eine Gruppe braucht mindestens 2 Objekte — markiere zuerst mehrere (Umschalt+Klick oder Rahmen aufziehen).", true) + return + var before: Dictionary = main.history.snapshot(["objects"]) + var gid := GroupOps.group(main.doc, valid) + main.history.commit("Auswahl gruppieren (%d Objekte → Gruppe %d)" % [valid.size(), gid + 1], + before, main.history.snapshot(["objects"])) + _mark_dirty() + main.call("_refresh_history_buttons") + _apply_selection_view() + main.call("_sync_selection_panel") + main.call("_set_status", "Gruppe %d gebaut: %d Objekte ziehen und drehen jetzt GEMEINSAM. „Gruppe lösen“ (Strg+Umschalt+G) macht es rückgängig — die Objekte bleiben, nur die Bindung löst sich." % [gid + 1, valid.size()]) + + +## Gruppe(n) der Auswahl komplett lösen (main-Button/Strg+Umschalt+G). +func ungroup_selection() -> void: + var groups: Array = GroupOps.groups_of(main.doc, selected_indices) + if groups.is_empty(): + main.call("_set_status", "In der Auswahl steckt keine Gruppe — markiere zuerst ein Objekt aus einer Gruppe.", true) + return + var before: Dictionary = main.history.snapshot(["objects"]) + var freed := GroupOps.ungroup(main.doc, selected_indices) + main.history.commit("Gruppe gelöst (%d Objekte)" % freed, + before, main.history.snapshot(["objects"])) + _mark_dirty() + main.call("_refresh_history_buttons") + _apply_selection_view() + main.call("_sync_selection_panel") + main.call("_set_status", "Gruppe gelöst: %d Objekte sind wieder einzeln verschiebbar — Strg+Z baut die Gruppe wieder zusammen." % freed) + + +# ---------------------------------------------------- Drehen / Löschen ------ + +## Auswahl drehen (R / Mausrad / Button): bei mehreren Objekten oder einer +## Gruppe rotiert ALLES gemeinsam um den Gruppen-Mittelpunkt. Mausrad-Ticks +## derselben Auswahl laufen zu EINEM Undo-Schritt zusammen (merge_tag). +func rotate_selected(delta_deg: float, merge_suffix: String = "") -> void: + if selected_index < 0 or selected_indices.is_empty(): + return + var objects: Array = main.doc.get("objects", []) + if selected_index >= objects.size(): + return + var members := selected_indices.duplicate() + # Einzelnes Objekt, das in einer Gruppe steckt → die GANZE Gruppe drehen + # (Sly-Erwartung: die Gruppe dreht gemeinsam) und sichtbar markieren. + if members.size() == 1: + var gid := GroupOps.group_id(objects[members[0]]) + if gid != GroupOps.UNGROUPED: + members = GroupOps.members_of(main.doc, gid) + select_many(members, true) + main.call("_sync_selection_panel") + var before: Dictionary = main.history.snapshot(["objects"]) + GroupOps.rotate_members(main.doc, members, delta_deg) + _sync_members_to_markers(members) + # Symmetrie (M3, t_34908425): Zwillinge der gedrehten Auswahl auf den exakten + # Spiegel setzen (Punkt: +180°-Basis, Achse: Vorzeichen) — IM SELBEN Commit. + var twins: Array = ObjectOps.symmetry_sync_pairs( + main.doc, members, _sym_mode(), selected_index) + _sync_members_to_markers(twins) + var tag := "rotmulti:%s%s" % [str(members), merge_suffix] + main.history.commit("Auswahl gedreht (%d Objekte)" % members.size(), + before, main.history.snapshot(["objects"]), tag) + _mark_dirty() + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + main.call("_sync_selection_panel") + + +## Auswahl KIPPEN (Karte t_e166e656 — Sly: „nicht nur rotieren, sondern auch +## kippen in sämtliche Achsen“). axis_x = Kippen um die X-Achse (vor/zurück +## neigen), sonst um die Z-Achse (links/rechts neigen). Ein Einzelobjekt in +## einer Gruppe kippt die GANZE Gruppe (wie beim Drehen), Mehrfachauswahl +## kippt jedes Objekt um denselben Betrag, Symmetrie-Zwillinge laufen im +## SELBEN Commit mit. rot/rot_y_deg bleiben über SvmapIO.rot_setzen synchron. +func tilt_selected(axis_x: bool, delta_deg: float, merge_suffix: String = "") -> void: + if selected_index < 0 or selected_indices.is_empty(): + return + var objects: Array = main.doc.get("objects", []) + if selected_index >= objects.size(): + return + var members := selected_indices.duplicate() + if members.size() == 1: + var gid := GroupOps.group_id(objects[members[0]]) + if gid != GroupOps.UNGROUPED: + members = GroupOps.members_of(main.doc, gid) + select_many(members, true) + main.call("_sync_selection_panel") + var before: Dictionary = main.history.snapshot(["objects"]) + var axis := "X" if axis_x else "Z" + for i in members: + var obj: Dictionary = objects[i] + var euler := SvmapIO.objekt_rotation(obj) + var neu := Vector3(euler.x, euler.y, euler.z) + if axis_x: + neu.x = fposmod(euler.x + delta_deg + 3600.0, 360.0) + else: + neu.z = fposmod(euler.z + delta_deg + 3600.0, 360.0) + SvmapIO.rot_setzen(obj, neu) + _sync_members_to_markers(members) + var twins: Array = ObjectOps.symmetry_sync_pairs( + main.doc, members, _sym_mode(), selected_index) + _sync_members_to_markers(twins) + var tag := ("tilt:%d" % selected_index if members.size() == 1 \ + else "tiltmulti:%s" % str(members)) + merge_suffix + main.history.commit("Auswahl gekippt (%s, %d Objekte)" % [axis, members.size()], + before, main.history.snapshot(["objects"]), tag) + _mark_dirty() + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + main.call("_sync_selection_panel") + main.call("_set_status", "Auswahl um %s%s gekippt — Inspektor zeigt die Werte; Strg+Z nimmt den Schritt zurück." % [ + ("+%.0f°" % delta_deg) if delta_deg > 0.0 else ("%.0f°" % delta_deg), axis]) + + +## Nach Doc-Änderung die betroffenen Marker positionieren/drehen (kein Rebuild). +func _sync_members_to_markers(members: Array) -> void: + var objects: Array = main.doc.get("objects", []) + for i in members: + if i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker == null or not is_instance_valid(marker): + continue + var pos: Array = (objects[i] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + marker.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + # Karte t_e166e656: volle 3-Achsen-Rotation (rot-Feld oder Fallback rot_y_deg). + marker.rotation_degrees = SvmapIO.objekt_rotation(objects[i] as Dictionary) + + +func delete_selected() -> void: + if selected_indices.is_empty() or selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — klicke zuerst ein Objekt an, das gelöscht werden soll.", true) + return + var objects: Array = main.doc.get("objects", []) + var doomed: Array = [] + for i in selected_indices: + if i is int and i >= 0 and i < objects.size(): + doomed.append(i) + if doomed.is_empty(): + clear_selection(true) + return + # Symmetrie (M3, t_34908425): die Partner der Auswahl löschen sich MIT — + # ein Paar lebt oder stirbt zusammen, IM SELBEN Undo-Schritt. + doomed = ObjectOps.symmetry_expand_delete(main.doc, doomed, _sym_mode()) + var before: Dictionary = main.history.snapshot(["objects", "group_names"]) + if not before.has("group_names"): + before["group_names"] = null + var label: String + if doomed.size() == 1: + label = "%s löschen" % main.object_label(str(objects[doomed[0]].get("id", ""))) + else: + label = "%d Objekte löschen" % doomed.size() + # Rückwärts löschen, damit die Indizes währenddessen stimmen. + doomed.sort() + doomed.reverse() + for i in doomed: + objects.remove_at(i) + GroupState.prune_orphans(main.doc) # Gebäude ohne Teile verlieren ihren Namen + main.history.commit(label, before, main.history.snapshot(["objects", "group_names"])) + clear_selection(true) + main.call("_after_structure_change") + main.call("_set_status", "%s ist erledigt — Strg+Z (Rückgängig) holt %s zurück." % [ + label, "alles" if doomed.size() > 1 else "es"]) + + +# ------------------------------------------ Duplizieren/Höhe/Spiegeln ------ +## (Karte t_7567a06f — alle drei auch bei Sly bedienbar: Button rechts + +## Tastenkürzel, alles über ActionHistory, Klartext-Status-Hilfe.) + +## Strg+D / Button "Duplizieren" (Karte t_c46d31c2): JEDE Kopie rastet +2 m in +## X versetzt ein (Füßpunkt-Höhe unverändert, mit Raster-Snap eingerastet, wenn +## an) — bei Mehrfachauswahl werden ALLE Objekte kopiert, EIN Undo-Schritt für +## alle Kopien, die Kopien sind direkt als neue Auswahl angewählt. Poly-Budget +## und Objekt-Limit werden VOR dem Kopieren gesammelt geprüft. +func duplicate_selected() -> void: + var objects: Array = main.doc.get("objects", []) + var sources: Array = [] + for i in selected_indices: + if i is int and i >= 0 and i < objects.size(): + sources.append(i) + if sources.is_empty(): + main.call("_set_status", "Nichts ausgewählt — klicke zuerst ein Objekt an, das du kopieren willst.", true) + return + if objects.size() + sources.size() > SvmapIO.MAX_OBJECTS \ + and _sym_mode() == SymmetryCore.Mode.OFF: + main.call("_set_status", "Limit erreicht: In eine Map passen maximal %d Objekte — das Löschen von Objekten macht wieder Platz." % SvmapIO.MAX_OBJECTS, true) + return + var lib: Dictionary = main.manifest.get("objects", {}) + var sym_mode: int = _sym_mode() + var sym_center := SymmetryCore.map_center(main.doc) + var twins_planned := 0 + var poly_sum := 0.0 + for i in sources: + var src_obj: Dictionary = objects[i] + var scl := float(src_obj.get("scale", 1.0)) + var entry: Dictionary = lib.get(str(src_obj.get("id", "")), {}) + poly_sum += float(entry.get("poly", 0)) * scl * scl + # Symmetrie (M3, t_34908425): JEDE Kopie bekommt einen Zwilling (außer + # auf der Mitte/Achse) — Budget und Objekt-Limit VORAB mitrechnen. + if sym_mode != SymmetryCore.Mode.OFF: + var spos: Array = src_obj.get("pos", [0.0, 0.0, 0.0]) + if (spos as Array).size() == 3 and not SymmetryCore.maps_to_self( + sym_mode, Vector3(float(spos[0]), float(spos[1]), float(spos[2])), sym_center): + twins_planned += 1 + poly_sum += float(entry.get("poly", 0)) * scl * scl + if twins_planned > 0 and objects.size() + sources.size() + twins_planned > SvmapIO.MAX_OBJECTS: + main.call("_set_status", "Limit erreicht: In eine Map passen maximal %d Objekte — mit Symmetrie-Zwilllingen bräuchte es %d. Das Löschen von Objekten macht wieder Platz." % [SvmapIO.MAX_OBJECTS, objects.size() + sources.size() + twins_planned], true) + return + var used := SvmapDocument.poly_usage(main.doc, main.manifest) + var budget := float(SvmapIO.MAX_TOTAL_POLY) + if used + poly_sum > budget: + main.call("_set_status", "Für %s fehlt das Poly-Budget (%s von %s belegt) — Strg+Z nimmt etwas zurück, oder lösche ein anderes Objekt." % [ + "die Kopien" if sources.size() > 1 else "eine Kopie", + main._fmt_int(int(used)), main._fmt_int(int(budget))], true) + return + var before: Dictionary = main.history.snapshot(["objects", "group_names"]) + if not before.has("group_names"): + before["group_names"] = null # Undo entfernt die Namen der Kopie-Gruppen wieder + # Gebäude (Bauteile): Kopie rückt um die Gebäudebreite weg statt 2 m — sonst + # läge sie halb im Original. + var offset_x := ObjectOps.DUPLICATE_OFFSET_M.x + var bau_min := INF + var bau_max := -INF + for i in sources: + var so: Dictionary = objects[i] + if BauKatalog.ist_bau(str(so.get("id", ""))): + var spx := float((so.get("pos", [0.0, 0.0, 0.0]) as Array)[0]) + bau_min = minf(bau_min, spx) + bau_max = maxf(bau_max, spx) + if bau_min < INF: + offset_x = maxf(offset_x, ceilf((bau_max - bau_min + 4.0) / 2.0) * 2.0) + var new_indices: Array = ObjectOps.duplicate_objects(main.doc, sources, bool(main.get("grid_snap")), offset_x) + if new_indices.is_empty(): + clear_selection(true) + return + # Symmetrie (M3, t_34908425): jede Kopie bekommt ihren Zwilling (exakter + # Spiegel der KOPIE, neue sym-Kennung) — im selben Commit wie die Kopien. + var sym_twins: Array = [] + if sym_mode != SymmetryCore.Mode.OFF: + for c in new_indices: + if c is int: + var sres: Dictionary = ObjectOps.symmetry_place_twin(main.doc, c, sym_mode) + if int(sres.get("twin", -1)) >= 0: + sym_twins.append(int(sres["twin"])) + var label: String + if new_indices.size() == 1: + label = "%s duplizieren" % main.object_label(str((objects[sources[0]] as Dictionary).get("id", ""))) + else: + label = "%d Objekte duplizieren" % new_indices.size() + main.history.commit(label, before, main.history.snapshot(["objects", "group_names"])) + main.call("_after_structure_change") + # Kopien direkt anwählen — Sly kann sie gleich weiterziehen. Die frischen + # Zwillinge wandern mit in die Auswahl (beide Seiten sichtbar markiert). + var selected_new: Array = new_indices.duplicate() + selected_new.append_array(sym_twins) + select_many(selected_new) + if sym_twins.is_empty(): + main.call("_set_status", "%s: die Kopie(n) liegen +2 m versetzt und sind direkt angewählt — Ziehen verschiebt, Entf löscht, Strg+Z nimmt ALLE Kopien auf einmal zurück." % label) + else: + main.call("_set_status", "%s: %d Kopie(n) + %d Zwilling(e) sind direkt angewählt — jede Kopie bildet mit ihrem Zwilling ein neues Paar. Strg+Z nimmt ALLE auf einmal zurück." % [label, new_indices.size(), sym_twins.size()]) + + +## ▲/▼ / Strg+Bild↑/↓: AUSWAHL in 0,25-m-Schritten anheben/absenken +## (Karte t_55257792: die GANZE Auswahl, wie Entf/Strg+D auch). +## Mehrere Klicks in Serie = EIN Undo-Schritt (Merge-Tag, wie beim Drehen). +## Absenken stoppt am Terrain (nichts wird begraben), Anheben an der +## Match-Grenze (bounds.max) — Regeln in ObjectOps.lift_object. +func lift_selected(delta_m: float) -> void: + if selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — klicke zuerst ein Objekt an, das du anheben oder absenken willst.", true) + return + if placing: + return + var objects: Array = main.doc.get("objects", []) + if selected_index >= objects.size(): + clear_selection(true) + return + var members := _lift_members() + var before: Dictionary = main.history.snapshot(["objects"]) + var applied_sum := 0.0 + for i in members: + applied_sum += ObjectOps.lift_object(main.doc, i, delta_m) + if absf(applied_sum) < 0.0001: + # Nichts passiert (Snapshot verwerfen): hilfsbereit erklären, WO die Grenze ist. + var ground: String + if delta_m > 0.0: + ground = "Die Match-Höhe ist erreicht — höher passt der Server nicht mehr." + else: + ground = "Die untere Match-Grenze ist erreicht — tiefer passt der Server nicht mehr (Versenken im Boden ist erlaubt, Karte t_e166e656)." + main.call("_set_status", ground) + return + var after: Dictionary = main.history.snapshot(["objects"]) + var tag := "lift:%d" % selected_index if members.size() == 1 \ + else "liftmulti:%s" % str(members) + main.history.commit(_lift_label(members, delta_m, applied_sum), + before, after, tag) + for i in members: + if i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + marker.position.y = float((objects[i]["pos"] as Array)[1]) + main.dirty = true + main.call("_refresh_file_label") + main.call("_refresh_stats") + main.call("_refresh_history_buttons") + main.call("_sync_selection_panel") + # Karte t_e166e656: Versenk-Tiefe sichtbar machen (Stein halb im Boden). + var sel_pos: Array = (objects[selected_index]["pos"] as Array) + var boden := SvmapDocument.terrain_height_at(main.doc, float(sel_pos[0]), float(sel_pos[2])) + var tiefe := boden - float(sel_pos[1]) + var versenk := "" + if tiefe > 0.01: + versenk = " (%.2f m versenkt)" % tiefe + main.call("_set_status", "%s auf %.2f m%s — mehrmals drücken stapelt weiter; Strg+Z nimmt alle Stufen auf einmal zurück." % [ + _lift_label(members, delta_m, applied_sum), float(sel_pos[1]), versenk]) + + +## Heben/Absenken wirkt auf die GANZE Auswahl (Karte t_55257792) — ein einzelnes +## Objekt, das in einer Gruppe steckt, zieht die ganze Gruppe mit (wie Drehen). +func _lift_members() -> Array: + var members := selected_indices.duplicate() + if members.size() == 1: + var objects: Array = main.doc.get("objects", []) + var gid := GroupOps.group_id(objects[members[0]]) + if gid != GroupOps.UNGROUPED: + members = GroupOps.members_of(main.doc, gid) + select_many(members, true) + main.call("_sync_selection_panel") + return members + + +func _lift_label(members: Array, delta_m: float, applied_sum: float) -> String: + var dir := "angehoben" if delta_m > 0.0 else "abgesenkt" + if members.size() == 1: + return "Objekt %s (%s)" % [dir, "%.2f m" % applied_sum] + return "%d Objekte %s (%.2f m)" % [members.size(), dir, applied_sum] + + +## Strg+Bild↑ (Karte t_55257792): die AUSWAHL direkt auf das Terrain/Boden +## absetzen (Godot "Snap Object to Floor"-Äquivalent fürs Fußpunkt-Modell). +## EIN Undo-Schritt; steht alles schon am Boden → Meldung statt leerem Commit. +func snap_selection_to_floor() -> void: + if selected_indices.is_empty() or selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — Strg+Bild↑ setzt die Auswahl auf den Boden. Klicke zuerst ein Objekt an.", true) + return + if placing: + return + var members := _lift_members() + var before: Dictionary = main.history.snapshot(["objects"]) + var changed := ObjectOps.snap_selection_to_floor(main.doc, members) + if changed == 0: + main.call("_set_status", "Steht schon alles auf dem Boden — nichts zu setzen.") + return + main.history.commit("Auswahl auf Boden gesetzt (%d Objekte)" % changed, + before, main.history.snapshot(["objects"])) + for i in members: + if i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + var pos: Array = ((main.doc.get("objects", []) as Array)[i] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + marker.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + _mark_dirty() + main.call("_refresh_stats") + main.call("_refresh_history_buttons") + main.call("_sync_selection_panel") + main.call("_set_status", "%d Objekt(e) auf den Boden gesetzt — Strg+Z nimmt es zurück." % changed) + + +## Pfeiltasten (Karte t_55257792): Auswahl im 2-m-Raster verschieben — EINE +## Zelle pro Tastendruck, Richtung = Kamera-Ausrichtung nächste Weltachse +## (MoveMath). Fußpunkt bleibt am Raster, Höhe wandert mit (t_7567a06f). +## EIN Undo-Schritt pro Tastendruck. +func nudge_selection(delta: Vector3) -> void: + if selected_indices.is_empty() or selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — Pfeiltasten verschieben die Auswahl im 2-m-Raster. Klicke zuerst ein Objekt an.") + return + if placing: + return + var members := selected_indices.duplicate() + var objects: Array = main.doc.get("objects", []) + if members.size() == 1: + var gid := GroupOps.group_id(objects[members[0]]) + if gid != GroupOps.UNGROUPED: + members = GroupOps.members_of(main.doc, gid) + var before: Dictionary = main.history.snapshot(["objects"]) + var changed := ObjectOps.nudge_objects(main.doc, members, delta) + if changed == 0: + return # am Map-Rand geklemmt — Snapshot verwerfen, kein Commit + _sync_members_to_markers(members) + # Symmetrie (M3, t_34908425): Zwillinge mitführen — exakter Spiegel der + # verschobenen Quelle, im selben Commit (Pfeiltasten = 1 Undo-Schritt). + var twins: Array = ObjectOps.symmetry_sync_pairs( + main.doc, members, _sym_mode()) + _sync_members_to_markers(twins) + var desc := "Objekt verschoben (Pfeiltaste)" if members.size() == 1 \ + else "%d Objekte verschoben (Pfeiltasten)" % members.size() + main.history.commit(desc, before, main.history.snapshot(["objects"])) + _mark_dirty() + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + main.call("_sync_selection_panel") + main.call("_set_status", "%s — 2 m pro Tastendruck. Greifen (G) setzt frei, Strg+Bild↑ setzt auf den Boden." % desc) + + +# ------------------------------------------------------------ Greifen (G) ---- +## Blender-Semantik (Karte t_55257792): G hängt die Auswahl an die Maus, +## Linksklick setzt die neue Position (EIN Undo-Schritt, Raster-Snap wie +## eingestellt bzw. Strg-Zwang), Esc bricht ab und setzt die EXAKTEN alten +## Positionen zurück. Die Auswahl wandert GEMEINSAM (Gruppe/Mehrfachauswahl), +## relative Anordnung bleibt exakt erhalten, Terrain-Abstand je Objekt bleibt. + +## Greifen starten. false = nichts ausgewählt (Meldung steht dann). +func grab_begin() -> bool: + if _grabbing: + return true + if placing: + return false + var objects: Array = main.doc.get("objects", []) + var members := selected_indices.duplicate() + if members.size() == 1: + var gid := GroupOps.group_id(objects[members[0]]) + if gid != GroupOps.UNGROUPED: + members = GroupOps.members_of(main.doc, gid) + select_many(members, true) + main.call("_sync_selection_panel") + if members.is_empty() or selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — G greift die Auswahl, damit du sie mit der Maus frei setzen kannst. Klicke zuerst ein Objekt an.") + return false + _grab_members = members + _grab_primary = selected_index + _grab_originals = {} + _grab_lifts = {} + for i in members: + var pos: Array = (objects[i] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + _grab_originals[i] = [float(pos[0]), float(pos[1]), float(pos[2])] + _grab_lifts[i] = float(pos[1]) - SvmapDocument.terrain_height_at( + main.doc, float(pos[0]), float(pos[2])) + # Symmetrie (M3, t_34908425): Zwillinge der Greif-Menge merken — sie ziehen + # live mit (exakter Spiegel) und Esc setzt sie EXAKT zurück. + _grab_twin_map = {} + _grab_twin_originals = {} + if _sym_mode() != SymmetryCore.Mode.OFF: + for i in members: + if not (i is int) or i < 0 or i >= objects.size() or not (objects[i] is Dictionary): + continue + var sym_key := SymmetryCore.sym_of(objects[i] as Dictionary) + if sym_key.is_empty(): + continue + var t := SymmetryCore.find_partner_index(objects, sym_key, i) + if t < 0: + continue + _grab_twin_map[i] = t + if not members.has(t) and not _grab_twin_originals.has(t): + var tpos: Array = (objects[t] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + if (tpos as Array).size() == 3: + _grab_twin_originals[t] = [float(tpos[0]), float(tpos[1]), float(tpos[2])] + _grabbing = true + _grab_before = main.history.snapshot(["objects"]) + main.call("_set_status", "GREIFEN an: die Auswahl hängt an der Maus — Linksklick setzt sie neu (mit Raster-Snap), Esc bricht ab und setzt zurück.") + return true + + +## Auswahl live zur Cursor-Bodenposition ziehen (Maus-Bewegung ODER Demo-Hook +## mit Weltpunkt). Der Primär-Anker geht EXAKT zum Punkt (rastet, wenn Snap +## an bzw. Strg), die Mitglieder wandern mit demselben Delta mit. +func grab_move(hit: Vector3) -> void: + if not _grabbing: + return + var target := _apply_grid(hit) + var half := _terrain_half() + var primary_pos: Array = _grab_originals[_grab_primary] + var delta := Vector3(target.x - float(primary_pos[0]), 0.0, + target.z - float(primary_pos[2])) + var objects: Array = main.doc.get("objects", []) + for i in _grab_members: + var orig: Array = _grab_originals[i] + var nx := clampf(float(orig[0]) + delta.x, -half.x, half.x) + var nz := clampf(float(orig[2]) + delta.z, -half.y, half.y) + var ny := SvmapDocument.terrain_height_at(main.doc, nx, nz) + float(_grab_lifts[i]) + (objects[i] as Dictionary)["pos"] = [nx, ny, nz] + if i < view_builder.object_markers.size(): + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + marker.position = Vector3(nx, ny, nz) + # Symmetrie (M3, t_34908425): Zwillinge live mitziehen — doc + Marker, + # exakter Spiegel der gegriffenen Quelle. + _sym_grab_twin_move() + + +## Linksklick im Greifen: Positionen festschreiben — EIN Undo-Schritt. +func grab_confirm() -> void: + if not _grabbing: + return + _grabbing = false + var desc := "Objekt verschoben (greifen)" if _grab_members.size() == 1 \ + else "%d Objekte verschoben (greifen)" % _grab_members.size() + main.history.commit(desc, _grab_before, main.history.snapshot(["objects"])) + _grab_members = [] + _grab_originals = {} + _grab_lifts = {} + _grab_before = {} + _grab_twin_map = {} + _grab_twin_originals = {} + _mark_dirty() + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + main.call("_sync_selection_panel") + main.call("_set_status", "%s — Strg+Z nimmt den ganzen Zug zurück. G greift erneut, Esc hebt immer ab." % desc) + + +## Esc im Greifen: EXAKTE Rückabwicklung auf die Positionen von grab_begin — +## byte-identisch (keine Float-Re-Rechnung), KEIN History-Eintrag. +func grab_cancel() -> void: + if not _grabbing: + return + _grabbing = false + var objects: Array = main.doc.get("objects", []) + for i in _grab_members: + var orig: Array = _grab_originals[i] + (objects[i] as Dictionary)["pos"] = [float(orig[0]), float(orig[1]), float(orig[2])] + if i < view_builder.object_markers.size(): + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + marker.position = Vector3(float(orig[0]), float(orig[1]), float(orig[2])) + # Symmetrie (M3, t_34908425): die MITgezogenen Zwillinge exakt zurücksetzen. + for t in _grab_twin_originals: + var torig: Array = _grab_twin_originals[t] + (objects[t] as Dictionary)["pos"] = [float(torig[0]), float(torig[1]), float(torig[2])] + if t < view_builder.object_markers.size(): + var tmarker: Node3D = view_builder.object_markers[t] + if tmarker != null and is_instance_valid(tmarker): + tmarker.position = Vector3(float(torig[0]), float(torig[1]), float(torig[2])) + _grab_members = [] + _grab_originals = {} + _grab_lifts = {} + _grab_before = {} + _grab_twin_map = {} + _grab_twin_originals = {} + main.call("_set_status", "Greifen abgebrochen — alles ist wieder, wie es war.") + + +## Symmetrie (M3): Zwillinge beim GREIFEN live mitziehen — doc + Marker, +## exakter Spiegel der Quelle. Gehören BEIDE Seiten eines Paares zur Greif- +## Menge, ist die Quelle das Primär-Objekt (sonst der niedrigere Index). +func _sym_grab_twin_move() -> void: + var sym_mode: int = _sym_mode() + if sym_mode == SymmetryCore.Mode.OFF or _grab_twin_map.is_empty(): + return + var objects: Array = main.doc.get("objects", []) + var center := SymmetryCore.map_center(main.doc) + var handled: Dictionary = {} + for i in _grab_members: + if not _grab_twin_map.has(i): + continue + var t: int = int(_grab_twin_map[i]) + var pair_key := "%d|%d" % [mini(i, t), maxi(i, t)] + if handled.has(pair_key): + continue + handled[pair_key] = true + var src_i: int = i + if _grab_members.has(t): + if _grab_primary == t: + src_i = t + elif _grab_primary != i and t < i: + src_i = t + var twin_i: int = t if src_i == i else i + if src_i >= objects.size() or not (objects[src_i] is Dictionary): + continue + var spos: Array = (objects[src_i] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + if (spos as Array).size() != 3: + continue + var mp := SymmetryCore.mirror_point(sym_mode, + Vector3(float(spos[0]), float(spos[1]), float(spos[2])), center) + (objects[twin_i] as Dictionary)["pos"] = [mp.x, mp.y, mp.z] + if twin_i < view_builder.object_markers.size(): + var tm: Node3D = view_builder.object_markers[twin_i] + if tm != null and is_instance_valid(tm): + tm.position = Vector3(mp.x, mp.y, mp.z) + + +## Symmetrie (M3): Zwillinge beim ZIEHEN live mitführen (nur Marker — das doc +## schreibt _commit_drag über symmetry_sync_pairs). Quelle = Primär-Objekt, +## wenn beide Seiten eines Paares in der Zieh-Menge sind, sonst niedrigerer Index. +func _sym_drag_sync_live() -> void: + var sym_mode: int = _sym_mode() + if sym_mode == SymmetryCore.Mode.OFF or view_builder == null: + return + var objects: Array = main.doc.get("objects", []) + var center := SymmetryCore.map_center(main.doc) + var handled: Dictionary = {} + for i in _drag_members: + if not (i is int) or i < 0 or i >= objects.size() or not (objects[i] is Dictionary): + continue + var key := SymmetryCore.sym_of(objects[i] as Dictionary) + if key.is_empty(): + continue + var t := SymmetryCore.find_partner_index(objects, key, i) + if t < 0: + continue + var pair_key := "%d|%d" % [mini(i, t), maxi(i, t)] + if handled.has(pair_key): + continue + handled[pair_key] = true + var src_i: int = i + if _drag_members.has(t): + if _drag_primary == t: + src_i = t + elif _drag_primary != i and t < i: + src_i = t + var twin_i: int = t if src_i == i else i + if src_i >= view_builder.object_markers.size() or twin_i >= view_builder.object_markers.size(): + continue + var sm: Node3D = view_builder.object_markers[src_i] + var tm: Node3D = view_builder.object_markers[twin_i] + if sm == null or tm == null or not is_instance_valid(sm) or not is_instance_valid(tm): + continue + tm.position = SymmetryCore.mirror_point(sym_mode, sm.position, center) + + + + +## ⇄ Spiegeln (X: links↔rechts, Z: vor↔zurück) an der Stelle des Objekts — +## die Blickrichtung wird gespiegelt (sicher selbstinvers: doppelt = zurück). +func mirror_selected(axis: String) -> void: + if selected_index < 0: + main.call("_set_status", "Nichts ausgewählt — klicke zuerst ein Objekt an, das du spiegeln willst.", true) + return + var objects: Array = main.doc.get("objects", []) + if selected_index >= objects.size(): + clear_selection(true) + return + var before: Dictionary = main.history.snapshot(["objects"]) + var obj: Dictionary = objects[selected_index] + var old_rot := float(obj.get("rot_y_deg", 0.0)) + ObjectOps.mirror_object(obj, axis) + var new_rot := float(obj.get("rot_y_deg", 0.0)) + var marker: Node3D = view_builder.object_markers[selected_index] if selected_index < view_builder.object_markers.size() else null + if marker != null and is_instance_valid(marker): + marker.rotation.y = deg_to_rad(new_rot) + # Symmetrie (M3, t_34908425): der Zwilling dreht MIT (exakter Spiegel der + # neuen Blickrichtung) — im selben Commit wie die Quelle. + var twins: Array = ObjectOps.symmetry_sync_pairs( + main.doc, [selected_index], _sym_mode(), selected_index) + _sync_members_to_markers(twins) + var desc := "Objekt gespiegelt (links↔rechts)" if axis == "x" else "Objekt gespiegelt (vor↔zurück)" + main.history.commit(desc, before, main.history.snapshot(["objects"])) + main.dirty = true + main.call("_refresh_file_label") + main.call("_refresh_history_buttons") + main.call("_sync_selection_panel") + main.call("_set_status", "%s — Blickrichtung %.0f° → %.0f°. Strg+Z stellt die alte Richtung wieder her." % [ + desc, old_rot, new_rot]) + + +# ------------------------------------------------------------ Eingaben ------ + +func _unhandled_input(event: InputEvent) -> void: + # Strg-Zwang (Karte t_55257792) aus JEDEM durchgelassenen Event verfolgen — + # Maus-Events tragen den Modifier, Tasten-Events ebenso (nicht konsumieren!). + if event is InputEventKey: + _ctrl_held = (event as InputEventKey).ctrl_pressed + var p := MousePointer.from_input(event) + if p != null and handle_pointer(p): + get_viewport().set_input_as_handled() + + +## Rechte Maustaste kommt hier nie an (Karte t_2470de89): sie gehört mit +## absolutem Vorrang dem Freelook-Flug (orbit_camera.gd). Beim Platzieren +## bleibt das Objekt IN der Hand, Abwurf nur über Esc. +func handle_pointer(p: PointerEvent) -> bool: + if p.kind != PointerEvent.Kind.CANCEL: + _ctrl_held = p.ctrl + var mode: String = main.get("tool_mode") + if mode == "terrain" or mode == "spawns": + return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter (eigene Werkzeuge) + match p.kind: + PointerEvent.Kind.MOVE: + _on_pointer_move(p.position) + PointerEvent.Kind.PRESS: + if _grabbing: + grab_confirm() + return true + _on_left_press(p) + return true + PointerEvent.Kind.RELEASE: + _on_left_release(p) + PointerEvent.Kind.SCROLL: + if _camera_flying(): + return false # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen + if placing: + place_rot_deg = fmod(place_rot_deg + p.scroll * (90.0 if BauKatalog.ist_bau(place_oid) else ROT_STEP_DEG) + 3600.0, 360.0) + _ghost_rotation_anwenden() + return true + if selected_index >= 0 and not _grabbing: + rotate_selected(p.scroll * ROT_STEP_DEG) + return true + return false + + +func _on_pointer_move(pos: Vector2) -> void: + if placing: + _ghost_update(pos) + if _grabbing: + var gh: Variant = _ground_point(pos) + if gh != null: + grab_move(gh as Vector3) + return + if _rubber_pending or _rubber_active: + var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length() + if dist > DRAG_THRESHOLD_PX: + _rubber_active = true + _rubber_pending = false + if _rubber_active: + main.call("_rubberband_update", _rubber_start, pos) + return + if _dragging and not placing and not _drag_members.is_empty(): + _drag_members_move(pos) + elif _dragging and not placing and selected_index >= 0: + # Einzel-Greifen (alter Pfad, ohne Gruppe/Mehrfachauswahl) + var p: Variant = _ground_point(pos) + if p == null: + return + var new_pos: Vector3 = _apply_grid((p as Vector3) + _grab_offset) + var half := _terrain_half() + new_pos.x = clampf(new_pos.x, -half.x, half.x) + new_pos.z = clampf(new_pos.z, -half.y, half.y) + # Höhe: der Terrain-Abstand bleibt beim Ziehen erhalten (angehoben + # schwebt weiter oben) — kein harter Terrain-Clamp mehr, t_7567a06f. + new_pos.y = SvmapDocument.terrain_height_at(main.doc, new_pos.x, new_pos.z) + _drag_lift + var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length() + if dist > DRAG_THRESHOLD_PX: + _drag_moved = true + if _drag_moved: + var marker: Node3D = view_builder.object_markers[selected_index] + if marker != null and is_instance_valid(marker): + marker.position = new_pos + + +func _on_left_press(p: PointerEvent) -> void: + if placing: + _place_object(p.position, p.shift) # U5: Shift = Serienmodus + return + _press_pos = p.position + var hit := _pick_object(p.position) + if hit >= 0: + if p.shift or p.command: + # Umschalt+Klick / Strg+Klick (Karte t_c46d31c2): zur Auswahl + # hinzufügen/entfernen — kein Ziehen. + select_toggle(hit) + return + if not selected_indices.has(hit): + select_object(hit) + _begin_drag(hit, p.position) + else: + # Leere Stelle: Rahmen-Ausziehen vorbereiten; loser Klick = Auswahl weg. + _rubber_pending = true + _rubber_active = false + _rubber_start = p.position + _rubber_shift = p.shift + + +## Zieh-Menge festlegen: Mehrfachauswahl oder komplette Gruppe ziehen GEMEINSAM. +## Vom Gizmo im Drehen-Modus gesetzt: ein Klick auf das gewählte Objekt zieht +## es NICHT mehr weg (nur die Ringe drehen). +var no_drag_move := false + + +func _begin_drag(hit: int, viewport_pos: Vector2) -> void: + if no_drag_move and hit == selected_index: + return + var objects: Array = main.doc.get("objects", []) + _drag_members = [] + _grab_offsets = {} + _drag_lifts = {} + _drag_is_group = false + if selected_indices.size() > 1: + _drag_members = selected_indices.duplicate() + else: + var gid := GroupOps.group_id(objects[hit]) + if gid != GroupOps.UNGROUPED: + var members := GroupOps.members_of(main.doc, gid) + if members.size() > 1: + _drag_members = members + _drag_is_group = true + select_many(members, true) # ganze Gruppe sichtbar markieren + if _drag_members.is_empty(): + _drag_members = [selected_index] + _drag_primary = selected_index if selected_index >= 0 else hit + _dragging = true + _drag_moved = false + var p: Variant = _ground_point(viewport_pos) + if p != null: + for i in _drag_members: + if i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + var off: Vector3 = marker.position - (p as Vector3) + off.y = 0.0 + _grab_offsets[i] = off + # Terrain-Abstand einfrieren (t_7567a06f, für die GANZE Zieh- + # Menge): ein Objekt mit Höhe behält beim Ziehen diesen Abstand + # zum Boden — bodennah bleibt bodennah, angehoben schwebt weiter. + var lift := marker.position.y \ + - SvmapDocument.terrain_height_at(main.doc, marker.position.x, marker.position.z) + _drag_lifts[i] = lift + if i == _drag_primary: + _grab_offset = off + _drag_lift = lift + else: + for i in _drag_members: + _grab_offsets[i] = Vector3.ZERO + _drag_lifts[i] = 0.0 + _grab_offset = Vector3.ZERO + _drag_lift = 0.0 + + +## Gemeinsames Ziehen: Delta vom Primär-Objekt auf ALLE Mitglieder — relative +## Anordnung bleibt exakt erhalten, Raster-Snap rastet die GANZE Gruppe. +func _drag_members_move(viewport_pos: Vector2) -> void: + var p: Variant = _ground_point(viewport_pos) + if p == null or not _grab_offsets.has(_drag_primary): + return + var half := _terrain_half() + var new_primary: Vector3 = _apply_grid((p as Vector3) + (_grab_offsets[_drag_primary] as Vector3)) + new_primary.x = clampf(new_primary.x, -half.x, half.x) + new_primary.z = clampf(new_primary.z, -half.y, half.y) + new_primary.y = SvmapDocument.terrain_height_at(main.doc, new_primary.x, new_primary.z) + var dist := Vector2(viewport_pos.x - _press_pos.x, viewport_pos.y - _press_pos.y).length() + if dist > DRAG_THRESHOLD_PX: + _drag_moved = true + if not _drag_moved: + return + var primary_marker: Node3D = view_builder.object_markers[_drag_primary] + if primary_marker == null or not is_instance_valid(primary_marker): + return + var delta: Vector3 = new_primary - primary_marker.position + delta.y = 0.0 + if delta.length_squared() < 0.0000001: + return + for i in _drag_members: + if i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker == null or not is_instance_valid(marker): + continue + var np: Vector3 = marker.position + delta + np.x = clampf(np.x, -half.x, half.x) + np.z = clampf(np.z, -half.y, half.y) + # Höhe: Terrain-Abstand bleibt erhalten (t_7567a06f, pro Objekt — + # angehoben schwebt weiter, bodennah bleibt bodennah). + np.y = SvmapDocument.terrain_height_at(main.doc, np.x, np.z) \ + + float(_drag_lifts.get(i, 0.0)) + marker.position = np + # Symmetrie (M3, t_34908425): Zwillinge LIVE mitziehen (nur Marker — + # das doc schreibt _commit_drag), exakter Spiegel der gezogenen Quelle. + _sym_drag_sync_live() + + +func _on_left_release(p: PointerEvent) -> void: + if _rubber_pending or _rubber_active: + var was_active := _rubber_active + _rubber_pending = false + _rubber_active = false + main.call("_rubberband_clear") + if was_active: + _finish_rubberband(p.position) + elif not _rubber_shift: + # Loser Klick auf leere Fläche: Auswahl aufheben (Klick statt Auswendiglernen). + if not selected_indices.is_empty(): + clear_selection() + return + if _dragging: + _dragging = false + if _drag_moved and not _drag_members.is_empty(): + _commit_drag() + _drag_moved = false + _drag_members = [] + _grab_offsets = {} + _drag_is_group = false + + +## Rahmen-Ausziehen abgeschlossen: alle Marker im Rahmen auswählen. +func _finish_rubberband(end_pos: Vector2) -> void: + var rect := SelectionMath.rect_from(_rubber_start, end_pos) + var positions: Array = [] + for marker in view_builder.object_markers: + if marker != null and is_instance_valid(marker): + positions.append(marker.global_position) + else: + positions.append(Vector3(INF, INF, INF)) # kaputter Marker: nie im Rahmen + var picked := SelectionMath.pick_in_rect(camera, positions, rect) + if _rubber_shift and not picked.is_empty(): + for i in picked: + if not selected_indices.has(i): + selected_indices.append(i) + selected_index = picked[picked.size() - 1] + elif not _rubber_shift: + selected_indices = picked + selected_index = picked.back() if not picked.is_empty() else -1 + _apply_selection_view() + main.call("_sync_selection_panel") + if selected_indices.is_empty(): + main.call("_set_status", "Der Rahmen hat kein Objekt erwischt — zieh ihn über die Objekte, die du zusammen bearbeiten willst.") + else: + main.call("_set_status", "Rahmen-Auswahl: %d Objekte markiert. Strg+G gruppier sie, oder ziehe sie gemeinsam weiter." % selected_indices.size()) + + +## Bewegte Marker → Doc schreiben, EIN Undo-Schritt für den ganzen Zug. +func _commit_drag() -> void: + var objects: Array = main.doc.get("objects", []) + var moved: Array = [] + for i in _drag_members: + if i >= objects.size() or i >= view_builder.object_markers.size(): + continue + var marker: Node3D = view_builder.object_markers[i] + if marker == null or not is_instance_valid(marker): + continue + moved.append(i) + if moved.is_empty(): + return + var before: Dictionary = main.history.snapshot(["objects"]) + for i in moved: + var pos: Vector3 = (view_builder.object_markers[i] as Node3D).position + objects[i]["pos"] = [float(pos.x), float(pos.y), float(pos.z)] + # Symmetrie (M3, t_34908425): Zwillinge der gezogenen Auswahl auf den exakten + # Spiegel setzen — IM SELBEN Commit (ein Zug = EIN Undo-Schritt für beide). + var twins: Array = ObjectOps.symmetry_sync_pairs( + main.doc, moved, _sym_mode(), _drag_primary) + _sync_members_to_markers(twins) + var desc: String + if _drag_is_group: + desc = "Gruppe verschoben (%d Objekte)" % moved.size() + elif moved.size() > 1: + desc = "%d Objekte verschoben" % moved.size() + else: + desc = "Objekt verschoben" + main.history.commit(desc, before, main.history.snapshot(["objects"])) + _mark_dirty() + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + + +## Dirty-Markierung ohne Full-Rebuild (Auswahl/Marker bleiben bestehen). +func _mark_dirty() -> void: + main.set("dirty", true) + main.call("_refresh_file_label") + + +# ---------------------------------------------------- Screenshot-Hilfe ------ + +## Demo-Hook (Desktop-Smoke): Ghost außerhalb echter Maus-Ereignisse parken, +## damit ein Screenshot die Vorschau zeigen kann. +func demo_park_ghost(world_pos: Vector3, oid: String) -> void: + start_placement(oid) + placing = true + place_oid = oid + _ghost_refresh_mesh() + _ghost.position = world_pos + _ghost_rotation_anwenden() + _ghost.visible = true + _ghost_active = true + main.call("_highlight_library", oid) + main.call("_set_status", "%s: führe die Maus über die Karte — Klick setzt es. Mausrad dreht vorher, ESC bricht ab (Rechte Maustaste = Flug)." % main.object_label(oid)) + + +## Demo-Hook (Gruppen-Smoke, Karte t_f6f29d86): die Auswahl EXAKT so ziehen +## wie ein echter Drag-Release (Marker bewegen + EIN Commit). +func demo_drag_selection(delta: Vector3) -> void: + if selected_indices.is_empty(): + return + _drag_members = selected_indices.duplicate() + _drag_is_group = false + _drag_primary = selected_index + _dragging = true + _drag_moved = true + for i in _drag_members: + if i < view_builder.object_markers.size(): + var marker: Node3D = view_builder.object_markers[i] + if marker != null and is_instance_valid(marker): + marker.position += delta + _commit_drag() + _dragging = false + _drag_members = [] + _drag_moved = false diff --git a/editor/interaction.gd.uid b/editor/interaction.gd.uid new file mode 100644 index 0000000..4d72a94 --- /dev/null +++ b/editor/interaction.gd.uid @@ -0,0 +1 @@ +uid://cdfcdpiga4j0s diff --git a/editor/library_browser.gd b/editor/library_browser.gd new file mode 100644 index 0000000..a7616c3 --- /dev/null +++ b/editor/library_browser.gd @@ -0,0 +1,167 @@ +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, die KEIN `name_de` im Manifest +## haben (IDs sind der Vertrag, Entscheidung 20). Seit der Military-Pack- +## Umstellung (25.10.2026) hat jedes Objekt ein `name_de` — die Liste bleibt +## leer und dient nur als Override-Möglichkeit. +const NAMES := {} + +## Fallback-Gruppe für Objekte ohne bekannte Kategorie. +const GROUP_ORDER := ["sonstiges"] +const GROUP_LABELS := { + "sonstiges": "Sonstiges", +} + +## Kategorien des Military-Desert-Packs (Manifest-Feld `kategorie`, siehe +## library/objects.json; Quelle der Slugs/Namen: scopedvr-match-server +## tools/pack_katalog.py). Reihenfolge = Anzeige-Reihenfolge. Unbekannte +## Kategorien (z. B. spätere Bauteil-Module) tauchen automatisch mit ihrem +## Slug als Gruppe auf, ganz vor "Sonstiges". +const KAT_ORDER := ["bauteile", "panzer", "radfahrzeuge", "luftfahrzeuge", "stellungen", "fahrzeugteile", "gebaeude", "zelte", "tuerme", "mauern", "sperren", "kisten", "technik", "ausruestung", "training", "baeume", "pflanzen", "felsen", "boden", "decals"] +const KAT_GROUPS := { + "bauteile": {"key": "bauteile", "label": "Bauteile (eigene Häuser)"}, + "panzer": {"key": "panzer", "label": "Panzer & Transportpanzer"}, + "radfahrzeuge": {"key": "radfahrzeuge", "label": "Radfahrzeuge"}, + "luftfahrzeuge": {"key": "luftfahrzeuge", "label": "Flugzeuge & Hubschrauber"}, + "stellungen": {"key": "stellungen", "label": "Geschütze & Stellungen"}, + "fahrzeugteile": {"key": "fahrzeugteile", "label": "Fahrzeugteile"}, + "gebaeude": {"key": "gebaeude", "label": "Gebäude & Hangars"}, + "zelte": {"key": "zelte", "label": "Zelte"}, + "tuerme": {"key": "tuerme", "label": "Türme & Masten"}, + "mauern": {"key": "mauern", "label": "Mauern & Wände"}, + "sperren": {"key": "sperren", "label": "Sperren & Zäune"}, + "kisten": {"key": "kisten", "label": "Kisten, Fässer & Container"}, + "technik": {"key": "technik", "label": "Technik & Funk"}, + "ausruestung": {"key": "ausruestung", "label": "Ausrüstung & Zubehör"}, + "training": {"key": "training", "label": "Training & Ziele"}, + "baeume": {"key": "baeume", "label": "Bäume & Totholz"}, + "pflanzen": {"key": "pflanzen", "label": "Pflanzen, Kakteen & Gras"}, + "felsen": {"key": "felsen", "label": "Felsen & Steine"}, + "boden": {"key": "boden", "label": "Boden, Straßen & Dünen"}, + "decals": {"key": "decals", "label": "Boden-Decals"}, +} + +## Anzeige-Größe der Vorschaubilder im Browser (Karte t_59fc0f21): vorher 40 px, +## jetzt Kacheln mit 128 px (25.10.: größer, deutlicher) (128-px-PNGs werden auf die Anzeige-Größe skaliert). +const THUMB_DISPLAY_PX := 128 + + +## Manifest-Cache (vom Editor beim Laden gesetzt): damit object_label() und +## group_of() auch ohne Manifest-Parameter den deutschen Namen / die Kategorie +## kennen (Statuszeile, Szenen-Baum, Inspector). +static var _manifest_cache: Dictionary = {} + + +static func setze_manifest(manifest: Dictionary) -> void: + _manifest_cache = (manifest.get("objects", {}) as Dictionary) if manifest.has("objects") else {} + + +static func object_label(oid: String) -> String: + if BauKatalog.ist_bau(oid): + return BauKatalog.label(oid) + if NAMES.has(oid): + return str(NAMES[oid]) + var entry: Dictionary = _manifest_cache.get(oid, {}) as Dictionary + var name_de := str(entry.get("name_de", "")) + if not name_de.is_empty(): + return name_de + return oid.capitalize() + + +## Gruppe einer ID (ohne Manifest-Eintrag → "sonstiges"). +static func group_of(oid: String) -> String: + if BauKatalog.ist_bau(oid): + return "bauteile" + var entry: Dictionary = _manifest_cache.get(oid, {}) as Dictionary + return group_of_entry(oid, entry) + + +## Gruppe eines Katalog-Eintrags: Manifest-Feld `kategorie` (KAT_GROUPS); +## unbekannte Kategorie → eigene Gruppe mit dem Slug als Schlüssel; kein +## Eintrag/keine Kategorie → "sonstiges". entry darf leer sein. +static func group_of_entry(_oid: String, entry: Dictionary) -> String: + var kat := str(entry.get("kategorie", "")) + if kat.is_empty(): + return "sonstiges" + if KAT_GROUPS.has(kat): + return str(KAT_GROUPS[kat]["key"]) + return kat + + +## Deutscher Anzeigename: Manifest-Feld `name_de`, sonst NAMES/fallback. +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 Gruppen mit +## Objekten erscheinen. Unbekannte Kategorien wandern als eigene Gruppe vor +## "Sonstiges" — neue Bibliotheks-Einträge brechen die UI 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 reihenfolge: Array = KAT_ORDER.duplicate() + var fremde: Array = [] + for key in by_group.keys(): + if not reihenfolge.has(key) and not GROUP_ORDER.has(key): + fremde.append(key) + fremde.sort() + reihenfolge.append_array(fremde) + reihenfolge.append_array(GROUP_ORDER) + var out: Array = [] + for key in reihenfolge: + 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.capitalize() + + +## Thumbnail-Pfad (128-px-PNG, tools/gen_thumbs.gd erzeugt sie einmalig). +static func thumb_path(oid: String) -> String: + return "res://core/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) diff --git a/editor/library_browser.gd.uid b/editor/library_browser.gd.uid new file mode 100644 index 0000000..d4c7d10 --- /dev/null +++ b/editor/library_browser.gd.uid @@ -0,0 +1 @@ +uid://o3jwxk87gtw diff --git a/editor/mouse_pointer.gd b/editor/mouse_pointer.gd new file mode 100644 index 0000000..cff83d9 --- /dev/null +++ b/editor/mouse_pointer.gd @@ -0,0 +1,39 @@ +class_name MousePointer +extends RefCounted +## Übersetzt Maus und Esc-Taste in PointerEvent. Die rechte Maustaste fehlt +## absichtlich: sie gehört dem Freelook-Flug (orbit_camera.gd). + + +static func from_input(event: InputEvent) -> PointerEvent: + if event is InputEventMouseMotion: + var mm := event as InputEventMouseMotion + var p := PointerEvent.make(PointerEvent.Kind.MOVE, mm.position) + p.shift = mm.shift_pressed + p.ctrl = mm.ctrl_pressed + p.command = mm.is_command_or_control_pressed() + p.pressure = mm.pressure + return p + if event is InputEventMouseButton: + var mb := event as InputEventMouseButton + var kind := PointerEvent.Kind.MOVE + var scroll := 0 + match mb.button_index: + MOUSE_BUTTON_LEFT: + kind = PointerEvent.Kind.PRESS if mb.pressed else PointerEvent.Kind.RELEASE + MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN: + if not mb.pressed: + return null + kind = PointerEvent.Kind.SCROLL + scroll = 1 if mb.button_index == MOUSE_BUTTON_WHEEL_UP else -1 + _: + return null + var p := PointerEvent.make(kind, mb.position) + p.shift = mb.shift_pressed + p.ctrl = mb.ctrl_pressed + p.command = mb.is_command_or_control_pressed() + p.scroll = scroll + return p + if event is InputEventKey and (event as InputEventKey).pressed \ + and (event as InputEventKey).keycode == KEY_ESCAPE: + return PointerEvent.make(PointerEvent.Kind.CANCEL) + return null diff --git a/editor/mouse_pointer.gd.uid b/editor/mouse_pointer.gd.uid new file mode 100644 index 0000000..3b06f99 --- /dev/null +++ b/editor/mouse_pointer.gd.uid @@ -0,0 +1 @@ +uid://1nyr0qwwdvnv diff --git a/editor/playtest/playtest_avatar.gd b/editor/playtest/playtest_avatar.gd new file mode 100644 index 0000000..8554ed2 --- /dev/null +++ b/editor/playtest/playtest_avatar.gd @@ -0,0 +1,461 @@ +class_name PlaytestAvatar +extends Node3D +## Playtest-Avatar (Karte t_17a35c59) — SPIEGEL des Bot-Avatars aus dem +## Quest-Client (scripts/match/bot_avatar.gd + MatchRemoteAvatar), Sly-Wunsch +## 20.10.: „das Player Model in den Map Editor … genau so wie im Spiel bei +## den Bots" + „direkt natürlich auch mit den Animationen bitte!". +## +## Quelle der Wahrheit: bot_avatar.gd. Dieses Modul übernimmt 1:1: +## - das GLB assets/avatars/quaternius_ual/ual1_standard.glb (Quaternius +## UAL, CC0) + das Rifle assets/weapons/m40_rifle.glb an hand_r — +## aufgelöst über den NACHBAR-KLON ../scopedvr-client (gleiche Auflösung +## wie GlbMeshStore/TestRun), NICHT dupliziert. Lädt mit GLTFDocument +## (die GLB liegt außerhalb res:// — kein Import-System). +## - die AnimationTree-Pipeline (A4/W14): BlendSpace1D Idle/Walk/Jog mit +## Blend-Punkten auf den Clip-Naturgeschwindigkeiten (0,0 / 0,979 / +## 2,620 m/s), sync = true, TimeScale-Subtrees für die Kadenz, darüber +## die StateMachine mit dem Motion-State. Crossfade ~0,2 s. +## - die Kadenz-Mathe (BOT2/vc23): Kadenz = m/s / Ziel-Schrittlänge +## (Walk 0,95 m / Jog 0,85 m), TimeScale = Kadenz × Zykluslänge / 2 +## Schritte pro Zyklus, Klammern CADENCE 1,0–4,5 und FOOT_SYNC 0,6–2,0. +## - die Gait-Schwellen (A2 §6.2): ≤ 0,3 m/s Idle, ≤ 2,6 m/s Walk, sonst +## Jog — im Editor aus der ECHTEN PlaytestBody-Geschwindigkeit gespeist. +## - die Frontachse (vc21-Fix): das UAL-GLB blickt nach +Z, der Body-Yaw +## richtet −Z aus → MODEL_YAW_OFFSET = π auf dem Modell-Kind. Blick- +## richtung == Bewegungsrichtung. +## - den Blend-Ruckel-Fix: Sollwerte werden in _process weich nachgezogen +## (BLEND_SMOOTH_S = 0,25 s), Parameter-Writes nur bei Änderung. +## +## NICHT gespiegelt (Match-only, kein Trigger im Editor-Testlauf): Engage/ +## Shoot/Hit/Death-One-Shots, Team-Tönung, Namensschild, Netzwerk-Interpo- +## lation, Ragdoll. Die StateMachine trägt deshalb nur den Motion-State — +## die Loop-Pipeline (die Sly sieht) ist identisch zum Spiel. +## +## Regel (wie KolliderBau/svmap_tint): Ändert der Client die Pipeline, wird +## dieser Spiegel MIT geändert. Bewusst KEINE Editor-eigenen Maße oder +## Anim-Parameter — Proportionen/Konstanten strikt aus der Client-Quelle. +## +## Fehlt der Client-Klon (oder die GLB): lade_avatar() gibt null zurück — +## PlaytestMode bleibt dann komplett in First Person (graceful, kein Crash). + +## ------------------------------------------------ Quelle (Client-Klon) ---- +## Relative Pfade IM Client-Repo (Nachbar-Klon, nicht dupliziert). +const MODEL_REL := "assets/avatars/quaternius_ual/ual1_standard.glb" +const RIFLE_REL := "assets/weapons/m40_rifle.glb" + +## ------------------------------------- Clips (MatchRemoteAvatar-Erbe) ----- +const CLIP_IDLE := "Idle" +const CLIP_WALK := "Walk" +const CLIP_JOG := "Jog_Fwd" + +## Server-/Inferenz-States (A1-Vertrag, bot_avatar.gd): 0=IDLE 1=WALK 2=JOG. +const STATE_IDLE := 0 +const STATE_WALK := 1 +const STATE_JOG := 2 + +## Gait-Schwellen (A2 §6.2, bot_avatar.gd): Velocity → State. +const IDLE_SPEED_MAX_MPS := 0.3 +const WALK_SPEED_MAX_MPS := 2.6 + +## Blend-Punkte = Clip-Naturgeschwindigkeiten (H1-Messung, bot_avatar.gd). +const WALK_NATURAL_MPS := 0.979 +const JOG_NATURAL_MPS := 2.620 +const BLEND_IDLE_MPS := 0.0 +const BLEND_MAX_MPS := 4.0 + +## Kadenz-Kalibrierung (BOT2/vc23, bot_avatar.gd): Ziel-Schrittlängen + Klammern. +const WALK_STEP_M := 0.95 +const JOG_STEP_M := 0.85 +const CADENCE_MIN := 1.0 +const CADENCE_MAX := 4.5 +const FOOT_SYNC_MIN := 0.6 +const FOOT_SYNC_MAX := 2.0 + +## Crossfade (A4) + Blend-Glättung (vc21-Ruckel-Fix, bot_avatar.gd). +const CROSSFADE_S := 0.2 +const BLEND_SMOOTH_S := 0.25 + +## Modell-Anker (bot_avatar.gd): Knoten = Kopf-Anker in Augenhöhe (1,65 m), +## GLB-Kind hängt mit negativem Y-Versatz (Füße am Boden, Modell 1,83 m). +const MODEL_EYE_HEIGHT := 1.65 +const MODEL_Y_OFFSET := -1.65 +## Frontachse (vc21-Messung): GLB blickt nach +Z → 180°-Korrektur auf dem +## Modell-Kind, damit die Front auf das lokale −Z (Body-Vorwärts) zeigt. +const MODEL_FORWARD_LOCAL := Vector3(0.0, 0.0, 1.0) +const MODEL_YAW_OFFSET := PI + +## Rifle-Mount (vc21-Messung, bot_avatar.gd): hand_r, Lauf → Hand-+Y, +## Rifle-oben → Hand-+Z = reine +90°-X-Drehung; Griff 0,067 m auf Hand-+Y. +const RIFLE_HAND_BONE := "hand_r" +const RIFLE_ROT_BASIS := Basis(Vector3(1.0, 0.0, 0.0), deg_to_rad(90.0)) +const RIFLE_POS := Vector3(0.0, 0.067, 0.0) + +## AnimationTree-Vertrag (bot_avatar.gd — Namen sind Teil des Vertrags). +const SM_MOTION := "Motion" +const P_BLEND := "parameters/Motion/Speed/blend_position" +const P_WALK_SCALE := "parameters/Motion/Speed/Walk/WalkTs/scale" +const P_JOG_SCALE := "parameters/Motion/Speed/Jog/JogTs/scale" +const P_PLAYBACK := "parameters/playback" + +## GLB-Instanz (Modell-Wurzel), AnimationPlayer (Clip-Lieferant), Tree. +var _model: Node3D = null +var _anim: AnimationPlayer = null +var _tree: AnimationTree = null +var _playback: AnimationNodeStateMachinePlayback = null +## Sichtbarer Zustand (Tests/Debug): Soll-Clip + Soll-Blend. +var _cur_clip := "" +var state := STATE_IDLE +var speed_mps := 0.0 +var _blend_cur := 0.0 +var _blend_target := 0.0 +var _walk_scale_cur := 1.0 +var _walk_scale_target := 1.0 +var _jog_scale_cur := 1.0 +var _jog_scale_target := 1.0 + + +## Client-Projektordner (Nachbar-Klon oder konfigurierter Pfad) — dieselbe +## Auflösung wie der alte Client-Start (test_run.gd). "" = keiner gefunden. +static func client_wurzel() -> String: + return TestRun.find_client(ProjectSettings.globalize_path("res://"), + TestRun.load_cfg()) + + +## Absoluter Modell-/Rifle-Pfad im Client-Klon ("" = kein Klon gefunden). +static func model_pfad() -> String: + var root := client_wurzel() + if root.is_empty(): + return "" + return root.path_join(MODEL_REL) + + +static func rifle_pfad() -> String: + var root := client_wurzel() + if root.is_empty(): + return "" + return root.path_join(RIFLE_REL) + + +## State aus der echten Geschwindigkeit (Spiegel von _track_velocity, +## bot_avatar.gd — dieselben Schwellen, ohne Netzwerk-Glättung). +static func zustand_fuer(speed: float) -> int: + if speed <= IDLE_SPEED_MAX_MPS: + return STATE_IDLE + if speed <= WALK_SPEED_MAX_MPS: + return STATE_WALK + return STATE_JOG + + +## Avatar aus dem Client-Klon laden (null = fehlt/nicht ladbar → graceful +## Fallback auf First Person beim Aufrufer). Baut Modell + AnimationTree +## + Rifle-Mount exakt wie bot_avatar._ready/_build_model/_build_anim_tree. +static func lade_avatar() -> PlaytestAvatar: + var glb_pfad := model_pfad() + if glb_pfad.is_empty() or not FileAccess.file_exists(glb_pfad): + push_warning("PLAYTEST_AVATAR: Modell fehlt (%s) — Third Person nicht verfügbar" % glb_pfad) + return null + var gltf := GLTFDocument.new() + var state := GLTFState.new() + if gltf.append_from_file(glb_pfad, state) != OK: + push_warning("PLAYTEST_AVATAR: GLB nicht ladbar: %s" % glb_pfad) + return null + var szene: Node = gltf.generate_scene(state) + if szene == null: + push_warning("PLAYTEST_AVATAR: GLB-Szene leer: %s" % glb_pfad) + return null + var avatar := PlaytestAvatar.new() + avatar.name = "Avatar" + avatar._model = szene as Node3D + if avatar._model == null: + push_warning("PLAYTEST_AVATAR: GLB-Wurzel kein Node3D: %s" % glb_pfad) + szene.free() + return null + avatar._model.name = "Body" + # Kopf-Anker-Konvention (bot_avatar._build_model): das GLB-Kind hängt mit + # MODEL_Y_OFFSET nach unten (GLB-Ursprung = Füße) — der Avatar-Node selbst + # sitzt bei PlaytestBody in Augenhöhe über dem Füßpunkt (set_avatar). + # vc21-Frontachse: GLB blickt nach +Z → 180° auf das Modell-Kind, damit + # die Modell-Front auf das Avatar-lokale −Z zeigt (= Body-Vorwärts). + avatar._model.position = Vector3(0.0, MODEL_Y_OFFSET, 0.0) + avatar._model.rotation.y = MODEL_YAW_OFFSET + avatar.add_child(avatar._model) + for child in avatar._model.find_children("*", "AnimationPlayer", true, false): + avatar._anim = child + if avatar._anim == null: + push_warning("PLAYTEST_AVATAR: kein AnimationPlayer im GLB — Avatar unbrauchbar") + avatar.free() + return null + # Importer-Semantik nachbilden: der Godot-Editor-Import (Client .import) + # streicht Animations-Namens-ENDUNGEN "-loop"/"_loop" und setzt dann + # Loop-Mode LOOP_LINEAR. GLTFDocument (Runtime-Ladung) liefert die rohen + # Namen ("Idle_Loop", "Walk_Loop", …) OHNE Loop-Mode — ohne diese + # Normalisierung hätten die Loops den falschen Namen (Bot-Vertrag: Idle/ + # Walk/Jog_Fwd) und würden nach einem Zyklus stehen bleiben. + avatar._normalisiere_clips() + if avatar._anim.get_animation_list().is_empty(): + push_warning("PLAYTEST_AVATAR: kein AnimationPlayer/Clips im GLB — Avatar unbrauchbar") + avatar.free() + return null + avatar._baue_anim_tree() + avatar._baue_rifle() + # Start-Anim: Idle (wie bot_avatar._ready — kein T-Pose-Blitz). + avatar.apply_state(STATE_IDLE, 0.0) + return avatar + + +## Importer-Semantik (siehe Aufrufkommentar): "_loop"/"-loop"-Endungen +## streichen, LOOP_LINEAR setzen — genau das, was der Client-.import mit +## derselben GLB macht (dort heißen die Clips Idle/Walk/Jog_Fwd, Loop-Mode +## im Asset korrekt, siehe bot_avatar.gd-Clip-Doku). +func _normalisiere_clips() -> void: + for lib_name in _anim.get_animation_library_list(): + var lib := _anim.get_animation_library(lib_name) + for clip_name in lib.get_animation_list(): + var animation: Animation = lib.get_animation(clip_name) + var lower := String(clip_name).to_lower() + if lower.ends_with("_loop") or lower.ends_with("-loop"): + var clean := String(clip_name).substr(0, String(clip_name).length() - 5) + lib.rename_animation(clip_name, clean) + animation.loop_mode = Animation.LOOP_LINEAR + + +## AnimationTree (A4-Pipeline, Motion-Zweig): BlendSpace1D Idle/Walk/Jog + +## TimeScale-Subtrees, darüber die StateMachine mit dem Motion-State. +## Verdrahtungs-Fallen (bot_avatar.gd A4-Sektion, Probe 18.10.): Clips als +## EIGENE Library auf die Tree kopieren (anim_player-Compatibility-Pfad +## überspringt die GLB-Default-Library), root_node = Modell-Wurzel +## (Track-Pfade "Armature/Skeleton3D:*"), Player bleibt reiner Clip-Lieferant. +func _baue_anim_tree() -> void: + _anim.stop() + var tree := AnimationTree.new() + tree.name = "AnimTree" + _model.add_child(tree) + var lib := AnimationLibrary.new() + for clip in _anim.get_animation_list(): + lib.add_animation(clip, _anim.get_animation(clip)) + tree.add_animation_library("", lib) + tree.root_node = tree.get_path_to(_model) + var motion := AnimationNodeBlendTree.new() + var blend := AnimationNodeBlendSpace1D.new() + var idle := AnimationNodeAnimation.new() + idle.animation = CLIP_IDLE + blend.add_blend_point(idle, BLEND_IDLE_MPS, 0, "Idle") + blend.add_blend_point(_speed_scaled_clip(CLIP_WALK, "WalkAnim", "WalkTs"), + WALK_NATURAL_MPS, 1, "Walk") + blend.add_blend_point(_speed_scaled_clip(CLIP_JOG, "JogAnim", "JogTs"), + JOG_NATURAL_MPS, 2, "Jog") + blend.min_space = 0.0 + blend.max_space = BLEND_MAX_MPS + blend.sync = true # vc21: gemeinsame Phase beim Blenden (kein Fuß-Pop) + motion.add_node("Speed", blend) + motion.connect_node("output", 0, "Speed") + var sm := AnimationNodeStateMachine.new() + sm.add_node(SM_MOTION, motion) + tree.tree_root = sm + tree.active = true + tree.process_callback = AnimationTree.ANIMATION_PROCESS_IDLE + _tree = tree + # _playback bewusst NICHT hier holen: der Baum hängt noch nicht im + # Szenenbaum — tree.get(P_PLAYBACK) liefert vorher null. Der Motion- + # Start passiert in apply_state() über _playback_holen() (nach dem + # Baum-Eintritt, wie _ensure_motion im Client). + + +## A4-Helfer (1:1 bot_avatar.gd): Clip-Subtree mit AnimationNodeTimeScale. +func _speed_scaled_clip(clip: String, anim_name: String, ts_name: String) -> AnimationNodeBlendTree: + var bt := AnimationNodeBlendTree.new() + var an := AnimationNodeAnimation.new() + an.animation = clip + var ts := AnimationNodeTimeScale.new() + bt.add_node(anim_name, an) + bt.add_node(ts_name, ts) + bt.connect_node(ts_name, 0, anim_name) + bt.connect_node("output", 0, ts_name) + return bt + + +## Rifle an hand_r (1:1 bot_avatar._build_rifle). Fehlt das Rifle-GLB oder +## der Knochen: warnen und ohne Waffe weiter (Avatar bleibt brauchbar). +func _baue_rifle() -> void: + var rifle_pfad_datei := rifle_pfad() + if rifle_pfad_datei.is_empty() or not FileAccess.file_exists(rifle_pfad_datei): + push_warning("PLAYTEST_AVATAR: Rifle fehlt (%s) — Avatar ohne Waffe" % rifle_pfad_datei) + return + var skeleton := _skeleton() + if skeleton == null: + push_warning("PLAYTEST_AVATAR: kein Skeleton3D — Rifle bleibt unattached") + return + if skeleton.find_bone(RIFLE_HAND_BONE) < 0: + push_warning("PLAYTEST_AVATAR: Hand-Knochen '%s' fehlt — Rifle bleibt unattached" % RIFLE_HAND_BONE) + return + var gltf := GLTFDocument.new() + var state := GLTFState.new() + if gltf.append_from_file(rifle_pfad_datei, state) != OK: + push_warning("PLAYTEST_AVATAR: Rifle-GLB nicht ladbar: %s" % rifle_pfad_datei) + return + var szene: Node = gltf.generate_scene(state) + if szene == null: + return + var attach := BoneAttachment3D.new() + attach.name = "RifleMount" + attach.bone_name = RIFLE_HAND_BONE + skeleton.add_child(attach) + var rifle := szene as Node3D + rifle.name = "Rifle" + rifle.basis = RIFLE_ROT_BASIS + rifle.position = RIFLE_POS + attach.add_child(rifle) + + +## Erstes Skeleton3D unter dem GLB (bot_avatar._build_model-Suche). +func _skeleton() -> Skeleton3D: + if _model == null: + return null + for child in _model.find_children("*", "Skeleton3D", true, false): + return child + return null + + +## ---------------------------------------------------------------- Anim ---- + +## Playtest-Movement-State anlegen (Kartenvorgabe: aus der ECHTEN Bewegung). +## mps < 0 = "nicht bekannt" → Kadenz unverändert lassen (wie bot_avatar). +func apply_state(new_state: int, new_speed_mps: float = -1.0) -> void: + if new_state != STATE_IDLE and new_state != STATE_WALK and new_state != STATE_JOG: + return # unbekannter State: nichts ändern (wie _clip_for_state ""). + state = new_state + if new_speed_mps >= 0.0: + speed_mps = new_speed_mps + var clip := _clip_for_state(state) + if clip.is_empty(): + return + _cur_clip = clip + if state == STATE_WALK: + _blend_target = WALK_NATURAL_MPS + _walk_scale_target = gait_scale_for(state, speed_mps) + _jog_scale_target = 1.0 + elif state == STATE_JOG: + _blend_target = JOG_NATURAL_MPS + _jog_scale_target = gait_scale_for(state, speed_mps) + _walk_scale_target = 1.0 + else: + _blend_target = BLEND_IDLE_MPS + _walk_scale_target = 1.0 + _jog_scale_target = 1.0 + if not is_processing(): + # Ohne _process-Treiber (Tests vor dem ersten Frame) sofort schreiben. + _blend_cur = _blend_target + _walk_scale_cur = _walk_scale_target + _jog_scale_cur = _jog_scale_target + _write_blend_params() + var playback := _playback_holen() + if playback != null: + # Wie bot_avatar._ensure_motion: Loop-State halten bzw. zurück. + # Der pre-tree start() der StateMachine kann beim Baum-Eintritt + # verworfen werden (leerer Knoten) → hier sauber (neu) starten. + var node := String(playback.get_current_node()) + if node.is_empty(): + playback.start(SM_MOTION) + elif node != SM_MOTION: + playback.travel(SM_MOTION) + + +## Bot2-Kadenz (1:1 bot_avatar.gd): Kadenz = m/s / Ziel-Schrittlänge, geklemmt. +func gait_cadence_for(s: int, mps: float) -> float: + var step := WALK_STEP_M if s == STATE_WALK else JOG_STEP_M + if s != STATE_WALK and s != STATE_JOG: + return 0.0 + return clampf(mps / maxf(step, 0.05), CADENCE_MIN, CADENCE_MAX) + + +## Bot2-TimeScale (1:1 bot_avatar.gd): Kadenz × Zykluslänge / 2 Schritte. +func gait_scale_for(s: int, mps: float) -> float: + var cadence := gait_cadence_for(s, mps) + if cadence <= 0.0: + return 1.0 + var cycle := _cycle_len_for(s) + if cycle <= 0.0: + return 1.0 + return clampf(cadence * cycle / 2.0, FOOT_SYNC_MIN, FOOT_SYNC_MAX) + + +func _cycle_len_for(s: int) -> float: + var clip := _clip_for_state(s) + if clip.is_empty() or _anim == null or not _anim.has_animation(clip): + return 0.0 + return _anim.get_animation(clip).length + + +## StateMachine-Playback faul holen: erst NACH dem Baum-Eintritt kann die +## Tree den "parameters/playback"-Parameter auflösen (vorher null). +func _playback_holen() -> AnimationNodeStateMachinePlayback: + if _playback == null and _tree != null: + _playback = _tree.get(P_PLAYBACK) as AnimationNodeStateMachinePlayback + return _playback + + +## Clip-Auflösung (1:1 bot_avatar._clip_for_state, ohne ENGAGE — Match-only). +func _clip_for_state(s: int) -> String: + match s: + STATE_IDLE: + return CLIP_IDLE + STATE_WALK: + return CLIP_WALK + STATE_JOG: + return CLIP_JOG + _: + return "" + + +## vc21: Welt-Richtung, in die das MESH blickt (Vertrag wie +## bot_avatar.model_forward: Front == Bewegungsrichtung). Hier wird die +## ECHTE Modell-Transform gemessen — der MODEL_YAW_OFFSET sitzt in diesem +## Spiegel auf dem Modell-Kind (nicht auf dem Node wie im Client), die +## Messung über _model.global_transform ist davon unabhängig korrekt. +func model_forward() -> Vector3: + if _model == null: + return Vector3(0.0, 0.0, -1.0) + var f: Vector3 = _model.global_transform.basis * MODEL_FORWARD_LOCAL + f.y = 0.0 + return f.normalized() + + +## Aktueller StateMachine-State ("" ohne Baum) — Tests/Debug. +func tree_state() -> String: + var playback := _playback_holen() + if playback == null: + return "" + return String(playback.get_current_node()) + + +## Soll-Clip des aktuellen States (wie bot_avatar.current_clip, ohne One-Shots). +func current_clip() -> String: + return _cur_clip + + +## AnimationTree aktiv? (Tests: "animiert überhaupt".) +func anim_running() -> bool: + return _tree != null and _tree.active + + +func _process(delta: float) -> void: + # vc21: Blend/Kadenz weich nachziehen (kein sichtbares Springen). + if _tree == null: + return + var k := clampf(delta / BLEND_SMOOTH_S, 0.0, 1.0) + _blend_cur = lerpf(_blend_cur, _blend_target, k) + _walk_scale_cur = lerpf(_walk_scale_cur, _walk_scale_target, k) + _jog_scale_cur = lerpf(_jog_scale_cur, _jog_scale_target, k) + _write_blend_params() + + +## vc21: geglätteten Stand in die Tree schreiben (nur bei Änderung). +func _write_blend_params() -> void: + if _tree == null: + return + if absf(float(_tree.get(P_BLEND)) - _blend_cur) > 0.0005: + _tree.set(P_BLEND, _blend_cur) + if absf(float(_tree.get(P_WALK_SCALE)) - _walk_scale_cur) > 0.0005: + _tree.set(P_WALK_SCALE, _walk_scale_cur) + if absf(float(_tree.get(P_JOG_SCALE)) - _jog_scale_cur) > 0.0005: + _tree.set(P_JOG_SCALE, _jog_scale_cur) diff --git a/editor/playtest/playtest_avatar.gd.uid b/editor/playtest/playtest_avatar.gd.uid new file mode 100644 index 0000000..4651d03 --- /dev/null +++ b/editor/playtest/playtest_avatar.gd.uid @@ -0,0 +1 @@ +uid://cvj6x35cenpbm diff --git a/editor/playtest/playtest_body.gd b/editor/playtest/playtest_body.gd new file mode 100644 index 0000000..b365446 --- /dev/null +++ b/editor/playtest/playtest_body.gd @@ -0,0 +1,581 @@ +extends CharacterBody3D +class_name PlaytestBody +## Lauf-Modus Spieler (Karte t_1cdc1a72, Physik-Neubau t_1da96275): +## erste Person, WASD + Maus, Shift = rennen, Space = springen, Gravitation. +## +## ECHTE PHYSIK — identisch zum Quest-3-Client (Sly-Forderung 20.10.: +## „Die Möglichkeiten des Map Editors sollen ABSOLUT identisch mit der +## Quest-3-Version sein“). Dieser Body ist ein CharacterBody3D mit EXAKT +## denselben Parametern wie client/scripts/world/testmap_player.gd +## (TestmapPlayer): Kapsel 0.35 × 1.8, WALK/SPRINT/JUMP/GRAVITY, +## Step-Up 0.45 m nach move_and_slide (RayCast-Kanten-Abtastung, 1:1 +## gespiegelt inkl. Decken-Guard). Die Kollider kommen von KolliderBau +## (Spiegel von svmap_world.gd) — dieselben Trimeshe wie im Spiel. +## +## Der alte AABB-Blocking-Code (vorgecachte Kisten, eigener step()-Solver +## ohne Physik-Server) ist ENTFERNT — es gibt genau EINEN Bewegungscode, +## denselben wie auf der Quest. +## +## Steuerung: _physics_process pollt die Tasten (oder die Autopilot-Haken +## auto_move_dir/auto_jump/auto_run für Suiten und spätere Automatisierung) +## und fährt dieselbe Logik wie der Client. ESC wird NICHT hier gefangen: +## main.gd behandelt Esc VOR der normalen Esc-Kette, solange der Lauf-Modus +## aktiv ist („Esc = zurück zum Editor“). +## +## Koordinaten-Konvention (wie Client): Body-Ursprung = KAPSEL-MITTE, +## Kamera sitzt EYE_HEIGHT über der Mitte (TestmapPlayer.EYE_HEIGHT). + +signal exit_requested + +# --- Parameter 1:1 aus client/scripts/world/testmap_player.gd --------------- +const WALK_SPEED := 6.0 +const SPRINT_SPEED := 14.0 +const JUMP_SPEED := 4.5 +const GRAVITY := 9.8 +const EYE_HEIGHT := 0.65 # Kamera über der Kapsel-Mitte (Kapsel 1.8 m) +const KILL_Y_DROP_M := -200.0 # Notfall: unter die Welt gefallen → Respawn +const KAPSEL_RADIUS := 0.35 +const KAPSEL_HEIGHT := 1.8 +const HALF_H := 0.9 # Kapsel-Halbhöhe (Kapsel 1.8 m) — Füße = y − 0.9 +const STEP_HEIGHT := 0.45 # = TestmapPlayer.STEP_HEIGHT (Karte t_f9adf97c) +const STEP_PROBE_DIST := 0.7 +const STEP_LIFT_EPS := 0.02 + +# --- Editor-seitige Konventionen (unverändert gegenüber dem alten Body) ----- +const DEFAULT_MOUSE_SENS := 0.25 # Grad pro Pixel Mausbewegung +const PITCH_LIMIT := 89.0 # Kopfüber/Nackenschmerz-Grenze +const TELEPORT_REICHWEITE := 4000.0 + +## Spielbare Parameter +var mouse_sens: float = DEFAULT_MOUSE_SENS + +## Map-Dokument (nur LESEND — Terrain-Raster für Spawn/HUD/Notfall) +var doc: Dictionary = {} +## Fliegen / God-Mode (Karte t_aaff5dcc, Taste G): KEINE Gravitation, Bewegung +## in Blickrichtung (inkl. Nickwinkel), Space = steigen, Noclip — der Tester +## kommt auch über Wände/Hügel schnell ans Ziel. +var fly_mode := false +## Autopilot-Haken (wie TestmapPlayer.auto_move_dir, Karte t_f9adf97c): +## nicht-leer = DIESER Welt-Vektor ist der Bewegungswunsch (Tastatur aus). +var auto_move_dir := Vector3.ZERO +## Autopilot-Ergänzung für Suiten: Sprung/Run erzwingen ohne Tastatur. +var auto_jump := false +var auto_run := false +## Suite-Hook: nicht-leer = komplettes Input-Dictionary überschreibt ALLES +## (gleiche Form wie der alte step()-Input: fwd/back/left/right/run/jump). +var auto_input: Dictionary = {} + +## Bewegungszustand (Kompatibilität zur alten Oberfläche) +var yaw_deg: float = 0.0 # Gierwinkel Blickrichtung (Grad) +var pitch_deg: float = 0.0 # Nickwinkel Blickrichtung (Grad) +var on_floor: bool = false + +## Kinder: die Spiel-Kamera (Augen) — PlaytestMode hängt sie an und macht current +var look_cam: Camera3D + +## Höhenfeld-Cache (Zeile: res*res Meter) — von PlaytestMode via set_terrain() +## gefüllt (Spawn/HUD/jump_to_spawn; die PHYSIK läuft über den Trimesh-Kollider). +var heights: PackedFloat32Array = PackedFloat32Array() +var res: int = 0 +var size_x: float = 0.0 +var size_z: float = 0.0 +var hmin: float = 0.0 +var hmax: float = 0.0 + +var _spawn_pos := Vector3.ZERO + + +func _ready() -> void: + _spawn_pos = global_position + var cs := CollisionShape3D.new() + var capsule := CapsuleShape3D.new() + capsule.radius = KAPSEL_RADIUS + capsule.height = KAPSEL_HEIGHT + cs.shape = capsule + add_child(cs) + look_cam = Camera3D.new() + look_cam.name = "Camera" + look_cam.position.y = EYE_HEIGHT + look_cam.far = 4000.0 + add_child(look_cam) + look_cam.current = true + _baue_dritte_kamera() + _apply_look() + + +## Third-Person-Kamera (Karte t_17a35c59): SpringArm3D = RayCast hinter dem +## Spieler — die Kamera wird an die Kollision zurückgezogen (kein Clipping +## durch Wände). Pivot auf Augenhöhe, Arm-Länge 3,5 m + 0,3 m Margin, der +## eigene Body ist ausgeschlossen (sonst trifft der Strahl die eigene Kapsel). +func _baue_dritte_kamera() -> void: + spring = SpringArm3D.new() + spring.name = "ThirdPersonArm" + spring.position = Vector3(0.0, EYE_HEIGHT, 0.0) + spring.spring_length = 3.5 + spring.margin = 0.3 + spring.collision_mask = 1 + tpp_cam = Camera3D.new() + tpp_cam.name = "ThirdPersonCamera" + tpp_cam.far = 4000.0 + spring.add_child(tpp_cam) + add_child(spring) + spring.add_excluded_object(get_rid()) + tpp_cam.current = false + + +## Kinder: die Third-Person-Kamera (SpringArm + Kamera) — Karte t_17a35c59. +var spring: SpringArm3D = null +var tpp_cam: Camera3D = null + +## Playtest-Avatar (Spiegel des Bot-Rigs, playtest_avatar.gd). null = kein +## Client-Klon/Modell → Third Person bleibt gesperrt (graceful Fallback). +var avatar: Node3D = null + +## Perspektive (Taste V, persistiert in PlaytestSettings): false = First +## Person (Standard), true = Third Person (Avatar sichtbar, Kamera hinten). +var perspektive_dritte := false + + +## Blickrichtung auf Körper + Kameras übertragen. +## Der BODY rotiert mit dem Blick-Yaw (wie TestmapPlayer.rotate_y) — das +## trägt die Avatar-Front (Kind des Körpers) und die Third-Person-Kamera. +## Die WASD-Bewegung selbst folgt dem Yaw über _fahren (parallel gefixt in +## 52919b3, dort aus yaw_deg gebaut — beide Wege ergeben dieselbe Richtung). +## Vor beiden Fixes blieb der Body ungedreht (Beweis-Probe +## tests/probe_move_dir.gd: yaw −90° + W → −Z statt +X = weltachsen-fixiert). +func _apply_look() -> void: + # 1:1 TestmapPlayer: der BODY trägt den Yaw (rotate_y), die Kamera nur + # den Pitch — sonst doppelt sich die Drehung (Kamera hängt am Körper). + rotation.y = deg_to_rad(yaw_deg) + if look_cam == null: + return + look_cam.rotation = Vector3(deg_to_rad(pitch_deg), 0.0, 0.0) + look_cam.position = Vector3(0.0, EYE_HEIGHT, 0.0) + # Third Person: die SpringArm-Kamera teilt dieselbe Blickachse (Yaw über + # den Körper, Pitch über den Arm) — Blickrichtung == Kamerarichtung. + if spring != null: + spring.rotation = Vector3(deg_to_rad(pitch_deg), 0.0, 0.0) + + +## Füß-Höhe über der Welt (Kapsel-Mitte minus Halbhöhe) — HUD/Tests. +func fuss_hoehe() -> float: + return global_position.y - HALF_H + + +## Volle 3D-Blickrichtung (Karte t_aaff5dcc): -Z um Pitch (X) und Yaw (Y) +## gedreht — genau die Blickachse der Kamera. Fliegen bewegt entlang DIESEM +## Vektor (Blick nach unten = abwärts fliegen), Gehen nur horizontal. +func view_direction() -> Vector3: + var yaw_rad := deg_to_rad(yaw_deg) + var pitch_rad := deg_to_rad(pitch_deg) + return Vector3(-sin(yaw_rad) * cos(pitch_rad), sin(pitch_rad), + -cos(yaw_rad) * cos(pitch_rad)) + + +## Echter Physics-Step: Input zusammenbauen (Tasten ODER Autopilot) → fahren. +func _physics_process(delta: float) -> void: + var input := {} + if not auto_input.is_empty(): + input = auto_input + else: + input = { + "fwd": Input.is_key_pressed(KEY_W), + "back": Input.is_key_pressed(KEY_S), + "left": Input.is_key_pressed(KEY_A), + "right": Input.is_key_pressed(KEY_D), + "run": Input.is_key_pressed(KEY_SHIFT) or auto_run, + "jump": Input.is_key_pressed(KEY_SPACE) or auto_jump, + } + if auto_move_dir.length_squared() > 0.0: + input["auto_dir"] = auto_move_dir + _fahren(delta, input) + + +## Ein Bewegungs-Schritt (physics frame) mit EXPLIZITEM Input ausführen. +## input = {fwd, back, left, right, run, jump: bool, auto_dir: Vector3}. +## DIESE Methode ist 1:1 die _physics_process des TestmapPlayers — +## identische Bewegungsmathe, identische Step-Up-Aufrufreihenfolge. +func _fahren(delta: float, input: Dictionary) -> void: + # --- Fliegen / God-Mode (Karte t_aaff5dcc, Taste G) ---------------------- + # Keine Gravitation, Bewegung in der VOLLEN Blickrichtung, Space = steigen, + # Noclip (direkt positionieren — kein move_and_slide, kein Kollider). + if fly_mode: + var fly_speed := WALK_SPEED if not bool(input.get("run", false)) else SPRINT_SPEED + var fly_yaw_rad := deg_to_rad(yaw_deg) + var fly_right := Vector3(cos(fly_yaw_rad), 0.0, -sin(fly_yaw_rad)) + var wish3: Vector3 + var ad: Variant = input.get("auto_dir", null) + if ad is Vector3 and (ad as Vector3).length_squared() > 0.0: + wish3 = (ad as Vector3).normalized() * fly_speed + else: + wish3 = view_direction() * (1.0 if bool(input.get("fwd", false)) else 0.0) \ + - view_direction() * (1.0 if bool(input.get("back", false)) else 0.0) \ + + fly_right * (1.0 if bool(input.get("right", false)) else 0.0) \ + - fly_right * (1.0 if bool(input.get("left", false)) else 0.0) + if bool(input.get("jump", false)): + wish3 += Vector3.UP + if wish3.length_squared() > 0.0: + wish3 = wish3.normalized() * fly_speed + velocity = wish3 + position += velocity * delta + on_floor = false + _apply_look() + return + + # Welt-Richtung aus dem Blickwinkel (y-only, wie Client). WICHTIG: aus + # yaw_deg bauen, NICHT aus transform.basis (Sly-Befund 20.10.: WASD lief + # sonst stur in eine Himmelsrichtung). Seit t_17a35c59 rotiert der + # Körperknoten ZUSÄTZLICH mit dem Yaw (_apply_look — Avatar-Front und + # Third-Person-Kamera hängen daran); die Bewegung bleibt bewusst an + # yaw_deg gekoppelt — beide Wege führen auf dieselbe Richtung, die + # Entkopplung hält die Bewegung unabhängig vom Avatar-Zustand. + # Identisch zu view_direction(). + var yaw_rad := deg_to_rad(yaw_deg) + var forward := Vector3(-sin(yaw_rad), 0.0, -cos(yaw_rad)) + var right := Vector3(cos(yaw_rad), 0.0, -sin(yaw_rad)) + var dir := Vector3.ZERO + var ad2: Variant = input.get("auto_dir", null) + if ad2 is Vector3 and (ad2 as Vector3).length_squared() > 0.0: + dir = ad2 as Vector3 + else: + if bool(input.get("fwd", false)): + dir += forward + if bool(input.get("back", false)): + dir -= forward + if bool(input.get("right", false)): + dir += right + if bool(input.get("left", false)): + dir -= right + var speed := SPRINT_SPEED if bool(input.get("run", false)) else WALK_SPEED + if dir.length_squared() > 0.0: + dir = dir.normalized() * speed + velocity.x = dir.x + velocity.z = dir.z + if is_on_floor() and bool(input.get("jump", false)): + velocity.y = JUMP_SPEED + if not is_on_floor(): + velocity.y -= GRAVITY * delta + var pre_pos := global_position + move_and_slide() + # Stufen-Hilfe (Karte t_f9adf97c, 1:1 aus TestmapPlayer): nach + # move_and_slide() prüfen, ob die horizontale Bewegung von einer Wand + # gestoppt wurde, während der Wunsch weiter in die Wand zeigt. + var wish_xz := Vector3(dir.x, 0.0, dir.z) + var blocked := false + if wish_xz.length_squared() > 0.0001: + if is_on_wall() and get_wall_normal().dot(wish_xz.normalized()) < -0.1: + blocked = true + else: + var got := global_position - pre_pos + got.y = 0.0 + var want := wish_xz * delta + if got.length() < want.length() * 0.4: + blocked = true + if blocked: + try_step_up(wish_xz) + if global_position.y < KILL_Y_DROP_M: + # Sicherheitsnetz (sollte mit Trimesh-Terrain nicht passieren). + global_position = _spawn_pos + velocity = Vector3.ZERO + on_floor = is_on_floor() + _apply_look() + + +## ------------------------------------------------------------------ +## Stufen-Hilfe — 1:1 aus client/scripts/world/testmap_player.gd +## (Karte t_f9adf97c): Stufen bis STEP_HEIGHT erklimmen. Nach +## move_and_slide(), wenn die horizontale Bewegung von einer Wand gestoppt +## wurde UND der Bewegungswunsch in die Wand zeigt: +## Ray 1 (Fußhöhe vorwärts) → Wand-/Kanten-Fläche finden, +## Ray 2 (Kopfhöhe vorwärts) → Wand überragt die Stufenhöhe? → blockt, +## Ray 3 (über der Kante abwärts) → neue Standfläche messen, +## Decken-Strahl → Platz für die Kapsel über der Stufe? +## ------------------------------------------------------------------ +func _capsule_half_height() -> float: + for child in get_children(): + var cs := child as CollisionShape3D + if cs != null and cs.shape is CapsuleShape3D: + return (cs.shape as CapsuleShape3D).height * 0.5 + return 0.9 + + +func try_step_up(wish: Vector3) -> bool: + var dir := Vector3(wish.x, 0.0, wish.z) + if not is_on_floor() or dir.length_squared() < 0.0001: + return false + dir = dir.normalized() + var space := get_world_3d().direct_space_state + if space == null: + return false + var half_h := _capsule_half_height() + var feet := global_position.y - half_h + var mask := collision_mask # nur die Welt tasten, nicht den eigenen Körper + var exclude: Array[RID] = [get_rid()] + + # Ray 1: vorwärts in Knöchelhöhe — gibt es überhaupt eine Stufe/Wand? + var ankle := global_position + Vector3(0.0, -half_h + 0.05, 0.0) + var q1 := PhysicsRayQueryParameters3D.create( + ankle + dir * 0.1, ankle + dir * STEP_PROBE_DIST, mask, exclude) + var hit1 := space.intersect_ray(q1) + if hit1.is_empty(): + return false + if (hit1["normal"] as Vector3).dot(dir) > -0.3: + return false # Fläche zeigt nicht gegen die Laufrichtung (Rampe/Kante) + + # Ray 2: vorwärts knapp ÜBER der maximalen Stufenhöhe — trifft er die + # Wand noch, ist sie höher als STEP_HEIGHT → kein Anheben. + var top_probe := global_position + Vector3(0.0, -half_h + STEP_HEIGHT + 0.01, 0.0) + var q2 := PhysicsRayQueryParameters3D.create( + top_probe + dir * 0.1, top_probe + dir * STEP_PROBE_DIST, mask, exclude) + if not space.intersect_ray(q2).is_empty(): + return false # Wand überragt die Stufenhöhe + + # Ray 3: über der Kante abwärts — neue Standfläche messen (Startpunkt + # liegt über Ray 2 frei, also außerhalb der Wand). + var edge := (hit1["position"] as Vector3) + dir * 0.1 + var q3 := PhysicsRayQueryParameters3D.create( + Vector3(edge.x, feet + STEP_HEIGHT + 0.05, edge.z), + Vector3(edge.x, feet - 0.05, edge.z), mask, exclude) + var hit3 := space.intersect_ray(q3) + if hit3.is_empty(): + return false + var new_feet := (hit3["position"] as Vector3).y + if new_feet < feet - 0.01: # Abgrund statt Fläche? Nichts anheben. + return false + var diff := new_feet - feet + if diff > STEP_HEIGHT + 0.005 or diff <= 0.0: + return false + if (hit3["normal"] as Vector3).y < 0.5: + return false # keine begehbare Fläche (Decke/Schräge von unten) + + # Decken-Check: Platz für die volle Kapselhöhe über der neuen Standfläche? + # An der Ziel-XZ prüfen (etwas vorwärts — dort landt die Kapsel nach dem + # Schritt), sonst entgeht die Terrassen-Decke über dem Stufenkopf. + var check := global_position + dir * 0.45 + var q4 := PhysicsRayQueryParameters3D.create( + Vector3(check.x, new_feet + 0.05, check.z), + Vector3(check.x, new_feet + half_h * 2.0 + 0.05, check.z), + mask, exclude) + if not space.intersect_ray(q4).is_empty(): + return false # Decke zu tief (z. B. Terrassen-Untersicht): unten bleiben + + # Sanftes Anheben: Positions-Snap auf die neue Standfläche … + global_position.y = new_feet + half_h + STEP_LIFT_EPS + # … und Bewegung fortführen: der zweite move_and_slide()-Schritt trägt + # die Kapsel VOR die Kante auf die neue Stufe (sonst steht sie nur + # tangential auf der Kante und fällt zurück). + move_and_slide() + return true + + +## Maus-Bewegung (nur der Spieler selbst fängt sie — die Editor-Werkzeuge +## sind im Lauf-Modus eingefroren). +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventMouseMotion: + var mm := event as InputEventMouseMotion + yaw_deg = wrapf(yaw_deg - mm.relative.x * mouse_sens, -180.0, 180.0) + pitch_deg = clampf(pitch_deg - mm.relative.y * mouse_sens, + -PITCH_LIMIT, PITCH_LIMIT) + _apply_look() + get_viewport().set_input_as_handled() + + +## ESC-Request — wird von main.gd angerufen (die Esc-Priorität gehört dort +## hin, damit die normale Esc-Kette NICHT auch noch zieht). +func request_exit() -> void: + exit_requested.emit() + + +## Terrain-Raster EINMALIG aus dem Dokument dekodieren (R16/LE/Base64 → +## Meter-Array). Nutzt den Standard-Decoder SvmapIO.decode_heights (derselbe +## Code wie beim Editor-Rendering) — nichts neu rechnen. Die PHYSIK läuft +## über den Trimesh-Kollider (KolliderBau.terrain_form, identisch zum +## Client); dieses Raster dient Spawn/jump_to_spawn/HUD-Referenz. +func set_terrain(d: Dictionary) -> void: + doc = d + heights = PackedFloat32Array() + res = 0 + size_x = 0.0 + size_z = 0.0 + hmin = 0.0 + hmax = 0.0 + if d.is_empty(): + return + var terrain: Dictionary = d.get("terrain", {}) + res = int(terrain.get("resolution", 0)) + size_x = float(terrain.get("size_x_m", 0.0)) + size_z = float(terrain.get("size_z_m", 0.0)) + hmin = float(terrain.get("min_height_m", 0.0)) + hmax = float(terrain.get("max_height_m", 0.0)) + if res < 2 or size_x <= 0.0 or size_z <= 0.0: + return + var decoded: PackedFloat32Array = SvmapIO.decode_heights(terrain) + if decoded.size() < res * res: + return + heights = decoded + + +## Teleport zum Punkt unter dem Fadenkreuz (Karte t_96e3c4ee, Taste T im +## Testlauf): Strahl aus dem Auge gegen die ECHTE Physik-Welt (Trimesh- +## Terrain + Objekt-/Bauteil-Kollider — dieselben Flächen wie im Spiel). +## Gelandet wird auf dem Boden DARUNTER (Strahl senkrecht abwärts). Nie im +## Objekt landen: ist die Kapsel am Zielpunkt nicht frei, wird der Teleport +## abgelehnt (ok=false). Rückgabe: {ok, x, z, ground, msg}. +func teleport_to_ground(origin: Vector3, dir: Vector3) -> Dictionary: + var space := get_world_3d().direct_space_state + if space == null: + return {"ok": false, "x": 0.0, "z": 0.0, "ground": 0.0, + "msg": "Kein Boden unterm Fadenkreuz — hier kann der Test-Start nicht hin."} + var d := dir + if d.length_squared() > 0.000000001: + d = d.normalized() + var q := PhysicsRayQueryParameters3D.create(origin, origin + d * TELEPORT_REICHWEITE) + q.exclude = [get_rid()] + var hit := space.intersect_ray(q) + if hit.is_empty(): + return {"ok": false, "x": 0.0, "z": 0.0, "ground": 0.0, + "msg": "Kein Boden unterm Fadenkreuz — hier kann der Test-Start nicht hin."} + var ziel := hit["position"] as Vector3 + # Boden DARUNTER: senkrechter Strahl ab etwas über dem Treffer — trifft + # er nichts (Treffer war eine Wandfläche ohne Boden drunter), Terrain- + # Raster als Rückfallebene. + var ground := ziel.y + var q2 := PhysicsRayQueryParameters3D.create( + Vector3(ziel.x, ziel.y + 0.5, ziel.z), + Vector3(ziel.x, ziel.y - 50.0, ziel.z)) + q2.exclude = [get_rid()] + var hit2 := space.intersect_ray(q2) + if hit2.is_empty(): + ground = terrain_height(ziel.x, ziel.z) + else: + ground = (hit2["position"] as Vector3).y + var tx := ziel.x + var tz := ziel.z + # Landeplatz prüfen: passt die Kapsel dort frei? (Nie im Objekt landen.) + var probe := CapsuleShape3D.new() + probe.radius = KAPSEL_RADIUS + probe.height = KAPSEL_HEIGHT + var qp := PhysicsShapeQueryParameters3D.new() + qp.shape = probe + qp.transform = Transform3D(Basis.IDENTITY, + Vector3(tx, ground + HALF_H + 0.02, tz)) + qp.exclude = [get_rid()] + if not space.intersect_shape(qp, 1).is_empty(): + return {"ok": false, "x": tx, "z": tz, "ground": ground, + "msg": "Da steht etwas im Weg (%.1f, %.1f) — dort kannst du nicht landen." % [tx, tz]} + position = Vector3(tx, ground + HALF_H + 0.02, tz) + velocity = Vector3.ZERO + on_floor = true + _apply_look() + return {"ok": true, "x": tx, "z": tz, "ground": ground, "msg": ""} + + +## Terrain-Höhe an Welt-X/Z — aus dem DOCUMENT-Raster (nichts neu rechnen). +## Referenz für Spawn/jump_to_spawn/HUD; die Physik nutzt den Trimesh. +func terrain_height(x: float, z: float) -> float: + if res < 2 or size_x <= 0.0 or size_z <= 0.0: + return hmin + if heights.size() < res * res: + return hmin + var last := float(res - 1) + var fx := clampf((x / size_x + 0.5) * last, 0.0, last) + var fz := clampf((z / size_z + 0.5) * last, 0.0, last) + var xi := int(floor(fx)) + var zi := int(floor(fz)) + var tx := fx - float(xi) + var tz := fz - float(zi) + var xi1 := mini(xi + 1, res - 1) + var zi1 := mini(zi + 1, res - 1) + var h00 := _sample(xi, zi) + var h10 := _sample(xi1, zi) + var h01 := _sample(xi, zi1) + var h11 := _sample(xi1, zi1) + return lerpf(lerpf(h00, h10, tx), lerpf(h01, h11, tx), tz) + + +func _sample(xi: int, zi: int) -> float: + var idx := zi * res + xi + if idx < 0 or idx >= heights.size(): + return hmin + return heights[idx] + + +## Maus-Empfindlichkeit als Multiplikator setzen (Karte t_aaff5dcc, 0,1–5,0, +## Standard 1,0): die Body-Basis (DEFAULT_MOUSE_SENS Grad/Pixel) wird mit dem +## Multiplikator skaliert — jede PC/Maus-Kombination ist anders. +func set_sensitivity(mult: float) -> void: + var m := clampf(mult, PlaytestSettings.MIN_SENS, PlaytestSettings.MAX_SENS) + mouse_sens = DEFAULT_MOUSE_SENS * m + + +# ------------------------------------------ Avatar + Perspektive (V) ------- +# Karte t_17a35c59: derselbe Avatar wie die Bots im Spiel (Spiegel-Modul +# playtest_avatar.gd, GLB aus dem Nachbar-Klon), umschaltbar First/Third. + +## Avatar (aus PlaytestAvatar.lade_avatar) anhängen. Anker: Kopf-Anker- +## Konvention des Clients (bot_avatar.gd) — Avatar-Node in Augenhöhe +## (MODEL_EYE_HEIGHT 1,65 m) über dem Füßpunkt, GLB-Kind hängt mit +## MODEL_Y_OFFSET nach unten. null = kein Modell (Third bleibt gesperrt). +func set_avatar(node: Node3D) -> void: + if avatar != null and is_instance_valid(avatar): + avatar.queue_free() + avatar = node + if avatar == null: + perspektive_dritte = false + _apply_perspektive() + return + # add_child reparentet NICHT — ein Node mit altem Parent würde still + # abgelehnt (Probe 20.10.: Avatar blieb unter root). Erst lösen, dann + # als Kind anhängen. + if avatar.get_parent() != null: + avatar.get_parent().remove_child(avatar) + add_child(avatar) + avatar.position = Vector3(0.0, + PlaytestAvatar.MODEL_EYE_HEIGHT - HALF_H, 0.0) + # Start-Zustand sauber in den Baum schreiben (apply_state vor add_child + # könnte Tree-Parameter noch nicht flushen). + avatar.call("apply_state", PlaytestAvatar.STATE_IDLE, 0.0) + _apply_perspektive() + + +## Taste V: Perspektive umschalten. Rückgabe: neuer Zustand (true = Third). +## Ohne Avatar bleibt First Person aktiv (graceful Fallback — Meldung macht +## der Aufrufer/PlaytestMode). +func toggle_perspektive() -> bool: + var neu := not perspektive_dritte + if avatar == null: + neu = false + return set_perspektive(neu) + + +## Perspektive direkt setzen (Enter-Pfad liest PlaytestSettings). Rückgabe: +## der tatsächlich aktive Zustand (ohne Avatar nie Third). +func set_perspektive(dritte: bool) -> bool: + if avatar == null: + dritte = false + perspektive_dritte = dritte + _apply_perspektive() + return perspektive_dritte + + +## Sichtbarkeit + aktive Kamera je Perspektive: +## First Person — Avatar unsichtbar (Kapsel bleibt physisch aktiv, +## Kamera auf Augenhöhe wie Client-TestmapPlayer), +## Third Person — Avatar sichtbar, SpringArm-Kamera hinter/über dem +## Spieler mit Wand-Kollision (kein Clipping). +func _apply_perspektive() -> void: + if avatar != null and is_instance_valid(avatar): + avatar.visible = perspektive_dritte + if look_cam != null: + look_cam.current = not perspektive_dritte + if tpp_cam != null: + tpp_cam.current = perspektive_dritte + + +## Echte horizontale Geschwindigkeit (m/s) aus dem letzten Physik-Schritt — +## Quelle für die Avatar-Gait-Speisung (Stillstand/Walk/Jog aus der realen +## Bewegung, nicht aus dem Input-Wunsch). +func horizontale_geschwindigkeit() -> float: + var v := velocity + v.y = 0.0 + return v.length() diff --git a/editor/playtest/playtest_body.gd.uid b/editor/playtest/playtest_body.gd.uid new file mode 100644 index 0000000..035ed25 --- /dev/null +++ b/editor/playtest/playtest_body.gd.uid @@ -0,0 +1 @@ +uid://csv8o2g8wdtue diff --git a/editor/playtest/playtest_mode.gd b/editor/playtest/playtest_mode.gd new file mode 100644 index 0000000..c43c4bc --- /dev/null +++ b/editor/playtest/playtest_mode.gd @@ -0,0 +1,536 @@ +extends Node +## Lauf-Modus (Karte t_1cdc1a72): erste Person IM Map-Builder selbst — +## kein Client-Ordner, kein zweiter Prozess, kein VR/OpenXR, kein Server. +## +## Verantwortung dieses Zustands: +## - Beim Start: die map_view_builder-Ausgabe (Modelle + Pack-Texturen) +## 1:1 weiterverwenden (der Node3D-Baum bleibt stehen, nur Editor-UI/Input +## geht weg), Maus-Capture-Wunsch setzen, Panels ausblenden. +## - Kollider-Bau via KolliderBau (core/svmap/kollider_bau.gd — 1:1-Spiegel +## des Quest-Clients, Karte t_1da96275): Terrain-Trimesh + Objekt-Kollider +## (box/kugel/zylinder/verbund/trimesh inkl. library/kollisions-Quellen) +## + Bauteil-Trimeshe (bau_*-Objekte: Treppen/Türen/Wände begehbar +## wie echte Geometrie). +## - PlaytestBody (CharacterBody3D, IDENTISCHE Parameter wie der +## Testmap-Spieler inkl. Step-Up 0.45 m) anlegen, Terrain-Raster EINMAL +## dekodieren, Start = erster Spawnpunkt (Team Blau), sonst Kartenmitte. +## - ESC → exit_requested → main.gd stellt den Editor-Zustand 1:1 wieder her +## (Panels, Maus, Kamera, Auswahl, Dokument, Undo-Stapel unverändert). +## - HUD (Position, FPS, „ESC = zurück zum Editor") als CanvasLayer-Overlay. +## +## Bewusst KEIN _physics_process hier: alle Bewegung/Eingabe lebt im +## PlaytestBody; dieser Zustand managed nur Lifecycle + HUD. + +const BODY_SCRIPT := "res://core/editor/playtest/playtest_body.gd" +const KOLLIDER_SCRIPT := "res://core/svmap/kollider_bau.gd" +const AVATAR_SCRIPT := "res://core/editor/playtest/playtest_avatar.gd" + +## Der laufende Spieler (PlaytestBody, CharacterBody3D mit echter Physik) +var body: Node3D = null +## Welt-Kollider (Terrain + Objekte + Bauteile) — Kind dieses Modus, +## wird mit exit() abgeräumt. +var _kollider_root: Node3D = null + +## Editor-UI-Verweise (vom main.gd-Handler gesetzt): die Panels, die +## AUSGEBLENDET werden sollen (Variant statt Control-Typ — keine UI-Kopie). +var ui_panels: Array = [] # Array[Control] +var ui_status: Variant = null # Statuszeilen-Label (nur visible umschalten) +var help_label: Variant = null # Hilfe-Label (nur visible umschalten) + +## Maus-Capture-Wunsch (Suite testet diesen Zustand) +var _want_capture := false +## HUD-Elemente +var hud: CanvasLayer = null +var hud_pos: Label = null +var hud_fps: Label = null +var hud_hint: Label = null +var hud_msg: Label = null # Klartext-Nachricht (z. B. Test-Start via T) +var hud_dir: Label = null # Blickrichtung + Höhe (Karte t_aaff5dcc) +## Hilfe-Overlay (F1, Karte t_aaff5dcc): alle Tasten im Klartext. +var help_layer: CanvasLayer = null +## Kartenmitte/Achsen-Linien (Taste X, Karte t_aaff5dcc — praktisch beim +## gespiegelten Bauen, Karte M1): X-Rot/Z-Blau/Y-Grün durch die Kartenmitte. +var _axis_node: Node3D = null +## FPS-Smoothing (Anzeige, keine Physik) +var _fps_smooth := 0.0 +## Ist der Lauf-Modus gerade aktiv? (Reentrancy-Schutz für enter/exit) +var active := false +## Avatar-Modelle waren beim Start verfügbar? (Karte t_17a35c59 — ohne +## Client-Klon bleibt Third Person gesperrt, graceful Fallback.) +var _avatar_da := false + +signal exit_requested + + +## Starten: übernimmt die Sicht (Panels aus, Maus-Capture-Wunsch an) und +## legt den Spieler am Startpunkt an. Fallback-Reihenfolge (Karte t_96e3c4ee, +## je mit Klartext-Hinweis): gesetzter Test-Start (start_override) → erster +## Spawnpunkt (Team Blau) → Kartenmitte. start_override = Vector3 (nur x/z +## zählen, Höhe kommt automatisch vom Terrain) oder null (= altes Verhalten). +## view_builder = der map_view_builder-Node (Marker-Tree mit pick_radius/ +## half_height-Metas bleibt stehen und liefert die Hindernis-Blöcke). +func enter(doc: Dictionary, view_builder: Node3D, start_override: Variant = null) -> void: + if active: + return + active = true + _set_ui_visible(false) + _set_status_capture(true) + + body = load(BODY_SCRIPT).new() + body.call("set_terrain", doc) + var start := _spawn_start(doc) + if start_override != null: + var sp := start_override as Vector3 + start = Vector3(sp.x, 0.0, sp.z) + # Spawn AUF der Terrainhöhe: Kapsel-Mitte = Füße + Halbhöhe (+ Toleranz, + # die Physik lässt den Body sanft auf den Trimesh fallen — wie im Spiel). + body.position = Vector3(start.x, + body.terrain_height(start.x, start.z) + PlaytestBody.HALF_H + 0.05, + start.z) + if start_override == null: + var first := _first_spawn(doc) + if first.has(SpawnOps.YAW_KEY): + body.yaw_deg = float(first[SpawnOps.YAW_KEY]) + # Kollider 1:1 vom Quest-Client (Karte t_1da96275): Terrain-Trimesh + + # Objekt-Kollider aus der Registry + Bauteil-Trimeshe. Alles als Kind + # dieses Modus — exit() räumt es mit ab. + _baue_kollider(doc) + add_child(body) + # Maus-Empfindlichkeit aus den Einstellungen (Karte t_aaff5dcc): der + # gespeicherte Multiplikator (0,1–5,0) skaliert die Body-Basis. + body.call("set_sensitivity", PlaytestSettings.load_sens()) + + # Playtest-Avatar (Karte t_17a35c59): derselbe Rig wie die Bots im Spiel + # (GLB aus dem Nachbar-Klon ../scopedvr-client). Fehlt der Klon/Modell: + # graceful — First Person bleibt, Third Person wird per Meldung gesperrt. + var avatar: Node3D = load(AVATAR_SCRIPT).call("lade_avatar") as Node3D + _avatar_da = avatar != null + if _avatar_da: + body.call("set_avatar", avatar) + # Persistierte Perspektive (Taste V): Third nur mit Avatar — sonst + # First Person wie bisher + Klartext-Hinweis. + if PlaytestSettings.load_third(): + if _avatar_da: + body.call("set_perspektive", true) + else: + show_message("Gespeicherte Third-Person-Einstellung ist ohne Avatar-Modelle nicht verfügbar (Client-Klon ../scopedvr-client fehlt) — First Person aktiv.") + + _build_hud() + exit_requested.connect(_on_exit_requested, CONNECT_ONE_SHOT) + + +## Zurück in den Editor: HUD weg, Spieler weg, Panels sichtbar. +## Editor-Zustand (Kamera, Auswahl, Dokument, Undo-Stapel) wird NICHT hier +## angefasst — das macht main.gd nach dem Signal. +func exit() -> void: + if not active: + return + active = false + _set_status_capture(false) + _set_ui_visible(true) + if body != null: + body.queue_free() + body = null + if _kollider_root != null: + _kollider_root.queue_free() + _kollider_root = null + if hud != null: + hud.queue_free() + hud = null + if help_layer != null: + help_layer.queue_free() + help_layer = null + if _axis_node != null: + _axis_node.queue_free() + _axis_node = null + + +func _on_exit_requested() -> void: + exit() + + +## Status/Maus-Capture-Wunsch: nur der Zustand (Suite testet wants_capture). +func _set_status_capture(want: bool) -> void: + _want_capture = want + + +## Panelselemente ausblenden/einblenden. Nur die als ui_panels übergebenen +## Controls werden angefasst — keine weiteren. +func _set_ui_visible(vis: bool) -> void: + for p in ui_panels: + if p is Control: + (p as Control).visible = vis + if ui_status is Control: + (ui_status as Control).visible = vis + if help_label is Control: + (help_label as Control).visible = vis + + +## Start-Punkt: erster Spawn-Punkt aus dem Dokument (spawn_zones-Team „0", +## erster Eintrag der points-Liste); sonst Kartenmitte aus den Bounds. +## doc-Format (svmap): {"spawn_zones": {"0": {"points": [{"id", "pos": [x,y,z]}]}, ...}} +func _first_spawn(doc: Dictionary) -> Dictionary: + var team0 = (doc.get("spawn_zones", {}) as Dictionary).get("0", {}) + if team0 is Dictionary: + var points: Array = (team0 as Dictionary).get("points", []) + if not points.is_empty() and points[0] is Dictionary: + return points[0] + return {} + + +func _spawn_start(doc: Dictionary) -> Vector3: + var zones: Dictionary = doc.get("spawn_zones", {}) + var team0 = zones.get("0", {}) + if team0 is Dictionary: + var points: Array = (team0 as Dictionary).get("points", []) + if not points.is_empty() and points[0] is Dictionary: + var pos = (points[0] as Dictionary).get("pos", []) + if pos is Array and (pos as Array).size() >= 3: + return Vector3(float(pos[0]), 0.0, float(pos[2])) + var bounds: Dictionary = doc.get("bounds", {}) + var mn: Array = bounds.get("min", [-50.0, -10.0, -50.0]) + var mx: Array = bounds.get("max", [50.0, 80.0, 50.0]) + return Vector3((float(mn[0]) + float(mx[0])) * 0.5, 0.0, + (float(mn[2]) + float(mx[2])) * 0.5) + + +## Echter Kollider-Bau (Karte t_1da96275 — „Editor-Playtest-Physik = Quest 3 +## identisch"): der alte AABB-Blocking-Code ist ENTFERNT. Es gibt genau EINEN +## Kollider-Pfad, denselben wie der Quest-Client: +## - Terrain + Registry-Objekte: KolliderBau.baue_welt_kollider (1:1-Spiegel +## von svmap_world.gd — box/kugel/zylinder/verbund/trimesh inkl. +## library/kollisions-Quelldateien). +## - Bauteile (bau_*) sind normale Registry-Objekte mit Trimesh aus +## library/kollisions — Türen/Fenster bleiben Durchgänge, Treppen begehbar. +func _baue_kollider(doc: Dictionary) -> void: + var kb := load(KOLLIDER_SCRIPT) + _kollider_root = kb.call("baue_welt_kollider", doc) + add_child(_kollider_root) + + +# ------------------------------------------------------------------- HUD ---- + +func _build_hud() -> void: + hud = CanvasLayer.new() + hud.layer = 100 + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 16) + margin.add_theme_constant_override("margin_top", 16) + margin.set_anchors_preset(Control.PRESET_TOP_LEFT) + hud.add_child(margin) + var vbox := VBoxContainer.new() + margin.add_child(vbox) + hud_pos = Label.new() + hud_fps = Label.new() + hud_hint = Label.new() + hud_msg = Label.new() + hud_dir = Label.new() + hud_hint.text = "WASD + Maus · Shift = rennen · Space = springen/steigen · G = Fliegen · V = 1./3.-Person · 1-4 = Spawnpunkt · T = Test-Start · H = HUD · X = Achsen · F1 = Hilfe · ESC = zurück zum Editor" + hud_msg.text = "" + for l: Label in [hud_pos, hud_dir, hud_fps, hud_hint, hud_msg]: + l.add_theme_font_size_override("font_size", 16) + l.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0)) + vbox.add_child(l) + add_child(hud) + + +## Klartext-Nachricht im HUD (die Editor-Statuszeile ist im Lauf-Modus +## ausgeblendet — hier landet das Feedback für Taste T, Karte t_96e3c4ee). +func show_message(text: String) -> void: + if hud_msg != null and is_instance_valid(hud_msg): + hud_msg.text = text + + +## Taste T im Testlauf (Karte t_96e3c4ee, „jederzeit“): setzt den Test-Start +## auf den Punkt unterm Fadenkreuz und versetzt den Spieler SOFORT dorthin +## (Augenhöhe 1,70 m via look_cam-Offset, Kollision geprüft — nie in Objekten). +## Das Speichern (Sidecar) + Marker-Update macht main.gd mit der Rückgabe. +## Rückgabe: {ok, x, z, ground, msg} aus PlaytestBody.teleport_to_ground. +func teleport_crosshair() -> Dictionary: + if not active or body == null or body.look_cam == null: + return {"ok": false, "x": 0.0, "z": 0.0, "ground": 0.0, "msg": ""} + var vp: Viewport = body.look_cam.get_viewport() + var center: Vector2 = (vp.get_visible_rect().size if vp != null else Vector2(800, 600)) * 0.5 + var origin: Vector3 = body.look_cam.project_ray_origin(center) + var dir: Vector3 = body.look_cam.project_ray_normal(center) + var res: Dictionary = body.call("teleport_to_ground", origin, dir) + if bool(res.get("ok")): + show_message("Test-Start gesetzt: x=%.0f z=%.0f — du bist jetzt dort (Höhe automatisch)." % [ + float(res.get("x")), float(res.get("z"))]) + else: + show_message(str(res.get("msg", ""))) + return res + + +func _process(delta: float) -> void: + if not active or body == null: + return + _speise_avatar() + # FPS glätten (Anzeige-Genauigkeit, keine Physik-Betroffenheit) + if _fps_smooth <= 0.0: + _fps_smooth = 1.0 / maxf(delta, 0.0001) + else: + _fps_smooth = lerpf(_fps_smooth, 1.0 / maxf(delta, 0.0001), 0.1) + if hud_pos != null: + hud_pos.text = "Pos: %.1f, %.1f, %.1f" % [body.position.x, body.position.y, body.position.z] + if hud_dir != null: + # Höhe = FUSSPUNKT (Kapsel-Mitte minus Halbhöhe) — direkt vergleichbar + # mit der Terrainhöhe unter den Füßen (Karte t_1da96275). + hud_dir.text = "Blick: %.0f° / %.0f° · Höhe: %.1f m" % [ + float(body.get("yaw_deg")), float(body.get("pitch_deg")), + body.position.y - PlaytestBody.HALF_H] + if hud_fps != null: + hud_fps.text = "FPS: %d" % int(round(_fps_smooth)) + + +## Maus-Capture-Wunsch für die Suite/Debug sichtbar machen. +func wants_capture() -> bool: + return _want_capture + + +## ESC → Signal an main.gd (die Esc-Priorität gehört in main.gd, damit die +## normale Esc-Kette im Editor-Modus unangetastet bleibt). +func handle_escape() -> void: + if active and body != null: + exit_requested.emit() + + +# ------------------------------------------------- Komfort (t_aaff5dcc) ----- + +## Tasten im Lauf-Modus (Karte t_aaff5dcc): G = Fliegen/God-Mode, 1–4 = +## Sprung zu Spawnpunkt, H = HUD an/aus, F1 = Hilfe, X = Achsen. ESC/T bleiben +## bewusst bei main.gd (Esc-Kette + Test-Start-Vertrag). Die Editor-Tasten +## sind im Lauf-Modus eingefroren (main gibt hier nichts anderes durch). +func _unhandled_input(event: InputEvent) -> void: + if not active or body == null: + return + var key_evt := event as InputEventKey + if key_evt == null or not key_evt.is_pressed() or key_evt.echo: + return + var key := key_evt.keycode + var handled := true + match key: + KEY_G: + toggle_fly() + KEY_V: + toggle_perspektive() + KEY_1: + jump_to_spawn(1) + KEY_2: + jump_to_spawn(2) + KEY_3: + jump_to_spawn(3) + KEY_4: + jump_to_spawn(4) + KEY_H: + toggle_hud() + KEY_F1: + toggle_help() + KEY_X: + toggle_axis() + _: + handled = false + if handled: + get_viewport().set_input_as_handled() + + +## Empfindlichkeit live setzen (aus dem Einstellungen-Panel, Registry-Weg — +## main.gd bleibt unangetastet). mult = Multiplikator 0,1–5,0. +func apply_sensitivity(mult: float) -> void: + if body != null: + body.call("set_sensitivity", mult) + + +## Taste V (Karte t_17a35c59): First/Third-Person umschalten. Der neue +## Zustand wird persistiert (PlaytestSettings, wirksam über Neustarts). +## Ohne Avatar-Modelle (kein Client-Klon ../scopedvr-client) bleibt First +## Person aktiv und der Grund steht im HUD — kein Crash (graceful Fallback). +func toggle_perspektive() -> bool: + if body == null: + return false + var neu: bool = body.call("toggle_perspektive") + if not _avatar_da: + show_message("Kein Avatar-Modell gefunden (Client-Klon ../scopedvr-client fehlt) — Third Person nicht verfügbar.") + return false + PlaytestSettings.save_third(neu) + if neu: + show_message("Dritte Person AN — derselbe Avatar wie die Bots im Spiel. V = zurück zur ersten Person.") + else: + show_message("Erste Person AN (Standard). V = dritte Person.") + return neu + + +## Avatar-Gait aus der ECHTEN Bewegung speisen (Kartenvorgabe: Stillstand/ +## Walk/Jog aus der tatsächlichen Geschwindigkeit, Blickrichtung == +## Bewegungsrichtung über die Body-Rotation). Jeden Frame — die Kadenz- +## Mathe des Avatars glättet selbst (BLEND_SMOOTH_S, wie die Bots). +func _speise_avatar() -> void: + if body == null: + return + var av: Node3D = body.get("avatar") as Node3D + if av == null or not is_instance_valid(av): + return + var speed: float = body.call("horizontale_geschwindigkeit") + av.call("apply_state", PlaytestAvatar.zustand_fuer(speed), speed) + + +## Fliegen / God-Mode umschalten (Taste G). Rückgabe: neuer Zustand. +func toggle_fly() -> bool: + if body == null: + return false + var new_state: bool = not bool(body.get("fly_mode")) + body.set("fly_mode", new_state) + body.set("velocity", Vector3.ZERO) # kein Fall-Übernehmen aus dem Gang + if new_state: + show_message("Fliegen AN (God-Mode): keine Gravitation — Blickrichtung fliegen, Space = steigen, G = aus.") + else: + show_message("Fliegen AUS — wieder mit Gravitation.") + return new_state + + +## Spawnpunkt-Punkt für Taste index (1 = Team A/Zone 0, 2 = Team B/Zone 1, +## 3/4 = weitere Zonen 2/3): erster Punkt der Zone. Rückgabe {} = keine Zone. +## pos = [x,y,z] aus dem Dokument. +func _spawn_point_for(index: int) -> Dictionary: + if body == null: + return {} + var zones: Dictionary = (body.get("doc") as Dictionary).get("spawn_zones", {}) + var zone: Variant = zones.get(str(index - 1)) + if not (zone is Dictionary): + return {} + var points: Array = (zone as Dictionary).get("points", []) + if points.is_empty() or not (points[0] is Dictionary): + return {} + var p := points[0] as Dictionary + var pos: Array = p.get("pos", []) + if pos.size() < 3: + return {} + return {"pos": Vector3(float(pos[0]), float(pos[1]), float(pos[2])), + "label": str((zone as Dictionary).get("label", "Zone %d" % index)), + "yaw": p.get(SpawnOps.YAW_KEY, null)} + + +## Sprung zu einem Spawnpunkt (Tasten 1–4): exakt auf den Punkt gesetzt +## (x/z aus dem Dokument, Höhe vom Terrain — dieselbe Konvention wie der +## Lauf-Modus-Start). Rückgabe true = gelandet. +func jump_to_spawn(index: int) -> bool: + var sp := _spawn_point_for(index) + if sp.is_empty() or body == null: + show_message("Kein Spawnpunkt für Taste %d — die Karte hat diese Zone nicht." % index) + return false + var target: Vector3 = sp["pos"] + var y: float = body.terrain_height(target.x, target.z) + # Kapsel-Mitte = Füße + Halbhöhe (+ Toleranz) — die Physik lässt den Body + # sanft auf den Trimesh fallen (wie beim Start, Karte t_1da96275). + body.set("position", Vector3(target.x, y + PlaytestBody.HALF_H + 0.02, target.z)) + body.set("velocity", Vector3.ZERO) + body.set("on_floor", true) + if sp["yaw"] != null: + body.set("yaw_deg", float(sp["yaw"])) + show_message("Spawnpunkt %d = %s: x=%.1f z=%.1f (Höhe %.1f) — Blickrichtung steht im HUD." % [ + index, str(sp["label"]), target.x, target.z, y]) + return true + + +## HUD (Position/Blick/FPS/Hinweis) an/aus (Taste H). Rückgabe: sichtbar? +func toggle_hud() -> bool: + if hud == null: + return false + hud.visible = not hud.visible + if hud.visible: + show_message("HUD an.") # aus: Meldung wäre selbst unsichtbar + return hud.visible + + +## Hilfe-Overlay an/aus (Taste F1). Rückgabe: sichtbar? +func toggle_help() -> bool: + if help_layer == null: + _build_help() + help_layer.visible = not help_layer.visible + return help_layer.visible + + +## Kartenmitte/Achsen-Linien an/aus (Taste X). Rückgabe: sichtbar? +func toggle_axis() -> bool: + if _axis_node == null: + _build_axis() + _axis_node.visible = not _axis_node.visible + if _axis_node.visible: + show_message("Kartenmitte/Achsen AN (X = aus): X-Rot, Z-Blau, Mitte Grün.") + else: + show_message("Kartenmitte/Achsen AUS.") + return _axis_node.visible + + +## Hilfe-Overlay (F1): Panel mit allen Tasten im Klartext, mittig unten. +func _build_help() -> void: + help_layer = CanvasLayer.new() + help_layer.layer = 101 + var center := CenterContainer.new() + center.set_anchors_preset(Control.PRESET_FULL_RECT) + center.mouse_filter = Control.MOUSE_FILTER_IGNORE + help_layer.add_child(center) + var panel := PanelContainer.new() + center.add_child(panel) + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 16) + margin.add_theme_constant_override("margin_right", 16) + margin.add_theme_constant_override("margin_top", 12) + margin.add_theme_constant_override("margin_bottom", 12) + panel.add_child(margin) + var label := Label.new() + label.text = "TESTLAUF — ALLE TASTEN +WASD — bewegen · Maus — umsehen +Shift — rennen · Space — springen / im Flug steigen +G — Fliegen (God-Mode) an/aus: keine Gravitation, Blickrichtung fliegen +V — Perspektive umschalten (erste / dritte Person) +1 — Sprung zu Team A (Spawnpunkt) · 2 — Team B +3 / 4 — weitere Spawnpunkte (Zonen 2/3) +T — Test-Start auf den Blickpunkt setzen (sofort dort) +H — HUD an/aus (Position, Blick, FPS, Höhe) +X — Kartenmitte/Achsen ein/aus (X-Rot, Z-Blau) +F1 — diese Hilfe · ESC — zurück zum Editor" + label.add_theme_font_size_override("font_size", 18) + margin.add_child(label) + add_child(help_layer) + help_layer.visible = false + + +## Achsen-Linien durch die Kartenmitte (Taste X): X-Achse rot, Z-Achse blau, +## senkrechte Mitte grün — beim gespiegelten Bauen (M1) auf einen Blick. +func _build_axis() -> void: + var doc: Dictionary = (body.get("doc") if body != null else {}) as Dictionary + var bounds: Dictionary = doc.get("bounds", {}) + var mn: Array = bounds.get("min", [-50.0, -10.0, -50.0]) + var mx: Array = bounds.get("max", [50.0, 80.0, 50.0]) + var cx := (float(mn[0]) + float(mx[0])) * 0.5 + var cz := (float(mn[2]) + float(mx[2])) * 0.5 + var y0: float = body.terrain_height(cx, cz) if body != null else 0.0 + var mesh := ImmediateMesh.new() + mesh.surface_begin(Mesh.PRIMITIVE_LINES) + # X-Achse (rot): ganze Kartenbreite durch die Mitte. + mesh.surface_set_color(Color(1.0, 0.25, 0.25)) + mesh.surface_add_vertex(Vector3(float(mn[0]), y0, cz)) + mesh.surface_add_vertex(Vector3(float(mx[0]), y0, cz)) + # Z-Achse (blau): ganze Kartentiefe durch die Mitte. + mesh.surface_set_color(Color(0.3, 0.5, 1.0)) + mesh.surface_add_vertex(Vector3(cx, y0, float(mn[2]))) + mesh.surface_add_vertex(Vector3(cx, y0, float(mx[2]))) + # Senkrechte Mitte (grün): vom Terrain bis zur oberen Grenze. + mesh.surface_set_color(Color(0.3, 1.0, 0.4)) + mesh.surface_add_vertex(Vector3(cx, y0, cz)) + mesh.surface_add_vertex(Vector3(cx, float(mx[1]), cz)) + mesh.surface_end() + var mi := MeshInstance3D.new() + mi.name = "PlaytestAchsen" + mi.mesh = mesh + # Vertexfarben sichtbar machen: unshaded + vertex_color_use_as_albedo + # (ohne das ignoriert der Default-Material-Shader die Linien-Farben). + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.vertex_color_use_as_albedo = true + mat.no_fog = true + mi.material_override = mat + mi.position = Vector3.ZERO + _axis_node = mi + add_child(_axis_node) diff --git a/editor/playtest/playtest_mode.gd.uid b/editor/playtest/playtest_mode.gd.uid new file mode 100644 index 0000000..76bf77a --- /dev/null +++ b/editor/playtest/playtest_mode.gd.uid @@ -0,0 +1 @@ +uid://spymwdq2qcvr diff --git a/editor/playtest/playtest_settings.gd b/editor/playtest/playtest_settings.gd new file mode 100644 index 0000000..a4f4c58 --- /dev/null +++ b/editor/playtest/playtest_settings.gd @@ -0,0 +1,84 @@ +extends RefCounted +class_name PlaytestSettings +## Einstellungen-Speicher für den Lauf-Modus (Karte t_aaff5dcc, T3 Komfort) — +## bewusst NICHT im .svmap-Format und NICHT in main.gd: der Operator hat für +## Einstellungen den Registry-Weg vorgegeben (Panel meldet sich in +## core/ui/panels/registry.gd an), der Zustand lebt HIER als statischer Store. +## +## Ablage: user://scopedvr_playtest.cfg, Abschnitt [playtest]. +## Wert: Maus-Empfindlichkeit als Multiplikator (0,1–5,0, Standard 1,0) auf +## die Body-Basis 0,25 Grad/Pixel — jede PC/Maus-Kombination ist anders. +## +## Statischer Cache: ein Lese-Vorgang pro Editor-Start; save_sens() hält den +## Cache frisch. Tests können mit reset_cache() neu laden erzwingen. + +const CFG_PATH := "user://scopedvr_playtest.cfg" +const SECTION := "playtest" +const KEY_SENS := "maus_empfindlichkeit" +const DEFAULT_SENS := 1.0 +const MIN_SENS := 0.1 +const MAX_SENS := 5.0 +## Perspektive im Testlauf (Karte t_17a35c59, Taste V): false = First Person +## (Standard, wie bisher), true = Third Person (Avatar sichtbar). +const KEY_THIRD := "dritte_person" + +## Cache (null = noch nicht gelesen). static var ist Godot 4.1+. +static var _cache: Variant = null +## Cache für den Perspektive-Eintrag (null = noch nicht gelesen). +static var _cache_third: Variant = null + + +## Gespeicherte Empfindlichkeit lesen (geclampt auf 0,1–5,0), Standard 1,0. +static func load_sens() -> float: + if _cache != null: + return float(_cache) + var cfg := ConfigFile.new() + var raw: Variant = DEFAULT_SENS + if cfg.load(CFG_PATH) == OK: + raw = cfg.get_value(SECTION, KEY_SENS, DEFAULT_SENS) + var val := clampf(float(raw), MIN_SENS, MAX_SENS) + _cache = val + return val + + +## Empfindlichkeit speichern (geclampt auf 0,1–5,0) und Cache frisch halten. +## Rückgabe: der tatsächlich gespeicherte Wert. +static func save_sens(value: float) -> float: + var val := clampf(value, MIN_SENS, MAX_SENS) + var cfg := ConfigFile.new() + cfg.load(CFG_PATH) # Fehler → leere Config, nur unser Key wird angelegt. + cfg.set_value(SECTION, KEY_SENS, val) + cfg.save(CFG_PATH) + _cache = val + return val + + +## Gespeicherte Perspektive lesen (Karte t_17a35c59): true = Third Person. +## Default FALSE = First Person — der Testlauf startet so wie bisher. +static func load_third() -> bool: + if _cache_third != null: + return bool(_cache_third) + var cfg := ConfigFile.new() + var raw: Variant = false + if cfg.load(CFG_PATH) == OK: + raw = cfg.get_value(SECTION, KEY_THIRD, false) + var val := bool(raw) + _cache_third = val + return val + + +## Perspektive speichern (persistiert über Editor-Neustarts) und Cache frisch +## halten. Rückgabe: der tatsächlich gespeicherte Wert. +static func save_third(value: bool) -> bool: + var cfg := ConfigFile.new() + cfg.load(CFG_PATH) # Fehler → leere Config, nur unser Key wird angelegt. + cfg.set_value(SECTION, KEY_THIRD, value) + cfg.save(CFG_PATH) + _cache_third = value + return value + + +## Cache verwerfen (für Tests / frisches Neulesen von der Platte). +static func reset_cache() -> void: + _cache = null + _cache_third = null diff --git a/editor/playtest/playtest_settings.gd.uid b/editor/playtest/playtest_settings.gd.uid new file mode 100644 index 0000000..8751c34 --- /dev/null +++ b/editor/playtest/playtest_settings.gd.uid @@ -0,0 +1 @@ +uid://1m7xfphtjvwd diff --git a/editor/playtest/test_start.gd b/editor/playtest/test_start.gd new file mode 100644 index 0000000..0918428 --- /dev/null +++ b/editor/playtest/test_start.gd @@ -0,0 +1,92 @@ +extends RefCounted +class_name TestStart +## Test-Startpunkt (Karte t_96e3c4ee): der Punkt, an dem „▶ Karte testen“ (F5) +## den Spieler absetzt — KEIN Spiel-Spawnpunkt (taucht nicht in doc, +## Spawn-Checks/Analysen oder Validator auf, ändert keine Zonen). +## +## Bewusste Entscheidungen dieser Karte: +## - Der Punkt lebt NUR editor-seitig (Sidecar, test_start_store.gd) — das +## .svmap-Format (Validator V1–V16, Client/Server) bleibt UNBERÜHRT. +## - Gespeichert wird nur x/z. Die Höhe kommt IMMER automatisch aus dem +## Terrain an der Stelle (SvmapDocument.terrain_height_at) — Terrain +## formen lässt den Punkt nicht schweben und versinken. +## - Fallback-Reihenfolge F5 (je mit Klartext-Hinweis): gesetzter Test-Start +## → erster Team-Spawnpunkt (Team Blau, wie bisher) → Kartenmitte. +## - Trifft der Klick ein Objekt/Bauteil: landet der Punkt auf dem BODEN +## DARUNTER (nie im Objekt schwebend) — das Tool meldet es im Klartext. +## +## Alles pure Statics (kein UI, kein Node-Zugriff) → headless testbar in +## tests/test_playtest_spawn.gd. + +## Kein Punkt gesetzt (Sentinel-Typ für Variant-Vergleiche in main.gd). + +## Start-Punkt für den Testlauf auflösen (Fallback-Kette). test_point ist +## Vector3 (nur x/z zählen) oder null. Rückgabe: +## {pos: Vector3 (y = Terrainhöhe), quelle: "test"|"team"|"mitte", +## status: Klartext für die Statuszeile} +static func resolve_start(doc: Dictionary, test_point: Variant) -> Dictionary: + if test_point != null: + var p: Vector3 = test_point + var y := SvmapDocument.terrain_height_at(doc, p.x, p.z) + return { + "pos": Vector3(p.x, y, p.z), + "quelle": "test", + "status": "Start an deinem gesetzten Test-Startpunkt (%.0f, %.0f, Höhe automatisch)." % [p.x, p.z], + } + # Erster Team-Spawnpunkt (Team Blau = „0“) — exakt wie bisher (t_1cdc1a72). + var zones: Dictionary = doc.get("spawn_zones", {}) + var team0 = zones.get("0", {}) + if team0 is Dictionary: + var points: Array = (team0 as Dictionary).get("points", []) + if not points.is_empty() and points[0] is Dictionary: + var pos = (points[0] as Dictionary).get("pos", []) + if pos is Array and (pos as Array).size() >= 3: + return { + "pos": Vector3(float(pos[0]), float(pos[1]), float(pos[2])), + "quelle": "team", + "status": "Kein Test-Start gesetzt — Start am ersten Team-Blau-Spawnpunkt.", + } + # Kartenmitte aus den Bounds. + var bounds: Dictionary = doc.get("bounds", {}) + var mn: Array = bounds.get("min", [-50.0, -10.0, -50.0]) + var mx: Array = bounds.get("max", [50.0, 80.0, 50.0]) + var cx := (float(mn[0]) + float(mx[0])) * 0.5 + var cz := (float(mn[2]) + float(mx[2])) * 0.5 + return { + "pos": Vector3(cx, SvmapDocument.terrain_height_at(doc, cx, cz), cz), + "quelle": "mitte", + "status": "Weder Test-Start noch Team-Spawnpunkte — Start in der Kartenmitte.", + } + + +## Strahl → erster Treffer auf einer AABB (Slab-Methode) oder -1.0. +## Für „Klick trifft Bauteil“ im Editor-Tool und „Fadenkreuz trifft Hindernis“ +## im Testlauf (kein Physik-Server im Editor/SubViewport). +## Rückgabe: Distanz t >= 0 (origin + dir * t) oder -1.0 = kein Treffer. +static func ray_aabb(origin: Vector3, dir: Vector3, box: AABB) -> float: + var d := dir + if d.length_squared() < 0.000000001: + return -1.0 + d = d.normalized() + var tmin := 0.0 + var tmax := INF + for axis in 3: + var o := origin[axis] + var dv := d[axis] + var lo := box.position[axis] + var hi := box.end[axis] + if absf(dv) < 0.0000001: + if o < lo or o > hi: + return -1.0 + continue + var t1 := (lo - o) / dv + var t2 := (hi - o) / dv + if t1 > t2: + var tmp := t1 + t1 = t2 + t2 = tmp + tmin = maxf(tmin, t1) + tmax = minf(tmax, t2) + if tmin > tmax: + return -1.0 + return tmin diff --git a/editor/playtest/test_start.gd.uid b/editor/playtest/test_start.gd.uid new file mode 100644 index 0000000..2a04600 --- /dev/null +++ b/editor/playtest/test_start.gd.uid @@ -0,0 +1 @@ +uid://b4barijsv6qs2 diff --git a/editor/playtest/test_start_store.gd b/editor/playtest/test_start_store.gd new file mode 100644 index 0000000..6023828 --- /dev/null +++ b/editor/playtest/test_start_store.gd @@ -0,0 +1,64 @@ +extends RefCounted +class_name TestStartStore +## Sidecar-Speicher für den Test-Startpunkt (Karte t_96e3c4ee) — bewusst +## NICHT im .svmap-Format (Validator V1–V16 + Client/Server bleiben unberührt; +## eine Map ohne den Punkt verhält sich exakt wie bisher). +## +## Ablage: user://scopedvr_teststart.cfg, Abschnitt [test_start], je Kartenpfad +## ein Schlüssel (globalisierter Pfad). Ungespeicherte Maps landen unter dem +## Sammel-Schlüssel UNGESPEICHERT (Session-Speicher über Neustarts hinaus nur, +## wenn die Map gespeichert wird — der Punkt wandert beim Speichern mit). +## Gespeichert werden nur x/z: die Höhe kommt immer automatisch vom Terrain. + +const CFG_PATH := "user://scopedvr_teststart.cfg" +const SECTION := "test_start" +const UNGESPEICHERT := "__ungespeichert__" + + +## Schlüssel für einen Kartenpfad (leer = ungespeicherte Map). +static func key_for(map_path: String) -> String: + if map_path.is_empty(): + return UNGESPEICHERT + return ProjectSettings.globalize_path(map_path) + + +## Gespeicherten Punkt lesen (Vector3 mit y=0 — Höhe kommt vom Terrain) +## oder null, wenn keiner gesetzt ist. +static func load_point(map_path: String) -> Variant: + var cfg := ConfigFile.new() + if cfg.load(CFG_PATH) != OK: + return null + var k := key_for(map_path) + var x: Variant = cfg.get_value(SECTION, k + "/x", null) + var z: Variant = cfg.get_value(SECTION, k + "/z", null) + if x == null or z == null: + return null + return Vector3(float(x), 0.0, float(z)) + + +## Punkt speichern (nur x/z — Höhe automatisch). +static func save_point(map_path: String, pos: Vector3) -> void: + var cfg := ConfigFile.new() + cfg.load(CFG_PATH) # Fehler → leere Config, Punkt wird neu angelegt. + var k := key_for(map_path) + cfg.set_value(SECTION, k + "/x", pos.x) + cfg.set_value(SECTION, k + "/z", pos.z) + cfg.save(CFG_PATH) + + +## Punkt löschen (Datei bleibt — andere Maps sind unberührt). +## Bewusst Sektions-Neuaufbau statt erase_section_key: Godots +## erase_section_key löscht Keys mit "/" im Namen still NICHT (Probe 20.10.). +static func clear_point(map_path: String) -> void: + var cfg := ConfigFile.new() + if cfg.load(CFG_PATH) != OK: + return + var k := key_for(map_path) + var keep := {} + for key in cfg.get_section_keys(SECTION): + if not key.begins_with(k + "/"): + keep[key] = cfg.get_value(SECTION, key) + cfg.erase_section(SECTION) + for key in keep: + cfg.set_value(SECTION, key, keep[key]) + cfg.save(CFG_PATH) diff --git a/editor/playtest/test_start_store.gd.uid b/editor/playtest/test_start_store.gd.uid new file mode 100644 index 0000000..6109a49 --- /dev/null +++ b/editor/playtest/test_start_store.gd.uid @@ -0,0 +1 @@ +uid://b3gc8f1aecsjl diff --git a/editor/playtest/test_start_tool.gd b/editor/playtest/test_start_tool.gd new file mode 100644 index 0000000..702c1ff --- /dev/null +++ b/editor/playtest/test_start_tool.gd @@ -0,0 +1,253 @@ +extends Node3D +## Test-Startpunkt-Werkzeug (Karte t_96e3c4ee): „hinklicken, wo man beim +## Testen spawnt“ — ein Klick auf Boden/Terrain setzt den Test-Startpunkt. +## KEIN Spiel-Spawnpunkt: der Punkt lebt NICHT im .svmap (Sidecar via +## TestStartStore), taucht in keinem Spawn-Check/Keiner Analyse auf und hat +## KEINEN Undo-Eintrag (editor-seitiger Session-Zustand, wie die UI-Größe). +## +## Bedienung (Einbindung in main.gd, wie T1 den Lauf-Modus angemeldet hat): +## Toolbar-Knopf „🎯 Test-Start“ oder Taste T → Werkzeug an +## Linksklick auf den Boden → Test-Start setzen (Höhe automatisch) +## Linksklick auf Objekt/Bauteil → Punkt auf den Boden DARUNTER (Klartext- +## Hinweis: „auf dem Boden unter dem Objekt“) +## Entf (im Werkzeug) / „Löschen“-Knopf rechts → Test-Start löschen +## ESC → Werkzeug beenden +## Rechte Maustaste: bewusst NICHT behandelt — sie gehört dem Freelook-Flug +## (Karte t_2470de89, absolute Priorität wie in jedem anderen Werkzeug). +## +## Marker (schmale Säule + Ring, Editor-Akzentfarbe) bleibt sichtbar, solange +## ein Punkt gesetzt ist — auch außerhalb des Werkzeugs. Vorschau-Ring folgt +## dem Cursor nur IM Werkzeug (wie der Spawn-Zonen-Ghost). + +## Editor-Akzentfarbe (wie Statuszeile/Kurzhilfe in main.gd) — „Farbe aus dem +## Theme“ laut Karte; dieselbe Konstante, keine neue Farbwelt. +const ACCENT := Color(0.55, 0.8, 1.0) + +var main # Hauptfenster (script-erweitert, Variant) +var camera: Camera3D +var view_builder # Welt-Bauer (script-erweitert, Variant) +var interaction # Objekt-Interaktion (für Objekt-Treffer-Test, nur LESEND) + +var active := false + +# Terrain-Cache für Cursor-Raycast (nur LESEN — dieses Werkzeug formt nichts) +var _heights := PackedFloat32Array() +var _res := 0 +var _sx := 0.0 +var _sz := 0.0 + +var _ghost: Node3D +var _marker: Node3D +var _cursor_valid := false +var _cursor := Vector3.ZERO + + +func setup(p_main, p_camera: Camera3D, p_builder, p_interaction) -> void: + main = p_main + camera = p_camera + view_builder = p_builder + interaction = p_interaction + _ensure_ghost() + _ensure_marker() + + +# ------------------------------------------------------------ Aktivierung --- + +func activate() -> bool: + active = true + refresh_from_doc() + return true # nie verweigert — der Test-Start braucht kein Auflösungs-Limit + + +func deactivate() -> void: + active = false + _cursor_valid = false + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + + +## Nach Laden/Neu/Speichern (Pfadwechsel → anderer Sidecar-Schlüssel) und +## nach Terrain-Änderungen: Cache + Marker aus main.test_start_point frisch. +func refresh_from_doc() -> void: + var terrain: Dictionary = main.doc.get("terrain", {}) + _res = int(terrain.get("resolution", 0)) + _sx = float(terrain.get("size_x_m", 0.0)) + _sz = float(terrain.get("size_z_m", 0.0)) + if _res >= 2 and _sx > 0.0 and _sz > 0.0: + _heights = SvmapIO.decode_heights(terrain) + else: + _heights = PackedFloat32Array() + _update_marker() + + +# --------------------------------------------------------------- Eingaben --- + +func _unhandled_input(event: InputEvent) -> void: + var p := MousePointer.from_input(event) + if p != null and handle_pointer(p): + get_viewport().set_input_as_handled() + + +func handle_pointer(p: PointerEvent) -> bool: + if not active: + return false + if p.kind == PointerEvent.Kind.MOVE: + _update_cursor(p.position) + elif p.kind == PointerEvent.Kind.PRESS: + _click(p.position) + return true + return false + + +func _click(viewport_pos: Vector2) -> void: + # 1) Objekt-Treffer? (Kugel-Approx der interaction — derselbe Pfad wie die + # Auswahl; bewusst nur GERUFEN, interaction.gd bleibt unangetastet.) + var obj_idx: Variant = interaction.call("_pick_object", viewport_pos) + var hit_obj := false + if obj_idx is int and int(obj_idx) >= 0: + hit_obj = true + # 3) Punkt setzen: x/z vom Strahl-Treffer, Höhe IMMER vom Terrain (nie + # im Objekt schwebend, nie versunken). + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + main.call("_set_status", "Da war kein Boden unter dem Mauszeiger — klicke IN die Karte (die grüne Fläche).", true) + return + var p: Vector3 = _clamped(hit as Vector3) + var hinweis := "" + if hit_obj: + hinweis = "auf dem Boden unter dem Objekt" + main.call("_test_start_apply", p.x, p.z, hinweis) + + +func _update_cursor(viewport_pos: Vector2) -> void: + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + _cursor_valid = false + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + return + _cursor_valid = true + _cursor = _clamped(hit as Vector3) + if _ghost != null and is_instance_valid(_ghost): + _ghost.position = Vector3(_cursor.x, _cursor.y + 0.06, _cursor.z) + _ghost.visible = true + + +## Strahl → Punkt auf dem Terrain (wie Terrain-/Spawn-Werkzeug; +## TerrainField.raycast — favorisiert echte Geländeberührung). +func _ray_hit(viewport_pos: Vector2) -> Variant: + if _res >= 2 and _heights.size() >= _res * _res: + return TerrainField.raycast(_heights, _res, _sx, _sz, + camera.project_ray_origin(viewport_pos), camera.project_ray_normal(viewport_pos)) + var terrain: Dictionary = main.doc.get("terrain", {}) + if terrain.is_empty(): + return null + var plane_y := float(terrain.get("min_height_m", 0.0)) + var origin := camera.project_ray_origin(viewport_pos) + var dir := camera.project_ray_normal(viewport_pos) + if absf(dir.y) < 0.0001: + return null + var t := (plane_y - origin.y) / dir.y + if t <= 0.0: + return null + return origin + dir * t + + +func _clamped(p: Vector3) -> Vector3: + var half := Vector2(_sx, _sz) * 0.5 + p.x = clampf(p.x, -half.x, half.x) + p.z = clampf(p.z, -half.y, half.y) + p.y = SvmapDocument.terrain_height_at(main.doc, p.x, p.z) + return p + + +# ----------------------------------------------------------------- Marker --- + +## Marker an den gesetzten Punkt hängen (oder ausblenden). Höhe IMMER +## automatisch: Terrainhöhe an (x, z) — nach Terrain-Formen bleibt der +## Punkt auf dem Boden (kein Schweben, kein Versinken). +func _update_marker() -> void: + _ensure_marker() + var point: Variant = main.get("test_start_point") + if point == null: + _marker.visible = false + return + var p: Vector3 = point + p.y = SvmapDocument.terrain_height_at(main.doc, p.x, p.z) + _marker.position = p + _marker.visible = true + + +# ------------------------------------------------------- Optik (Ghost/Marker) -- + +func _accent_mat(alpha: float) -> StandardMaterial3D: + var mat := StandardMaterial3D.new() + mat.albedo_color = Color(ACCENT.r, ACCENT.g, ACCENT.b, alpha) + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.emission_enabled = true + mat.emission = ACCENT * 0.35 + return mat + + +func _ensure_ghost() -> void: + if _ghost != null and is_instance_valid(_ghost): + return + _ghost = Node3D.new() + _ghost.visible = false + add_child(_ghost) + var ring := MeshInstance3D.new() + var torus := TorusMesh.new() + torus.inner_radius = 0.65 + torus.outer_radius = 0.85 + torus.rings = 48 + torus.ring_segments = 6 + ring.mesh = torus + ring.position.y = 0.05 + ring.material_override = _accent_mat(0.6) + _ghost.add_child(ring) + + +## Marker: schmale Säule + Ring am Boden (Karten-Vorgabe), Akzentfarbe. +func _ensure_marker() -> void: + if _marker != null and is_instance_valid(_marker): + return + _marker = Node3D.new() + _marker.visible = false + add_child(_marker) + var pillar := MeshInstance3D.new() + var cyl := CylinderMesh.new() + cyl.top_radius = 0.12 + cyl.bottom_radius = 0.12 + cyl.height = 4.0 + pillar.mesh = cyl + pillar.position.y = 2.0 # Fußpunkt auf dem Terrain + pillar.material_override = _accent_mat(0.55) + _marker.add_child(pillar) + var ring := MeshInstance3D.new() + var torus := TorusMesh.new() + torus.inner_radius = 0.55 + torus.outer_radius = 0.75 + torus.rings = 48 + torus.ring_segments = 6 + ring.mesh = torus + ring.position.y = 0.05 + ring.material_override = _accent_mat(0.85) + _marker.add_child(ring) + + +# ------------------------------------------------------------ Demo/Tests ---- + +## Demo-Hook (Suite/Renderprobe): Punkt programmatisch setzen — macht exakt, +## was ein Klick machen würde (ohne echte Maus-Ereignisse). hinweis = derselbe +## Klartext wie beim echten Klick ("" = freier Boden, sonst „unter dem Objekt“). +func demo_set(x: float, z: float, hinweis := "") -> void: + main.call("_test_start_apply", x, z, hinweis) + + +## Demo-Hook: Ghost an Position parken (Renderprobe ohne Maus). +func demo_park_ghost(x: float, z: float) -> void: + _cursor = _clamped(Vector3(x, 0.0, z)) + _cursor_valid = true + _ghost.position = Vector3(_cursor.x, _cursor.y + 0.06, _cursor.z) + _ghost.visible = true diff --git a/editor/playtest/test_start_tool.gd.uid b/editor/playtest/test_start_tool.gd.uid new file mode 100644 index 0000000..228ca33 --- /dev/null +++ b/editor/playtest/test_start_tool.gd.uid @@ -0,0 +1 @@ +uid://byjsw857bu1k8 diff --git a/editor/pointer_router.gd b/editor/pointer_router.gd new file mode 100644 index 0000000..782764b --- /dev/null +++ b/editor/pointer_router.gd @@ -0,0 +1,13 @@ +class_name PointerRouter +extends RefCounted +## Gibt eine Zeige-Eingabe in derselben Reihenfolge an die Werkzeuge wie Godot +## die Maus: tiefer und später im Baum zuerst, das erste Werkzeug nimmt sie. +## Für Eingaben, die nicht über den Viewport kommen, also den VR-Controller. + + +static func dispatch(main: Node, p: PointerEvent) -> bool: + for key in ["test_start_tool", "spawn_tool", "terrain_tool", "gizmo", "interaction"]: + var werkzeug: Variant = main.get(key) + if werkzeug != null and bool(werkzeug.call("handle_pointer", p)): + return true + return false diff --git a/editor/pointer_router.gd.uid b/editor/pointer_router.gd.uid new file mode 100644 index 0000000..445bba9 --- /dev/null +++ b/editor/pointer_router.gd.uid @@ -0,0 +1 @@ +uid://bem4edb2s282 diff --git a/editor/shortcut_keys.gd b/editor/shortcut_keys.gd new file mode 100644 index 0000000..5327530 --- /dev/null +++ b/editor/shortcut_keys.gd @@ -0,0 +1,121 @@ +extends RefCounted +class_name ShortcutKeys +## InputMap-Actions der Editor-Tastatur-Shortcuts (Karte t_c46d31c2, Karte 1/3). +## +## Konvention (alle Shortcut-Karten): Tastaturcode-basiert — JEDER Action wird +## ein InputEventKey mit PHYSICAL keycode zugewiesen (KEY_*-Konstante). Die +## physische Taste liegt unabhängig vom Tastaturlayout an derselben Position +## (QWERTZ wie QWERTY bei W/A/S/D/Leertaste/Shift), damit deutsche und +## Windows-Tastaturen identisch bedienen — keine OS-Hardcodes (PLAN.md +## Plattform-Regel). +## +## Registrierung per Code statt project.godot: KEY_*-Konstanten bleiben im +## Quelltext lesbar und der Test kann exakt dieselben Konstanten prüfen. +## register() ist idempotent (mehrfacher Aufruf = keine Doppel-Events). + +## Action → Liste von Events. Einträge: +## KEY_X = einfache physische Taste +## {"key": KEY_X, "shift": true} = Taste + Modifier (z. B. Umschalt+F) +const ACTIONS := { + # --- Freelook-Flug (Rechte Maustaste halten bzw. Umschalt+F) ------------- + "editor_freelook_toggle": [{"key": KEY_F, "shift": true}], + "editor_freelook": [MOUSE_BUTTON_RIGHT], + "editor_fly_forward": [KEY_W], + "editor_fly_back": [KEY_S], + "editor_fly_left": [KEY_A], + "editor_fly_right": [KEY_D], + # Flug-Vertikal + Tempo (Shooter-Konvention, Sly-Wunsch 17.10.): + # Leertaste = hoch, Shift = runter (Half-Life/CS-Konvention), Strg halten = + # temporär schneller, Alt halten = langsamer. Mausrad bleibt Grundtempo. + # Konflikt-Freiheit: Leertaste/Shift/Strg SIND im Editor sonst nur als + # Modifier-Kombos belegt (Strg+Z, Umschalt+Klick …) — allein gedrückt + # triggern sie KEINE Werkzeug-Aktion (Tests: test_shortcuts.gd, + # Konflikt-Gruppe). Im Flugmodus gibt der Kamera-Einstieg den GUI-Fokus + # frei (orbit_camera._enter_fly), damit Leertaste keinen fokussierten + # Button auslöst. + "editor_fly_up": [KEY_SPACE], + "editor_fly_down": [KEY_SHIFT], + "editor_fly_fast": [KEY_CTRL], + "editor_fly_slow": [KEY_ALT], + # --- Auswahl / Objekt-Operationen ---------------------------------------- + "editor_focus_selection": [KEY_F], + "editor_cancel": [KEY_ESCAPE], + "editor_delete_selection": [KEY_DELETE], + "editor_duplicate_selection": [{"key": KEY_D, "ctrl": true}], + # Strg+LMB = Mehrfachauswahl umschalten (Maus-Event, handler prüft den + # Modifier direkt am InputEventMouseButton — Action dokumentiert die Idee). + "editor_multi_select": [{"key": KEY_NONE, "mouse_left_ctrl": true}], + # --- Objekt-Manipulation (Karte t_55257792, Karte 2/3) ------------------- + "editor_rotate_90": [KEY_R], + "editor_nudge_up": [KEY_UP], + "editor_nudge_down": [KEY_DOWN], + "editor_nudge_left": [KEY_LEFT], + "editor_nudge_right": [KEY_RIGHT], + "editor_lift_to_floor": [{"key": KEY_PAGEUP, "ctrl": true}], + "editor_grab": [KEY_G], + # --- Terrain-Pinsel (nur im Terrain-Werkzeug aktiv, main gated) ---------- + "editor_brush_smaller": [KEY_BRACKETLEFT], + "editor_brush_larger": [KEY_BRACKETRIGHT], + "editor_brush_raise": [KEY_1], + "editor_brush_lower": [KEY_2], + "editor_brush_smooth": [KEY_3], + "editor_brush_paint": [KEY_4], + "editor_brush_flatten": [KEY_5], + "editor_brush_ramp": [KEY_6], + # --- Datei & Undo (Karte t_11b5c25b, Karte 3/3) -------------------------- + # Strg+Z/Strg+Y laufen seit dem M2-UX-Fundament; hier NACHGEBUGGEN als + # dokumentierte Actions (alle 3 Shortcut-Karten: ACTIONS-Dict = Source of + # truth). Strg+Umschalt+Z ist der zweite Redo-Weg (beide belegt). + "editor_undo": [{"key": KEY_Z, "ctrl": true}], + "editor_redo_y": [{"key": KEY_Y, "ctrl": true}], + "editor_redo_shift_z": [{"key": KEY_Z, "ctrl": true, "shift": true}], + "editor_save": [{"key": KEY_S, "ctrl": true}], + "editor_save_as": [{"key": KEY_S, "ctrl": true, "shift": true}], + "editor_open_map": [{"key": KEY_O, "ctrl": true}], + "editor_new_map": [{"key": KEY_N, "ctrl": true}], + # --- Werkzeugwechsel (F-Tasten statt Ziffern, Slys Weiche) --------------- + "editor_tool_terrain": [KEY_F1], + "editor_tool_objects": [KEY_F2], + "editor_tool_bauen": [KEY_F3], + "editor_tool_spawns": [KEY_F4], + # --- Kurzhilfe-Overlay + Test-Platzhalter -------------------------------- + "editor_help_overlay": [KEY_H], + "editor_test_map": [KEY_F5], + # --- Höhenlinien fixieren (Karte t_89c0ec95) ------------------------------ + # L = Nutzer-Wahl „Linien immer an/aus" — ohne Fix sind sie NUR im + # Terrain-Werkzeug (F1) sichtbar (Werkzeug-Kopplung in main.gd). L ist + # sonst unbesetzt (Kollisions-Prüfung Karte t_89c0ec95: keine Taste in + # ACTIONS nutzt KEY_L, Ziffern bleiben Terrain-Pinsel allein vorbehalten). + "editor_contours_toggle": [KEY_L], +} + + +static func register() -> void: + for action in ACTIONS: + if InputMap.has_action(action): + continue + InputMap.add_action(action) + for entry in ACTIONS[action]: + InputMap.action_add_event(action, _make_event(entry)) + + +static func _make_event(entry) -> InputEvent: + if entry is Dictionary: + if entry.get("mouse_left_ctrl", false): + var mb := InputEventMouseButton.new() + mb.button_index = MOUSE_BUTTON_LEFT + mb.ctrl_pressed = true + return mb + var key := InputEventKey.new() + key.physical_keycode = int(entry.get("key", KEY_NONE)) + key.shift_pressed = bool(entry.get("shift", false)) + key.ctrl_pressed = bool(entry.get("ctrl", false)) + key.alt_pressed = bool(entry.get("alt", false)) + return key + if entry is int and entry >= MOUSE_BUTTON_NONE and entry <= MOUSE_BUTTON_RIGHT: + var mouse := InputEventMouseButton.new() + mouse.button_index = int(entry) + return mouse + var simple := InputEventKey.new() + simple.physical_keycode = int(entry) + return simple \ No newline at end of file diff --git a/editor/shortcut_keys.gd.uid b/editor/shortcut_keys.gd.uid new file mode 100644 index 0000000..3bf4d56 --- /dev/null +++ b/editor/shortcut_keys.gd.uid @@ -0,0 +1 @@ +uid://ctglyo67gpgdc diff --git a/editor/shortcut_policy.gd b/editor/shortcut_policy.gd new file mode 100644 index 0000000..21e7c8a --- /dev/null +++ b/editor/shortcut_policy.gd @@ -0,0 +1,48 @@ +extends RefCounted +class_name ShortcutPolicy +## Esc-Verhalten des Map-Editors (Karte t_c46d31c2, Karte 1/3) — bewusst pure +## Funktion: main.gd fragt pro Esc-Taste ab WELCHE Zustandsänderung fällig ist +## und macht nur noch den Text. tests/test_shortcuts.gd prüft alle Kombos. +## +## Priorität (spec „Esc = Auswahl aufheben / Werkzeug abbrechen / Ghost abbrechen"): +## 1. aktives Sonder-Werkzeug verlassen (Terrain/Spawns → Objekt-Werkzeug) +## 2. Ghost-Preview (Platzieren) abbrechen +## 3. Auswahl aufheben +## Rückgabe-Werte: +## "exit_terrain" | "exit_spawns" | "exit_teststart" | "cancel_grab" +## "cancel_placement" | "clear_selection" | "none" +## grabbing (Karte t_55257792): G-Greifen läuft — Esc rollt die Positionen +## zurück (Blender-Semantik). Vorrang NACH Werkzeug-Exit (im Terrain- +## Werkzeug gibt es kein Greifen), VOR Platzieren/Greifen-Auswahlheben. +static func esc_action(tool_mode: String, placing: bool, has_selection: bool, + grabbing := false) -> String: + if tool_mode == "terrain": + return "exit_terrain" + if tool_mode == "spawns": + return "exit_spawns" + if tool_mode == "teststart": + # Karte t_96e3c4ee (Wiederanbindung 20.10.): Esc verlässt das + # Test-Start-Werkzeug — der gesetzte Punkt BLEIBT bestehen. + return "exit_teststart" + if grabbing: + return "cancel_grab" + if placing: + return "cancel_placement" + if has_selection: + return "clear_selection" + return "none" + + +## Karte 3/3 (t_11b5c25b): Dürfen die globalen Tasten (F1–F5, H) gerade wirken? +## Nein, wenn ein Textfeld/SpinBox fokussiert ist (Tippen geht vor) oder ein +## Dialog/Fenster offen ist (Taste soll nicht HINTER dem Dialog wirken). +## Pure Funktion — main.gd liefert nur die beiden Fakten (testbar, wie esc_action). +static func tool_keys_allowed(text_focused: bool, dialog_open: bool) -> bool: + return not text_focused and not dialog_open + + +## Karte 3/3: Verlust-Operation (Neue Map / Öffnen) bei ungespeicherten +## Änderungen → erst NACHFRAGEN (deutscher Dialog), sonst direkt ausführen. +## Pure Entscheidung — der Dialog selbst lebt in main.gd. +static func needs_dirty_confirm(dirty: bool) -> bool: + return dirty \ No newline at end of file diff --git a/editor/shortcut_policy.gd.uid b/editor/shortcut_policy.gd.uid new file mode 100644 index 0000000..8cde9b1 --- /dev/null +++ b/editor/shortcut_policy.gd.uid @@ -0,0 +1 @@ +uid://0sobeix0nlyg diff --git a/editor/spawn_tool.gd b/editor/spawn_tool.gd new file mode 100644 index 0000000..e30090c --- /dev/null +++ b/editor/spawn_tool.gd @@ -0,0 +1,713 @@ +extends Node3D +## Spawn-Zonen-Werkzeug (M2, Karte t_c4162c5d): Zonen für Team Blau/Rot auf +## den Boden malen — im Editor-Modus (Chef-Grundsatz Sly: easy + Spaß). +## +## Bedienung: +## Linksklick = Zone setzen (Klick wie beim Objekt-Platzieren) +## Linksklick auf Zone = Zone auswählen; ZIEHEN verschiebt sie (Terrain-Höhe) +## Pfeilspitze ziehen = Blickrichtung der Zone (auch Conquest-Zonen; grau = Standard) +## Regel rechts = Radius der neuen Zonen; mit Auswahl: Radius DER Zone +## Entf = ausgewählte Zone löschen +## ESC = Auswahl weg / Werkzeug beendet (ESC in main.gd; +## Rechte Maustaste = immer Flug, t_2470de89) +## +## Jede Operation ist EIN Undo-Eintrag über die gemeinsame ActionHistory +## (Blöcke spawn_zones + design_notes, SpawnOps.snapshot_blocks). +## +## Feedback: halbtransparenter Kreis folgt dem Cursor auf dem Terrain +## (Vorschau VOR dem Klick — wie der Objekt-Ghost), die gesetzte Zone leuchtet. + +const MODE_HELP := "Linksklick: Zone setzen · Klick+Ziehen: Zone verschieben · Pfeilspitze ziehen: Blickrichtung · Entf: löschen ·\nRadius/Team rechts · Mittlere Maustaste: Kamera · Strg+Z: zurück" + +## Symmetrie (M4, Karte t_ffa207b1): Partner-Kopplung gespiegelter Spawn-Zonen. +## Liegt in design_notes (Loader-frei, rundtrip-sicher — wie spawn_radius_m): +## {"A5": "B3", "B3": "A5"} — direkte Partner-IDs, beide Richtungen gesetzt. +const SPAWN_MIRROR_KEY := "spawn_mirror" + +var main # Hauptfenster (script-erweitert, Variant) +var camera: Camera3D +var view_builder # Welt-Bauer (script-erweitert, Variant) +var interaction # Objekt-Interaktion (für Auswahl-Entkoppelung) + +var active := false +var team := 0 # 0 = Blau, 1 = Rot (TDM-Konvention) +var zone_radius := SpawnOps.DEFAULT_ZONE_RADIUS + +var selected_team := -1 +var selected_id := "" + +# Auswähl-Ziehen +var _dragging := false +var _drag_moved := false +var _press_pos := Vector2.ZERO +var _grab_offset := Vector3.ZERO + +# Pfeilspitze ziehen (Blickrichtung): Griff aus view_builder.yaw_handles(), {} = keiner +const ARROW_PICK_M := 2.5 +const ARROW_SNAP_DEG := 5.0 +var _arrow_grab: Dictionary = {} +var _arrow_yaw := 0.0 + +# Terrain-Cache für Cursor-Raycast (nur LESEN — dieses Werkzeug formt nichts) +var _heights := PackedFloat32Array() +var _res := 0 +var _sx := 0.0 +var _sz := 0.0 + +var _cursor_valid := false +var _cursor := Vector3.ZERO + +# Ghost-Ring (Radius-Ring) + Fläche als Vorschau +var _ghost: Node3D + + +func setup(p_main, p_camera: Camera3D, p_builder, p_interaction) -> void: + main = p_main + camera = p_camera + view_builder = p_builder + interaction = p_interaction + _ensure_ghost() + + +func _ensure_ghost() -> void: + if _ghost != null and is_instance_valid(_ghost): + return + _ghost = Node3D.new() + _ghost.visible = false + add_child(_ghost) + + +# ------------------------------------------------------------ Aktivierung --- + +func activate() -> bool: + active = true + selected_team = -1 + selected_id = "" + _dragging = false + _cursor_valid = false + _refresh_from_doc() + if view_builder != null: + view_builder.call("set_spawn_selected", -1, "") + refresh_selection_panel() + return true # nie verweigert — Spawn-Zonen brauchen kein Auflösungs-Limit + + +func deactivate() -> void: + active = false + _dragging = false + selected_team = -1 + selected_id = "" + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + + +## Nach Undo/Redo/Laden/Neu: Terrain-Cache neu lesen, Auswahl weg. +func refresh_from_doc() -> void: + _refresh_from_doc() + var still_there := false + if selected_team >= 0: + for p in SpawnOps.points(main.doc, selected_team): + if p is Dictionary and str(p.get("id", "")) == selected_id: + still_there = true + break + if not still_there: + selected_team = -1 + selected_id = "" + + +func _refresh_from_doc() -> void: + var terrain: Dictionary = main.doc.get("terrain", {}) + _res = int(terrain.get("resolution", 0)) + _sx = float(terrain.get("size_x_m", 0.0)) + _sz = float(terrain.get("size_z_m", 0.0)) + if _res >= 2 and _sx > 0.0 and _sz > 0.0: + _heights = SvmapIO.decode_heights(terrain) + else: + _heights = PackedFloat32Array() + + +# ------------------------------------------------------------ Werkzeug-UI --- + +func set_team(next_team: int) -> void: + team = clampi(next_team, 0, 1) + _refresh_ghost_look() + if active: + main.call("_set_status", "Neue Zonen landen für %s (IDs %s1, %s2, …). Bestehende Zonen anklicken zum Verschieben." % [ + SpawnOps.team_label(team), SpawnOps.team_prefix(team), SpawnOps.team_prefix(team)]) + + +func set_radius(m: float) -> void: + zone_radius = clampf(m, SpawnOps.MIN_ZONE_RADIUS, SpawnOps.MAX_ZONE_RADIUS) + _refresh_ghost_look() + # Ausgewählte Zone? Dann gilt die Regel SOFORT für sie (mit Undo, merge-gekoppelt). + if active and selected_team >= 0 and selected_id != "": + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + SpawnOps.set_radius(main.doc, selected_id, zone_radius) + main.history.commit("Radius von %s: %d m" % [selected_id, int(zone_radius)], + before, SpawnOps.snapshot_blocks(main.history, main.doc), + "radius:%s" % selected_id) + main.call("_refresh_history_buttons") + main.call("_refresh_spawn_status") + _refresh_ghost_look() + var marker: Node3D = _marker_of(selected_team, selected_id) + if marker != null and is_instance_valid(marker): + view_builder.call("style_spawn_marker", marker, + SpawnOps.zone_radius(main.doc, selected_id), true) + + +# --------------------------------------------------------------- Eingaben --- + +func _unhandled_input(event: InputEvent) -> void: + var p := MousePointer.from_input(event) + if p != null and handle_pointer(p): + get_viewport().set_input_as_handled() + + +## Rechte Maustaste kommt hier nie an (Karte t_2470de89): sie gehört mit +## absolutem Vorrang dem Freelook-Flug. Deselect/Werkzeug beenden läuft über +## ESC (main.gd), die Auswahl bleibt im Flug erhalten. +func handle_pointer(p: PointerEvent) -> bool: + if not active: + return false + match p.kind: + PointerEvent.Kind.MOVE: + _update_cursor(p.position) + if not _arrow_grab.is_empty(): + _drag_arrow(p.position) + return false + if _dragging and selected_team >= 0: + var hit: Variant = _ray_hit(p.position) + if hit == null: + return false + var pos: Vector3 = _clamped(hit as Vector3) + _grab_offset + var half := _terrain_half() + pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius) + pos.z = clampf(pos.z, -half.y + zone_radius, half.y - zone_radius) + pos.y = SvmapDocument.terrain_height_at(main.doc, pos.x, pos.z) + _drag_moved = true + var marker: Node3D = _marker_of(selected_team, selected_id) + if marker != null and is_instance_valid(marker): + marker.position = pos + PointerEvent.Kind.PRESS: + _on_left_press(p.position) + return true + PointerEvent.Kind.RELEASE: + _on_left_release() + return false + + +func _update_cursor(viewport_pos: Vector2) -> void: + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + _cursor_valid = false + if _ghost != null and is_instance_valid(_ghost): + _ghost.visible = false + return + _cursor_valid = true + _cursor = _clamped(hit as Vector3) + if _ghost != null and is_instance_valid(_ghost): + _ghost.position = Vector3(_cursor.x, _cursor.y + 0.15, _cursor.z) + _ghost.visible = true + + +## Strahl → Punkt auf dem Terrain (Favorisiert echte Geländeberührung wie +## der Terrain-Pinsel; ohne Höhenfeld: Bodenebene). +func _ray_hit(viewport_pos: Vector2) -> Variant: + if _res >= 2 and _heights.size() >= _res * _res: + return TerrainField.raycast(_heights, _res, _sx, _sz, + camera.project_ray_origin(viewport_pos), camera.project_ray_normal(viewport_pos)) + var terrain: Dictionary = main.doc.get("terrain", {}) + if terrain.is_empty(): + return null + var plane_y := float(terrain.get("min_height_m", 0.0)) + var origin := camera.project_ray_origin(viewport_pos) + var dir := camera.project_ray_normal(viewport_pos) + if absf(dir.y) < 0.0001: + return null + var t := (plane_y - origin.y) / dir.y + if t <= 0.0: + return null + return origin + dir * t + + +func _clamped(p: Vector3) -> Vector3: + var half := _terrain_half() + p.x = clampf(p.x, -half.x, half.x) + p.z = clampf(p.z, -half.y, half.y) + p.y = SvmapDocument.terrain_height_at(main.doc, p.x, p.z) + return p + + +func _terrain_half() -> Vector2: + return Vector2(_sx, _sz) * 0.5 + + +# ----------------------------------------------------- Setzen/Auswählen ----- + +func _on_left_press(viewport_pos: Vector2) -> void: + _arrow_grab = _pick_arrow(viewport_pos) + if not _arrow_grab.is_empty(): + _drag_arrow(viewport_pos) + return + var picked := _pick_spawn(viewport_pos) + if picked.is_empty(): + _place_zone(viewport_pos) + return + # Bestehende Zone anfassen → auswählen + Greifen vorbereiten. + selected_team = int(picked["team"]) + selected_id = str(picked["id"]) + view_builder.call("set_spawn_selected", selected_team, selected_id) + _dragging = true + _drag_moved = false + _press_pos = viewport_pos + _grab_offset = Vector3.ZERO + var hit: Variant = _ray_hit(viewport_pos) + var marker: Node3D = _marker_of(selected_team, selected_id) + if hit != null and marker != null and is_instance_valid(marker): + _grab_offset = marker.position - (_clamped(hit as Vector3)) + _grab_offset.y = 0.0 + refresh_selection_panel() + main.call("_set_status", "%s (%s) ausgewählt — ziehen zum Verschieben, Entf zum Löschen, der Radius-Regel rechts gilt jetzt für SIE." % [ + selected_id, SpawnOps.team_label(selected_team)]) + + +func _on_left_release() -> void: + if not _arrow_grab.is_empty(): + _commit_arrow() + return + if not _dragging: + return + _dragging = false + if not (_drag_moved and selected_team >= 0 and selected_id != ""): + _drag_moved = false + return + var marker: Node3D = _marker_of(selected_team, selected_id) + if marker == null or not is_instance_valid(marker): + _drag_moved = false + return + var half := _terrain_half() + var pos: Vector3 = marker.position + pos.x = clampf(pos.x, -half.x, half.x) + pos.z = clampf(pos.z, -half.y, half.y) + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + SpawnOps.move_point(main.doc, selected_team, selected_id, pos.x, pos.z) + # Symmetrie (M4): Zwilling folgt der gespiegelten neuen Position. + var mirror_msg: String = spawn_mirror_move(main.doc, selected_team, selected_id, _sym_mode()) + main.history.commit("%s verschoben" % selected_id, + before, SpawnOps.snapshot_blocks(main.history, main.doc)) + main.call("_after_structure_change") + _point_selection_at_doc() + view_builder.call("set_spawn_selected", selected_team, selected_id) + main.call("_set_status", "%s verschoben — Strg+Z macht das rückgängig.%s" % [ + selected_id, "" if mirror_msg.is_empty() else " " + mirror_msg]) + _drag_moved = false + + +func _place_zone(viewport_pos: Vector2) -> void: + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + main.call("_set_status", "Da war kein Boden unter dem Mauszeiger — klicke IN die Karte (die grüne Fläche).", true) + return + var half := _terrain_half() + var pos := _clamped(hit as Vector3) + if absf(pos.x) > half.x - zone_radius or absf(pos.z) > half.y - zone_radius: + pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius) + pos.z = clampf(pos.z, -half.y + zone_radius, half.y - zone_radius) + main.call("_set_status", "Der Kreis würde über den Rand hinausschauen — ich habe ihn ganz nach innen geschoben.") + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + var point := SpawnOps.add_zone(main.doc, team, pos.x, pos.z) + var pid := str(point["id"]) + SpawnOps.set_radius(main.doc, pid, zone_radius) + # Symmetrie (M4): Zwilling im Gegner-Team — innerhalb desselben Undo-Schritts. + var mirror_res: Dictionary = spawn_mirror_add(main.doc, team, pid, _sym_mode()) + main.history.commit("Spawn-Zone %s (%s) gesetzt" % [pid, SpawnOps.team_label(team)], + before, SpawnOps.snapshot_blocks(main.history, main.doc)) + main.call("_after_structure_change") + # Frische Zone auswählen und anfassen — Radius-Regel gilt ab jetzt für SIE. + selected_team = team + selected_id = pid + view_builder.call("set_spawn_selected", selected_team, selected_id) + refresh_selection_panel() + var mirror_text := str(mirror_res["status"]) + main.call("_set_status", "Spawn-Zone %s (%s) gesetzt.%s Radius: Regler rechts · verschieben: klicken+ziehen · Entf löscht · Strg+Z macht alles rückgängig." % [ + pid, SpawnOps.team_label(team), "" if mirror_text.is_empty() else " " + mirror_text]) + + +## Kursor-Strahl → nächste Pfeilspitze in Greifweite oder {}. +func _pick_arrow(viewport_pos: Vector2) -> Dictionary: + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + return {} + var p: Vector3 = hit as Vector3 + var best := {} + var best_d := ARROW_PICK_M + for h in view_builder.yaw_handles(): + var tip: Vector3 = h["tip"] + var d := Vector2(p.x - tip.x, p.z - tip.z).length() + if d <= best_d: + best = h + best_d = d + return best + + +func _drag_arrow(viewport_pos: Vector2) -> void: + var hit: Variant = _ray_hit(viewport_pos) + var node: Node3D = _arrow_grab.get("node") + if hit == null or node == null or not is_instance_valid(node): + return + var c := node.global_position + var yaw := SpawnOps.yaw_towards(c, Vector2((hit as Vector3).x, (hit as Vector3).z)) + _arrow_yaw = fposmod(snappedf(yaw, ARROW_SNAP_DEG), 360.0) + var arrow: MeshInstance3D = view_builder.yaw_arrow_of(node) + if arrow != null: + arrow.rotation.y = deg_to_rad(_arrow_yaw) + main.call("_set_status", "Blickrichtung von %s: %d°" % [str(_arrow_grab["id"]), int(_arrow_yaw)]) + + +func _commit_arrow() -> void: + var grab := _arrow_grab + _arrow_grab = {} + if str(grab["kind"]) == "zone": + apply_zone_yaw(str(grab["id"]), _arrow_yaw) + return + selected_team = int(grab["team"]) + selected_id = str(grab["id"]) + set_selected_yaw(_arrow_yaw) + + +## Blickrichtung der ausgewählten Zone setzen (yaw = null: Standard). EIN Undo-Eintrag. +func set_selected_yaw(yaw: Variant) -> void: + if selected_team < 0 or selected_id == "": + return + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + if not SpawnOps.set_point_yaw(main.doc, selected_team, selected_id, yaw): + return + spawn_mirror_yaw(main.doc, selected_id, yaw, _sym_mode()) + main.history.commit("Blickrichtung von %s" % selected_id, + before, SpawnOps.snapshot_blocks(main.history, main.doc)) + main.call("_after_structure_change") + view_builder.call("set_spawn_selected", selected_team, selected_id) + refresh_selection_panel() + main.call("_set_status", "Blickrichtung von %s: %s — Strg+Z macht das rückgängig." % [ + selected_id, "Standard (zur Kartenmitte)" if yaw == null else "%d°" % int(yaw)]) + + +## Dasselbe für eine Conquest-Zone (der Griff im Bild, das Panel geht über modus_panel). +func apply_zone_yaw(zid: String, yaw: Variant) -> void: + var zones: Variant = main.doc.get("conquest_zones", []) + if not (zones is Array): + return + var before := {"conquest_zones": (zones as Array).duplicate(true)} + for z in zones: + if z is Dictionary and str(z.get("id", "")) == zid: + SpawnOps.write_yaw(z, yaw) + main.history.commit("Blickrichtung von %s" % zid, before, + {"conquest_zones": (zones as Array).duplicate(true)}) + main.call("_after_structure_change") + main.call("_set_status", "Blickrichtung von %s: %d° — Strg+Z macht das rückgängig." % [zid, int(yaw)]) + + +## Kursor-Strahl → getroffene Zone ({team, id}) oder {}. +func _pick_spawn(viewport_pos: Vector2) -> Dictionary: + var hit: Variant = _ray_hit(viewport_pos) + if hit == null: + return {} + var p: Vector3 = hit as Vector3 + var best := {} + var best_d := INF + for marker in view_builder.spawn_markers: + if marker == null or not is_instance_valid(marker): + continue + var mpos: Vector3 = marker.global_position + var d := Vector2(p.x - mpos.x, p.z - mpos.z).length() + var pick_r := float(marker.get_meta("spawn_pick_radius", 4.0)) + if d <= pick_r and d < best_d: + best = {"team": int(marker.get_meta("spawn_team", -1)), "id": str(marker.get_meta("spawn_id", ""))} + best_d = d + return best + + +func _marker_of(team_i: int, pid: String) -> Node3D: + for marker in view_builder.spawn_markers: + if marker == null or not is_instance_valid(marker): + continue + if int(marker.get_meta("spawn_team", -1)) == team_i and str(marker.get_meta("spawn_id", "")) == pid: + return marker + return null + + +func _point_selection_at_doc() -> void: + # Nach rebuild: Auswahl-Referenzen im DOC halten (selbes Team/ID-Prinzip). + for p in SpawnOps.points(main.doc, selected_team): + if p is Dictionary and str(p.get("id", "")) == selected_id: + return + selected_team = -1 + selected_id = "" + + +func _clear_selection() -> void: + selected_team = -1 + selected_id = "" + if _dragging: + _dragging = false + view_builder.call("set_spawn_selected", -1, "") + refresh_selection_panel() + + +func delete_selected() -> void: + if selected_team < 0 or selected_id == "": + main.call("_set_status", "Nichts ausgewählt — klicke zuerst eine Spawn-Zone an, die weg soll.", true) + return + var label := "%s (%s)" % [selected_id, SpawnOps.team_label(selected_team)] + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + if not SpawnOps.remove_point(main.doc, selected_team, selected_id): + _clear_selection() + return + # Symmetrie (M4): Zwilling mit entfernen (innerhalb desselben Undo-Schritts). + var mirror_msg: String = spawn_mirror_delete(main.doc, selected_id, _sym_mode()) + main.history.commit("%s löschen" % label, before, + SpawnOps.snapshot_blocks(main.history, main.doc)) + _clear_selection() + main.call("_after_structure_change") + main.call("_set_status", "Spawn-Zone %s gelöscht — Strg+Z holt sie zurück.%s %s braucht mindestens %d Zonen!" % [ + label, "" if mirror_msg.is_empty() else " " + mirror_msg, + SpawnOps.team_label(team), SvmapIO.MIN_SPAWN_POINTS_PER_TEAM]) + + +## Panel-Zeile rechts (Welche Zone ist an?) synchron halten — ohne Statuszeile anzufassen. +func refresh_selection_panel() -> void: + if main.has_method("_sync_spawn_selection"): + main.call("_sync_spawn_selection") + + +# ------------------------------------------------- Symmetrie (M4) Statics --- +## Karte t_ffa207b1: Gespiegelte Spawn-Zonen. ENTScheidung (Spec vc27, +## „Teams bleiben einander gegenüber fair"): der Zwilling landet im GEGNER- +## Team — Blau (West) ↔ Rot (Ost), denn genau auf den Gegenseiten liegen die +## Default-Streifen der beiden Teams. Jede gesetzte Zone gibt dem anderen Team +## automatisch die faire Gegenstelle. Alles pure Statics (kein +## UI, kein Node-Zugriff) → headless testbar; spawn_ops.gd bleibt unangetastet. + +## Partner-ID einer Spawn-Zone ("" = keiner). +static func spawn_mirror_partner(doc: Dictionary, pid: String) -> String: + var m := _mirror_map_of(doc) + return str(m.get(pid, "")) + + +static func _mirror_map_of(doc: Dictionary) -> Dictionary: + var notes = doc.get("design_notes", {}) + if not (notes is Dictionary): + return {} + var m = (notes as Dictionary).get(SPAWN_MIRROR_KEY, {}) + return m if m is Dictionary else {} + + +static func _mirror_map_set(doc: Dictionary, pid_a: String, pid_b: String) -> void: + var notes = doc.get("design_notes", {}) + if not (notes is Dictionary): + notes = {} + doc["design_notes"] = notes + var m = (notes as Dictionary).get(SPAWN_MIRROR_KEY, {}) + if not (m is Dictionary): + m = {} + (notes as Dictionary)[SPAWN_MIRROR_KEY] = m + m[pid_a] = pid_b + m[pid_b] = pid_a + + +static func _mirror_map_clear(doc: Dictionary, pid_a: String, pid_b: String) -> void: + var m := _mirror_map_of(doc) + if m.is_empty(): + return + m.erase(pid_a) + m.erase(pid_b) + + +## Gespiegelte Spawn-Zone zu `pid` anlegen (Symmetrie an). Rückgabe: +## {"ok": bool, "status": Klartext}. ok=false + V9-Text = Zwilling NICHT +## gespeichert (Grenzen scharf — Spec vc27). Auf der Symmetrie-Mitte liegende +## Zonen werden nicht gedoppelt (Spec). +static func spawn_mirror_add(doc: Dictionary, team: int, pid: String, mode: int) -> Dictionary: + if mode == SymmetryCore.Mode.OFF: + return {"ok": true, "status": ""} + var original := _spawn_point_of(doc, team, pid) + if original.is_empty(): + return {"ok": false, "status": "Spawn-Zone %s nicht gefunden — Zwilling nicht gesetzt." % pid} + var pos := _spawn_pos_of(original) + var center := SymmetryCore.map_center(doc) + if SymmetryCore.maps_to_self(mode, pos, center): + return {"ok": true, "status": "Spawn-Zone %s liegt auf der Symmetrie-Mitte — nicht gedoppelt." % pid} + var mirror_pos := SymmetryCore.mirror_point(mode, pos, center) + var bounds: Dictionary = doc.get("bounds", {}) + if not bounds.is_empty() and not SvmapIO._inside_bounds( + [mirror_pos.x, mirror_pos.y, mirror_pos.z], bounds): + return {"ok": false, "status": "❌ V9: Gespiegelter Spawn läge außerhalb der Match-Grenzen — nicht gesetzt."} + var twin := SpawnOps.add_zone(doc, 1 - team, mirror_pos.x, mirror_pos.z) + var twin_id := str(twin["id"]) + SpawnOps.set_radius(doc, twin_id, SpawnOps.zone_radius(doc, pid)) + _mirror_map_set(doc, pid, twin_id) + if SpawnOps.has_yaw(original): + SpawnOps.write_yaw(twin, SymmetryCore.mirror_facing(mode, float(original[SpawnOps.YAW_KEY]))) + return {"ok": true, "status": "Gespiegelte Zone %s (%s) gesetzt." % [ + twin_id, SpawnOps.team_label(1 - team)]} + + +## Verschiebung spiegeln: Zwilling bekommt die gespiegelte Position der NEUEN +## Position des Originals (y = Terrain-Höhe am gespiegelten Punkt, wie beim +## normalen Verschieben). Rückgabe: Klartext-Status ("" = nichts zu tun). +static func spawn_mirror_move(doc: Dictionary, team: int, pid: String, mode: int) -> String: + if mode == SymmetryCore.Mode.OFF: + return "" + var partner := spawn_mirror_partner(doc, pid) + if partner.is_empty(): + return "" + var original := _spawn_point_of(doc, team, pid) + if original.is_empty(): + return "" + var pos := _spawn_pos_of(original) + var center := SymmetryCore.map_center(doc) + if SymmetryCore.maps_to_self(mode, pos, center): + return "" + var mirror_pos := SymmetryCore.mirror_point(mode, pos, center) + var partner_team := _team_of_point(doc, partner) + if partner_team < 0: + return "" + if not SpawnOps.move_point(doc, partner_team, partner, mirror_pos.x, mirror_pos.z): + return "" + return "Gespiegelte Zone %s mitverschoben." % partner + + +## Blickrichtung spiegeln: Zwilling schaut gespiegelt (yaw = null: auch er auf Standard). +static func spawn_mirror_yaw(doc: Dictionary, pid: String, yaw: Variant, mode: int) -> void: + if mode == SymmetryCore.Mode.OFF: + return + var partner := spawn_mirror_partner(doc, pid) + var partner_team := _team_of_point(doc, partner) if not partner.is_empty() else -1 + if partner_team >= 0: + SpawnOps.set_point_yaw(doc, partner_team, partner, + null if yaw == null else SymmetryCore.mirror_facing(mode, float(yaw))) + + +## Löschung spiegeln (entfernen → beide weg, inkl. Radius-Eintrag und +## Partner-Map). Rückgabe: Klartext-Status ("" = nur das Original war weg). +static func spawn_mirror_delete(doc: Dictionary, pid: String, mode: int) -> String: + if mode == SymmetryCore.Mode.OFF: + return "" + var partner := spawn_mirror_partner(doc, pid) + if partner.is_empty(): + return "" + var partner_team := _team_of_point(doc, partner) + if partner_team < 0: + _mirror_map_clear(doc, pid, partner) + return "" + if SpawnOps.remove_point(doc, partner_team, partner): + _mirror_map_clear(doc, pid, partner) + return "Gespiegelte Zone %s (%s) mit entfernt." % [partner, SpawnOps.team_label(partner_team)] + return "" + + +static func _spawn_point_of(doc: Dictionary, team: int, pid: String) -> Dictionary: + for p in SpawnOps.points(doc, team): + if p is Dictionary and str(p.get("id", "")) == pid: + return p as Dictionary + return {} + + +static func _team_of_point(doc: Dictionary, pid: String) -> int: + for team in [0, 1]: + if not _spawn_point_of(doc, team, pid).is_empty(): + return team + return -1 + + +static func _spawn_pos_of(point: Dictionary) -> Vector3: + var pos: Array = point.get("pos", [0.0, 0.0, 0.0]) + return Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + + +# ------------------------------------------------------------ Ghost-Optik --- + +func _refresh_ghost_look() -> void: + if _ghost == null or not is_instance_valid(_ghost): + return + # Kreisfläche: flacher Cylinder (Zonen-Scheibe), teamfarbig halbtransparent. + for child in _ghost.get_children(): + child.queue_free() + var disc := MeshInstance3D.new() + var cyl := CylinderMesh.new() + cyl.top_radius = zone_radius + cyl.bottom_radius = zone_radius + cyl.height = 0.25 + disc.mesh = cyl + disc.position.y = 0.12 + var col: Color = view_builder.TEAM_COLORS[team] + var mat := StandardMaterial3D.new() + mat.albedo_color = Color(col.r, col.g, col.b, 0.4) + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.emission_enabled = true + mat.emission = col * 0.35 + disc.material_override = mat + _ghost.add_child(disc) + var ring := MeshInstance3D.new() + var torus := TorusMesh.new() + torus.inner_radius = zone_radius - 0.2 + torus.outer_radius = zone_radius + 0.2 + torus.rings = 48 + torus.ring_segments = 6 + ring.mesh = torus + ring.position.y = 0.05 + ring.material_override = mat + _ghost.add_child(ring) + + +# ------------------------------------------------------- Demo/Screenshot ---- + +## Demo-Hook (Desktop-Smoke): Zone programmatisch setzen (inkl. History). +## Macht exakt, was ein Klick machen würde — ohne echte Maus-Ereignisse. +func demo_place(zone_team: int, x: float, z: float, radius: float) -> String: + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + var point := SpawnOps.add_zone(main.doc, zone_team, x, z) + var pid := str(point["id"]) + if radius > 0.0: + SpawnOps.set_radius(main.doc, pid, radius) + # Symmetrie (M4): Zwilling im Gegner-Team — innerhalb desselben Undo-Schritts. + spawn_mirror_add(main.doc, zone_team, pid, _sym_mode()) + main.history.commit("Spawn-Zone %s (%s) gesetzt" % [pid, SpawnOps.team_label(zone_team)], + before, SpawnOps.snapshot_blocks(main.history, main.doc)) + return pid + + +## Demo-Hook: Zone programmatisch verschieben (inkl. History). +func demo_move(zone_team: int, pid: String, x: float, z: float) -> void: + var before: Dictionary = SpawnOps.snapshot_blocks(main.history, main.doc) + SpawnOps.move_point(main.doc, zone_team, pid, x, z) + # Symmetrie (M4): Zwilling folgt der gespiegelten neuen Position. + spawn_mirror_move(main.doc, zone_team, pid, _sym_mode()) + main.history.commit("%s verschoben" % pid, + before, SpawnOps.snapshot_blocks(main.history, main.doc)) + + +## Aktiver Symmetrie-Modus aus main (M1-Schalter). Defensive über main.get(): +## Mock-Mains (Tests) haben sym_mode nicht — dann gilt OFF (altes Verhalten), +## statt mit SCRIPT ERROR abzustürzen (Lektion: SCRIPT ERROR in -s-Suiten = rot). +func _sym_mode() -> int: + var v: Variant = main.get("sym_mode") if main != null else null + return SymmetryCore.Mode.OFF if v == null else int(v) + + +## Demo-Hook: Ghost an Position parken (für Screenshots ohne Maus). +func demo_park_ghost(x: float, z: float, team_i: int, radius: float) -> void: + set_team(team_i) + if radius > 0.0: + set_radius_demo_only(radius) + _refresh_ghost_look() + _cursor = Vector3(x, SvmapDocument.terrain_height_at(main.doc, x, z), z) + _cursor_valid = true + _ghost.position = Vector3(_cursor.x, _cursor.y + 0.15, _cursor.z) + _ghost.visible = true + + +## Nur-Editor-Radius (kein Doc-Eintrag, kein Undo) für die Vorschau im Demo-Hook. +func set_radius_demo_only(m: float) -> void: + zone_radius = clampf(m, SpawnOps.MIN_ZONE_RADIUS, SpawnOps.MAX_ZONE_RADIUS) \ No newline at end of file diff --git a/editor/spawn_tool.gd.uid b/editor/spawn_tool.gd.uid new file mode 100644 index 0000000..44902a0 --- /dev/null +++ b/editor/spawn_tool.gd.uid @@ -0,0 +1 @@ +uid://dcr747fch22k diff --git a/editor/symmetry_ops.gd b/editor/symmetry_ops.gd new file mode 100644 index 0000000..b58b318 --- /dev/null +++ b/editor/symmetry_ops.gd @@ -0,0 +1,647 @@ +extends RefCounted +class_name SymmetryOps +## M5 Ein-Klick „Karte symmetrisch machen“ + Symmetrie-Check (Karte t_60014669, +## Spec /opt/data/scopedvr/KARTEN_MIRROR_vc27.md). Baut auf M1–M4 auf: +## Spiegel-Mathe und Partner-Links kommen aus SymmetryCore, das Anwenden auf +## Objekten aus ObjectOps, auf Bauteilen (normale Objekte) und auf Spawn-Zonen aus +## dem Spawn-Spiegel-Mechanismus (spawn_tool.gd — dort NUR BENUTZT, nie +## geändert). Diese Datei orchestriert den GANZEN Vorgang und den Check: +## +## analyze(doc, mode) → ehrliche Bestandsaufnahme (nur lesen): Paare, die +## schon passen, Paare, die widersprechen, Elemente +## ohne Zwilling, abweichende Boden-Zellen. +## apply(doc, mode, quelle_hoch) → gleicht die Karte an: alles aus der +## Quell-Hälfte wird ERGÄNZT (Objekte, Bau-Teile, +## Spawn-Punkte, Boden zellgenau); widersprüchliche +## Paare bekommen die Quelle zugesprochen (nichts wird +## gelöscht, die Zahl wurde vorher genannt). +## check(doc, mode) → Klartext-Bericht: „Alles symmetrisch ✓“ oder +## „N Stellen weichen ab: Objekt #12, Boden (31,17) …“ +## +## Seiten-Logik ohne Mode-Wirrwarr: Jedes Element wird mit seinem Spiegelbild +## verglichen. Die „linke“/„vordere“ Seite eines Paares ist die, deren +## Spiegelbild weiter rechts/hinten liegt (lex-Antwort: erst x, dann z). +## Die Quell-Hälfte-Wahl (quelle_hoch=false = links/vorn, true = rechts/hinten) +## entscheidet NUR, welche Seite eines Paares bei Widerspruch gewinnt und in +## welche Richtung einzelstehende Elemente ergänzt werden. +## +## Ein-Klick-Anwendung (Panel) macht den GANZEN Vorgang zu EINEM Undo-Schritt: +## ActionHistory snapshotet das ganze Dokument — hier wird nur mutiert, das +## Committen bleibt beim Aufrufer (Panel/main, Muster wie beim Resize R3). +## +## Alles static + reine Funktionen (mutieren nur das übergebene doc) → +## headless testbar wie SymmetryCore/ObjectOps. + +const SpawnToolScript := preload("res://core/editor/spawn_tool.gd") + +## Wie nah ein Spiegelbild an einem Element liegen darf, damit sie als Paar +## gelten (Meter, XZ-Ebene). Handgebaute Karten sitzen nie auf den Millimeter — +## aber exakt gespiegelte Paare aus M2–M4 liegen auf 0. +const PAIR_TOLERANCE_M := 1.0 +## Ab wann Höhen als verschieden gelten (Terrain-R16-Schritt ist viel kleiner). +const HEIGHT_EPS_M := 0.02 +## Kleine Vergleichstoleranzen für Position/Drehung/Maßstab/Farbe. +const POS_EPS_M := 0.01 +const ROT_EPS_DEG := 0.5 +const VALUE_EPS := 0.01 +## Wie viele Beispiele der Klartext-Bericht maximal einzeln nennt. +const MAX_EXAMPLES := 8 + + +# ------------------------------------------------------------ Seiten-Logik -- + +## Klartext-Namen der beiden Hälften für den Modus ([niedrige Seite, hohe]). +static func side_labels(mode: int) -> Array: + if mode == SymmetryCore.Mode.AXIS_Z: + return ["vorn", "hinten"] + return ["links", "rechts"] + + +static func _center_of(doc: Dictionary) -> Vector2: + return SymmetryCore.map_center(doc) + + +static func _mirror(mode: int, doc: Dictionary, p: Vector3) -> Vector3: + return SymmetryCore.mirror_point(mode, p, _center_of(doc)) + + +static func _pos_of(arr: Array) -> Vector3: + if (arr as Array).size() < 3: + return Vector3.ZERO + return Vector3(float(arr[0]), float(arr[1]), float(arr[2])) + + +## „self“ = liegt auf Mitte/Achse (wird nie gedoppelt, nie geprüft). +## „low“ = lex-erste Seite (links bzw. vorn), „high“ = die andere. +## Bei Punkt-Spiegelung entscheidet auf der Mittel-Linie z statt x. +static func _side_of(mode: int, p: Vector3, doc: Dictionary) -> String: + if SymmetryCore.maps_to_self(mode, p, _center_of(doc)): + return "self" + var m := _mirror(mode, doc, p) + if p.x < m.x - 0.0001: + return "low" + if p.x > m.x + 0.0001: + return "high" + return "low" if p.z < m.z - 0.0001 else "high" + + +## Templat-loses Sammeln: items = [{"idx": int, "pos": Vector3}]. Paarung +## IMMERS vom low-Spiegel aus (deterministisch, Richtung der Quell-Wahl +## ändert die Paare nicht). Rückgabe: +## pairs: [{"low": idx, "high": idx, "mirror": Vector3}] +## singles_low / singles_high: idx-Listen der Zwillinglosen +static func _pair_items(mode: int, doc: Dictionary, items: Array) -> Dictionary: + var out := {"pairs": [], "singles_low": [], "singles_high": []} + var low_pool: Array = [] + var high_pool: Array = [] + for it in items: + if not (it is Dictionary): + continue + var side: String = _side_of(mode, it["pos"], doc) + if side == "self": + continue + if side == "low": + low_pool.append(it) + else: + high_pool.append(it) + var used: Dictionary = {} + for d in low_pool: + var m := _mirror(mode, doc, d["pos"]) + var best: int = -1 + var best_d: float = PAIR_TOLERANCE_M + for k in high_pool.size(): + if used.has(k): + continue + var t: Dictionary = high_pool[k] + var dxy := Vector2(m.x - t["pos"].x, m.z - t["pos"].z).length() + if dxy <= best_d: + best = k + best_d = dxy + if best >= 0: + used[best] = true + out["pairs"].append({ + "low": int(d["idx"]), + "high": int(high_pool[best]["idx"]), + "mirror": m, + }) + for k in high_pool.size(): + if not used.has(k): + out["singles_high"].append(int(high_pool[k]["idx"])) + for d in low_pool: + var paired := false + for p in out["pairs"]: + if int(p["low"]) == int(d["idx"]): + paired = true + break + if not paired: + out["singles_low"].append(int(d["idx"])) + return out + + +# -------------------------------------------------------------- analyze() --- + +## Ehrliche Bestandsaufnahme (liest NUR). Siehe Klassenkopf für die Struktur. +static func analyze(doc: Dictionary, mode: int) -> Dictionary: + var an := { + "mode": mode, + "objects": {"pairs": [], "singles_low": [], "singles_high": [], "diff": 0}, + "spawns": {"pairs": [], "singles_low": [], "singles_high": [], "diff": 0}, + "terrain_cells": [], + "material_cells": [], # Material-Raster (fein, Karte 24.10.) — eigenes Gitter + "deviations": [], + "links": 0, # positionierende Paare ohne sym-Kopplung (werden verknüpft) + "conflicts": 0, # widersprüchliche Paare + Boden-Zellen + "adds": 0, # Zwillinge, die ergänzt werden + "_dev_total": 0, + "total_changes": 0, + "total_deviations": 0, + } + if mode == SymmetryCore.Mode.OFF: + return an + _analyze_objects(doc, mode, an) + _analyze_spawns(doc, mode, an) + _analyze_terrain(doc, mode, an) + an["conflicts"] = int(an["objects"]["diff"]) \ + + int(an["spawns"]["diff"]) + (an["terrain_cells"] as Array).size() \ + + (an["material_cells"] as Array).size() + an["adds"] = (an["objects"]["singles_low"] as Array).size() \ + + (an["objects"]["singles_high"] as Array).size() \ + + (an["spawns"]["singles_low"] as Array).size() \ + + (an["spawns"]["singles_high"] as Array).size() + an["total_deviations"] = int(an["_dev_total"]) + an["total_changes"] = int(an["conflicts"]) + int(an["adds"]) + int(an["links"]) + return an + + +## Meldet eine Abweichung: Zähler ZÄHLT IMMER, die Beispielliste ist gekappt. +static func _push_dev(an: Dictionary, label: String) -> void: + an["_dev_total"] = int(an["_dev_total"]) + 1 + var devs: Array = an["deviations"] + if devs.size() < MAX_EXAMPLES: + devs.append(label) + + +static func _valid_link(list: Array, idx: int) -> int: + ## sym-Partner-Index NUR wenn der Partner wirklich existiert — sonst gilt + ## das Element als verkoppelt-lost und läuft über die Paarung mit. + var item: Dictionary = list[idx] + var key := SymmetryCore.sym_of(item) + if key.is_empty(): + return -1 + return SymmetryCore.find_partner_index(list, key, idx) + + +## Frische gemeinsame sym-Kennung für ein Paar. Hingen die Elemente vorher an +## ANDEREN Partnern, verlieren diese ihre Kennung mit — sonst bliebe ein +## V16-Waise übrig (eine Kennung = GENAU 2 Elemente). Merkwurzeln einer +## beschädigten Karte können so pro Lauf kleiner werden; ein zweiter Klick +## erledigt den Rest. +static func _fresh_pair_link(doc: Dictionary, list: Array, donor_i: int, + twin_i: int) -> void: + for idx in [donor_i, twin_i]: + var key := SymmetryCore.sym_of(list[idx]) + if key.is_empty(): + continue + var old_partner := SymmetryCore.find_partner_index(list, key, idx) + if old_partner >= 0 and old_partner != donor_i and old_partner != twin_i: + SymmetryCore.clear_sym(list[old_partner]) + SymmetryCore.clear_sym(list[idx]) + var nk := SymmetryCore.next_sym_key(doc) + SymmetryCore.set_sym(list[donor_i], nk) + SymmetryCore.set_sym(list[twin_i], nk) + + +## Spawn-Gegenstück: Kopplung design_notes.spawn_mirror sauber neu setzen +## (beide Richtungen überschreiben, alte Partner beidseitig auskoppeln). +static func _fresh_spawn_link(doc: Dictionary, donor_id: String, + twin_id: String) -> void: + for pid in [donor_id, twin_id]: + var old := _spawn_linked(doc, pid) + if not old.is_empty() and old != donor_id and old != twin_id: + SpawnToolScript._mirror_map_clear(doc, pid, old) + SpawnToolScript._mirror_map_set(doc, donor_id, twin_id) + + +static func _obj_pair_differs(src: Dictionary, twin: Dictionary, mp: Vector3, + mode: int) -> bool: + var tpos := _pos_of(twin.get("pos", [])) + if Vector2(mp.x - tpos.x, mp.z - tpos.z).length() > POS_EPS_M: + return true + if absf(tpos.y - mp.y) > POS_EPS_M: + return true + # 3 Achsen (Karte t_e166e656): der Vergleich läuft über die gespiegelte + # Euler-Orientierung (für reines Y-Drehen identisch zur alten rot_y-Regel). + var gespiegelt := SymmetryCore.mirror_rot_euler(mode, SvmapIO.objekt_rotation(src)) + var twin_euler := SvmapIO.objekt_rotation(twin) + var trot := fposmod(twin_euler.y - gespiegelt.y, 360.0) + var rotd := minf(trot, 360.0 - trot) + if rotd > ROT_EPS_DEG: + return true + var kipp := fposmod(twin_euler.x - gespiegelt.x + 180.0, 360.0) - 180.0 + if absf(kipp) > ROT_EPS_DEG: + return true + kipp = fposmod(twin_euler.z - gespiegelt.z + 180.0, 360.0) - 180.0 + if absf(kipp) > ROT_EPS_DEG: + return true + if absf(float(twin.get("scale", 1.0)) - float(src.get("scale", 1.0))) > VALUE_EPS: + return true + if str(twin.get("id", "")) != str(src.get("id", "")): + return true + var s_tint: Variant = src.get("tint", null) + var t_tint: Variant = twin.get("tint", null) + if (s_tint is Array) != (t_tint is Array): + return true + if s_tint is Array and t_tint is Array: + var sa: Array = s_tint + var ta: Array = t_tint + if (sa as Array).size() != (ta as Array).size(): + return true + for k in sa.size(): + if absf(float(sa[k]) - float(ta[k])) > VALUE_EPS: + return true + return false + + +static func _analyze_objects(doc: Dictionary, mode: int, an: Dictionary) -> void: + var objects: Array = doc.get("objects", []) + var items: Array = [] + for i in objects.size(): + var obj = objects[i] + if not (obj is Dictionary): + continue + var pos: Array = obj.get("pos", []) + if (pos as Array).size() < 3: + continue + items.append({"idx": i, "pos": _pos_of(pos)}) + var paired := _pair_items(mode, doc, items) + var pairs: Array = paired["pairs"] + var diff_n := 0 + for p in pairs: + var low_i: int = p["low"] + var high_i: int = p["high"] + var low_ok: bool = _valid_link(objects, low_i) == high_i + var high_ok: bool = _valid_link(objects, high_i) == low_i + if not (low_ok and high_ok): + an["links"] = int(an["links"]) + 1 + var donor: Dictionary = objects[low_i] + var twin: Dictionary = objects[high_i] + var differs := _obj_pair_differs(donor, twin, p["mirror"], mode) + if differs: + diff_n += 1 + _push_dev(an, "Objekt #%d" % low_i) + an["objects"]["pairs"] = pairs + an["objects"]["singles_low"] = paired["singles_low"] + an["objects"]["singles_high"] = paired["singles_high"] + an["objects"]["diff"] = diff_n + for idx in (paired["singles_low"] as Array) + (paired["singles_high"] as Array): + _push_dev(an, "Objekt #%d hat keinen Zwilling" % int(idx)) + + +# ------------------------------------------------------------------ Spawns -- +## Nur Teams 0/1 (TDM-Konvention wie M4): der Spiegel-Zwilling eines Spawn- +## Punkts liegt im GEGNER-Team. Kopplung läuft über design_notes.spawn_mirror. + +static func _spawn_items(doc: Dictionary) -> Array: + var items: Array = [] + for team in [0, 1]: + for p in SpawnOps.points(doc, team): + if not (p is Dictionary): + continue + var pos: Array = p.get("pos", []) + if (pos as Array).size() < 3: + continue + items.append({ + "team": team, + "id": str(p.get("id", "")), + "pos": _pos_of(pos), + }) + return items + + +static func _spawn_linked(doc: Dictionary, pid: String) -> String: + return SpawnToolScript.spawn_mirror_partner(doc, pid) + + +static func _analyze_spawns(doc: Dictionary, mode: int, an: Dictionary) -> void: + var items := _spawn_items(doc) + var paired := _pair_items_spawn(mode, doc, items) + var pairs: Array = paired["pairs"] + var diff_n := 0 + for p in pairs: + var low_ok: bool = _spawn_linked(doc, str(p["low_id"])) == str(p["high_id"]) + var high_ok: bool = _spawn_linked(doc, str(p["high_id"])) == str(p["low_id"]) + if not (low_ok and high_ok): + an["links"] = int(an["links"]) + 1 + var donor: Dictionary = p["low_point"] + var mp := SymmetryCore.mirror_point(mode, donor["pos"], _center_of(doc)) + var twin: Dictionary = p["high_point"] + if Vector2(mp.x - twin["pos"].x, mp.z - twin["pos"].z).length() > POS_EPS_M: + diff_n += 1 + _push_dev(an, "Spawn-Punkt %s" % str(p["low_id"])) + an["spawns"]["pairs"] = pairs + an["spawns"]["singles_low"] = paired["singles_low"] + an["spawns"]["singles_high"] = paired["singles_high"] + an["spawns"]["diff"] = diff_n + for s in (paired["singles_low"] as Array) + (paired["singles_high"] as Array): + _push_dev(an, "Spawn-Punkt %s hat keinen Zwilling" % str(s["id"])) + + +## Spawn-Variante der Paarung: items tragen team/id; Singles bleiben Objekte +## ({team, id, pos}) statt idx (Spawn-Punkte hängen an Teams, nicht am Array). +static func _pair_items_spawn(mode: int, doc: Dictionary, items: Array) -> Dictionary: + var out := {"pairs": [], "singles_low": [], "singles_high": []} + var low_pool: Array = [] + var high_pool: Array = [] + for it in items: + var side: String = _side_of(mode, it["pos"], doc) + if side == "self": + continue + if side == "low": + low_pool.append(it) + else: + high_pool.append(it) + var used: Dictionary = {} + for d in low_pool: + var m := _mirror(mode, doc, d["pos"]) + var best: int = -1 + var best_d: float = PAIR_TOLERANCE_M + for k in high_pool.size(): + if used.has(k): + continue + var t: Dictionary = high_pool[k] + var dxy := Vector2(m.x - t["pos"].x, m.z - t["pos"].z).length() + if dxy <= best_d: + best = k + best_d = dxy + if best >= 0: + used[best] = true + out["pairs"].append({ + "low_team": int(d["team"]), "low_id": str(d["id"]), + "high_team": int(high_pool[best]["team"]), + "high_id": str(high_pool[best]["id"]), + "low_point": d, "high_point": high_pool[best], + }) + for k in high_pool.size(): + if not used.has(k): + out["singles_high"].append(high_pool[k]) + for d in low_pool: + var paired := false + for p in out["pairs"]: + if str(p["low_id"]) == str(d["id"]): + paired = true + break + if not paired: + out["singles_low"].append(d) + return out + + +# ---------------------------------------------------------------- Terrain --- + +## Abweichende Zell-PAARE (je einmal, kanonisch = lex-erste Zelle des Paares). +static func _analyze_terrain(doc: Dictionary, mode: int, an: Dictionary) -> void: + var terrain: Dictionary = doc.get("terrain", {}) + var res := int(terrain.get("resolution", 0)) + if res < 2: + return + var heights := SvmapIO.decode_heights(terrain) + if heights.size() < res * res: + return + var cells: Array = an["terrain_cells"] + var eps := HEIGHT_EPS_M # absolut in Metern + for j in res: + for i in res: + var mc := SymmetryCore.mirror_cell(mode, i, j, res) + if mc.x == i and mc.y == j: + continue + # Nur das lex-erste Paarmitglied behandeln (keine Doppel-Meldung). + var is_low := i < mc.x or (i == mc.x and j < mc.y) + if not is_low: + continue + var ia := j * res + i + var ib := mc.y * res + mc.x + var dh := absf(heights[ia] - heights[ib]) + if dh > eps: + cells.append(Vector2i(i, j)) + if cells.size() <= MAX_EXAMPLES: + _push_dev(an, "Boden (%d,%d)" % [i, j]) + # Material auf seinem eigenen Raster (Byte exakt vergleichen). + var mres := SvmapIO.material_resolution(terrain) + var mats := SvmapIO.decode_materials(terrain) + if mres < 2 or mats.size() < mres * mres: + return + var mix := SvmapIO.decode_mix(terrain) # weiche Ränder: b + Anteil mitvergleichen + var mb: PackedByteArray = mix[0] + var mw: PackedByteArray = mix[1] + var mcells: Array = an["material_cells"] + for j in mres: + for i in mres: + var mc := SymmetryCore.mirror_cell(mode, i, j, mres) + if not (i < mc.x or (i == mc.x and j < mc.y)): + continue + var ia := j * mres + i + var ib := mc.y * mres + mc.x + if mats[ia] != mats[ib] or mb[ia] != mb[ib] or mw[ia] != mw[ib]: + mcells.append(Vector2i(i, j)) + if mcells.size() <= MAX_EXAMPLES: + _push_dev(an, "Boden-Material (%d,%d)" % [i, j]) + + +# ----------------------------------------------------------------- check() -- + +## Klartext-Bericht (nur lesen). report: {"ok": bool, "text": String, +## "deviations": Array}. „Alles symmetrisch ✓“ / „N Stellen weichen ab: …“. +static func check(doc: Dictionary, mode: int) -> Dictionary: + if mode == SymmetryCore.Mode.OFF: + return {"ok": false, + "text": "Der Symmetrie-Modus ist Aus — erst einen Modus wählen (Punkt / Achse), dann prüfen.", + "deviations": [], "analyze": analyze(doc, mode)} + var an := analyze(doc, mode) + var devs: Array = an["deviations"] + var total := int(an["total_deviations"]) + var text := "" + if total == 0: + text = "Alles symmetrisch ✓" + else: + var shown: Array = [] + for d in devs: + shown.append(str(d)) + var rest := total - shown.size() + var listing := ", ".join(PackedStringArray(shown)) + if rest > 0: + listing += " … und %d weitere" % rest + text = "%d Stellen weichen ab: %s" % [total, listing] + return {"ok": total == 0, "text": text, "deviations": devs, + "analyze": an} + + +# ----------------------------------------------------------------- apply() -- + +## Karte angleichen. quelle_hoch=false → links/vorn ist die Quelle (Gewinner +## bei Widersprüchen), true → rechts/hinten. Mutiert doc IN PLACE (Pattern +## wie die übrigen Tools: der Aufrufer snapshotet drum herum für EIN Undo). +## Rückgabe: {"ok": bool, "report": Klartext (mehrzeilig), "counts": {...}}. +static func apply(doc: Dictionary, mode: int, quelle_hoch: bool) -> Dictionary: + if mode == SymmetryCore.Mode.OFF: + return {"ok": false, "report": "Der Symmetrie-Modus ist Aus — ohne Modus weiß ich nicht, wie gespiegelt werden soll.", + "counts": {}} + var an := analyze(doc, mode) + var c := { + "objects_added": 0, "objects_fixed": 0, "objects_linked": 0, + "spawns_added": 0, "spawns_fixed": 0, "spawns_linked": 0, + "terrain_cells": 0, "skipped": 0, + } + _apply_objects(doc, mode, an, quelle_hoch, c) + _apply_terrain(doc, mode, an, quelle_hoch, c) + # Spawns ZULETZT: die Zwillinge sitzen auf der Terrain-Höhe — die stimmt + # erst nach dem Boden-Ausgleich (M2/M4-Konvention: y = Terrainhöhe). + _apply_spawns(doc, mode, an, quelle_hoch, c) + return {"ok": true, "counts": c, "report": _apply_report(c, quelle_hoch, mode)} + + +static func _apply_report(c: Dictionary, quelle_hoch: bool, mode: int) -> String: + var erg: Array = [] + for entry in [["objects_added", "Objekte ergänzt"], + ["spawns_added", "Spawn-Punkte ergänzt"], ["terrain_cells", "Bodenstellen angeglichen"], + ["objects_fixed", "Objekt-Widersprüche"], + ["spawns_fixed", "Spawn-Widersprüche"], ["objects_linked", "Objekt-Paare verknüpft"], + ["spawns_linked", "Spawn-Paare verknüpft"]]: + var n := int(c[entry[0]]) + if n > 0: + erg.append("%d %s" % [n, entry[1]]) + if erg.is_empty(): + return "Nichts zu tun — die Karte ist schon symmetrisch ✓" + var sides := side_labels(mode) + var conflict_sum := int(c["objects_fixed"]) + int(c["spawns_fixed"]) + int(c["terrain_cells"]) + var text := "Fertig ✓ — " + ", ".join(PackedStringArray(erg)) + if conflict_sum > 0: + text += ". Bei %d Widersprüchen hat %s gewonnen" % [conflict_sum, str(sides[1] if quelle_hoch else sides[0])] + return text + ". Ein Strg+Z macht alles rückgängig." + + +static func _write_object_from(twin: Dictionary, donor: Dictionary, mp: Vector3, + mode: int) -> void: + twin["pos"] = [mp.x, mp.y, mp.z] + # 3 Achsen (Karte t_e166e656): Kippen wird mitgespiegelt; für reines + # Y-Drehen exakt die alte mirror_rot_y-Regel (gleiche Zahlenwerte). + SvmapIO.rot_setzen(twin, SymmetryCore.mirror_rot_euler( + mode, SvmapIO.objekt_rotation(donor))) + twin["scale"] = donor.get("scale", 1.0) + if donor.has("tint"): + twin["tint"] = (donor["tint"] as Array).duplicate() + else: + twin.erase("tint") + twin["id"] = donor.get("id", "") + + +static func _apply_objects(doc: Dictionary, mode: int, an: Dictionary, + quelle_hoch: bool, c: Dictionary) -> void: + var objects: Array = doc.get("objects", []) + if (objects as Array).is_empty(): + return + # a) Paare: verknüpfen + Widersprüche zugunsten der Quelle entscheiden. + for p in (an["objects"]["pairs"] as Array): + var low_i: int = p["low"] + var high_i: int = p["high"] + var donor_i: int = high_i if quelle_hoch else low_i + var twin_i: int = low_i if quelle_hoch else high_i + var donor: Dictionary = objects[donor_i] + var twin: Dictionary = objects[twin_i] + var key_d := SymmetryCore.sym_of(donor) + var key_t := SymmetryCore.sym_of(twin) + if key_d != key_t or key_d.is_empty(): + # Ohne/widerstreitende Kopplung: frische gemeinsame Kennung. + _fresh_pair_link(doc, objects, donor_i, twin_i) + c["objects_linked"] = int(c["objects_linked"]) + 1 + var mp := SymmetryCore.mirror_point(mode, _pos_of(donor.get("pos", [])), _center_of(doc)) + if _obj_pair_differs(donor, twin, mp, mode): + _write_object_from(twin, donor, mp, mode) + c["objects_fixed"] = int(c["objects_fixed"]) + 1 + # b) Zwillinglose ergänzen (aus welcher Seite auch immer — nichts gelöscht). + for idx in (an["objects"]["singles_low"] as Array) + (an["objects"]["singles_high"] as Array): + var res := ObjectOps.symmetry_place_twin(doc, int(idx), mode) + if int(res["twin"]) >= 0: + c["objects_added"] = int(c["objects_added"]) + 1 + else: + c["skipped"] = int(c["skipped"]) + 1 + + +static func _apply_terrain(doc: Dictionary, mode: int, an: Dictionary, + quelle_hoch: bool, c: Dictionary) -> void: + _apply_materials(doc, mode, an, quelle_hoch, c) + var cells: Array = an["terrain_cells"] + if (cells as Array).is_empty(): + return + var terrain: Dictionary = doc.get("terrain", {}) + var res := int(terrain.get("resolution", 0)) + if res < 2: + return + var heights := SvmapIO.decode_heights(terrain) + var hmin := float(terrain.get("min_height_m", 0.0)) + var hmax := float(terrain.get("max_height_m", 1.0)) + for cell in cells: + var cc: Vector2i = cell + var mc := SymmetryCore.mirror_cell(mode, cc.x, cc.y, res) + var src := cc if not quelle_hoch else mc + var dst := mc if not quelle_hoch else cc + var si: int = src.y * res + src.x + var di: int = dst.y * res + dst.x + heights[di] = heights[si] + c["terrain_cells"] = int(c["terrain_cells"]) + 1 + terrain["heightmap_r16_b64"] = TerrainField.encode(heights, hmin, hmax) + + +## Material-Schicht angleichen (eigenes feines Raster, Karte 24.10.). +static func _apply_materials(doc: Dictionary, mode: int, an: Dictionary, + quelle_hoch: bool, c: Dictionary) -> void: + var cells: Array = an.get("material_cells", []) + if cells.is_empty(): + return + var terrain: Dictionary = doc.get("terrain", {}) + var mres := SvmapIO.material_resolution(terrain) + var mats := SvmapIO.decode_materials(terrain) + if mres < 2 or mats.size() < mres * mres: + return + var mix := SvmapIO.decode_mix(terrain) + var mb: PackedByteArray = mix[0] + var mw: PackedByteArray = mix[1] + for cell in cells: + var cc: Vector2i = cell + var mc := SymmetryCore.mirror_cell(mode, cc.x, cc.y, mres) + var src := cc if not quelle_hoch else mc + var dst := mc if not quelle_hoch else cc + var di := dst.y * mres + dst.x + var si := src.y * mres + src.x + mats[di] = mats[si] + mb[di] = mb[si] + mw[di] = mw[si] + c["terrain_cells"] = int(c["terrain_cells"]) + 1 + terrain["materials_r8_b64"] = SvmapIO.encode_materials(mats) + SvmapIO.encode_mix(terrain, mb, mw) + + +static func _apply_spawns(doc: Dictionary, mode: int, an: Dictionary, + quelle_hoch: bool, c: Dictionary) -> void: + # a) Paare: verknüpfen (design_notes.spawn_mirror) + Quelle entscheidet. + for p in (an["spawns"]["pairs"] as Array): + var donor_team: int = int(p["high_team"]) if quelle_hoch else int(p["low_team"]) + var donor_id: String = str(p["high_id"]) if quelle_hoch else str(p["low_id"]) + var linked_d := _spawn_linked(doc, donor_id) + var twin_id: String = str(p["low_id"]) if quelle_hoch else str(p["high_id"]) + if linked_d != twin_id: + # Beide Richtungen sauber neu setzen, alte Partner auskoppeln. + _fresh_spawn_link(doc, donor_id, twin_id) + c["spawns_linked"] = int(c["spawns_linked"]) + 1 + var donor_point: Dictionary = p["low_point"] if not quelle_hoch else p["high_point"] + var mp := SymmetryCore.mirror_point(mode, donor_point["pos"], _center_of(doc)) + var twin_point: Dictionary = p["high_point"] if not quelle_hoch else p["low_point"] + if Vector2(mp.x - twin_point["pos"].x, mp.z - twin_point["pos"].z).length() > POS_EPS_M: + var msg := SpawnToolScript.spawn_mirror_move(doc, donor_team, donor_id, mode) + if not msg.is_empty(): + c["spawns_fixed"] = int(c["spawns_fixed"]) + 1 + # b) Zwillinglose ergänzen (Team wechselt beim Spiegeln — M4-Regel). + for s in (an["spawns"]["singles_low"] as Array) + (an["spawns"]["singles_high"] as Array): + var res := SpawnToolScript.spawn_mirror_add(doc, int(s["team"]), str(s["id"]), mode) + if bool(res["ok"]) and str(res["status"]).contains("gesetzt"): + c["spawns_added"] = int(c["spawns_added"]) + 1 + else: + c["skipped"] = int(c["skipped"]) + 1 diff --git a/editor/symmetry_ops.gd.uid b/editor/symmetry_ops.gd.uid new file mode 100644 index 0000000..1ba1588 --- /dev/null +++ b/editor/symmetry_ops.gd.uid @@ -0,0 +1 @@ +uid://csb4dkvi508of diff --git a/editor/terrain_tool.gd b/editor/terrain_tool.gd new file mode 100644 index 0000000..0404d18 --- /dev/null +++ b/editor/terrain_tool.gd @@ -0,0 +1,765 @@ +extends Node3D +## Terrain-Pinsel (M2, Karte t_9aba04f1): Höhenfeld heben/senken/glätten im +## 3D-Viewport — im Terrain-Modus (Chef-Grundsatz Sly: maximal easy + Spaß). +## +## Bedienung: +## Linksklick-Ziehen = gewählter Pinsel-Modus (Heben / Senken / Glätten) +## Strg+Linksklick = das Gegenteil (bei Heben senkt er, bei Senken hebt +## er — Invert-Konvention wie Blender/Photoshop; im +## Malen-Modus malt Strg+Klick Gras zurück). Karte +## t_2470de89: die RECHTE Maustaste gehört IMMER dem +## Freelook-Flug — sie startet hier keinen Strich mehr. +## Der ganze Strich = EIN Undo-Eintrag (Strg+Z stellt die Form VOR dem +## Strich wieder her, nicht nur eine Zwischenstufe). +## +## Feedback: Pinsel-Ring folgt dem Cursor AUF dem Gelände (ray-march auf das +## Höhenfeld), das Mesh folgt dem Strich LIVE (Vertices pro Frame neu). +## +## Der Doc wird nur ZUM Strich-Ende verändert (encode → heightmap_r16_b64) +## — während des Strichs lebt ein dekodiertes PackedFloat32Array, das Mesh +## liest daraus. Undo/Redo läuft über die gemeinsame ActionHistory (terrain-Block). + +const SMOOTH_RATE_DIVISOR := 20.0 # Stärke-Slider (m/s) → Glätt-Tempo (Anteil/s) +const MODE_LABELS := ["Heben", "Senken", "Glätten", "Malen", "Einebnen", "Rampe"] +const FLATTEN_RATE_DIVISOR := 8.0 # Stärke-Slider → Einebnen-Tempo (Anteil/s) + +var main # Hauptfenster (script-erweitert, Variant) +var camera: Camera3D +var view_builder # TerrainMesh-Ansicht (script-erweitert, Variant) +var interaction # Objekt-Interaktion (für Modus-Wechsel) + +var active := false +var brush_mode: int = TerrainField.Mode.RAISE +var brush_radius := 12.0 # Meter +## Kante beim Malen: 0 = hart, 1 = ganz weich (Karte 25.10.). +var brush_weichheit := 0.4 +## Pinsel-Falloff (Karte 25.10.): 0 = hart (linear), 1 = weich (quadratisch). +var brush_falloff := 0.5 +## Druck 0.05–1 (Pen-Druck / Controller-Trigger / Maus = 1.0), skaliert die Stärke. +var pressure := 1.0 +var brush_strength := 8.0 # Meter pro Sekunde (Heben/Senken) +var paint_material: int = 0 # Material-ID im Malen-Modus (0=Gras, 1=Sand, 2=Fels) + +# Stroke-Zustand +var _stroking := false +var _stroke_mode: int = TerrainField.Mode.RAISE +var _stroke_material: int = 0 +var _flatten_target := 0.0 # Einebnen: Höhe unter dem Cursor beim Strichstart +var _ramp_a := Vector2.ZERO # Rampe: Startpunkt (Welt-X/Z) + Höhe +var _ramp_ha := 0.0 +var _ramp_b := Vector2.ZERO # Rampe: aktueller Endpunkt +var _heights := PackedFloat32Array() +var _mats := PackedByteArray() # Material-Schicht (res²) — seit _refresh_ + # _from_doc immer dekodiert (t_55ed041a), + # Mal-Strich schreibt live hinein. +var _res := 0 +var _mres := 0 # Raster der Material-Schicht (feines Raster, Karte 24.10.; alt = _res) +var _mix_b := PackedByteArray() # zweites Material je Punkt (weiche Ränder, Karte 25.10.) +var _mix_w := PackedByteArray() # Anteil des zweiten Materials 0–255 +var _sx := 0.0 +var _sz := 0.0 +var _hmin := 0.0 +var _hmax := 0.0 +var _cursor_valid := false +var _cursor := Vector3.ZERO + +# Pinsel-Ring +var _ring: MeshInstance3D +var _ramp_line: MeshInstance3D # Rampen-Vorschau: Linie Start → Cursor +var _ring_radius := -1.0 +var _ring_mode := -1 + + +func setup(p_main, p_camera: Camera3D, p_builder, p_interaction) -> void: + main = p_main + camera = p_camera + view_builder = p_builder + interaction = p_interaction + _ensure_ring() + + +func _ensure_ring() -> void: + if _ring != null and is_instance_valid(_ring): + return + _ring = MeshInstance3D.new() + _ring.visible = false + add_child(_ring) + _ramp_line = MeshInstance3D.new() + _ramp_line.visible = false + _ramp_line.mesh = ImmediateMesh.new() + var lm := StandardMaterial3D.new() + lm.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + lm.albedo_color = Color(1.0, 0.85, 0.3) + lm.no_depth_test = true + _ramp_line.material_override = lm + add_child(_ramp_line) + + +# ------------------------------------------------------------ Aktivierung --- + +## Terrain-Modus einschalten. false = abgelehnt (Auflösungs-Limit) — main +## bleibt dann im Objekt-Modus und erklärt warum. +func activate() -> bool: + var terrain: Dictionary = main.doc.get("terrain", {}) + var res := int(terrain.get("resolution", 0)) + if res > TerrainField.EDITOR_MAX_RESOLUTION: + main.call("_set_status", + "Diese Map hat eine Terrain-Auflösung von %d — das Editor-MVP formt Terrain nur bis %d (Performance). Neu angelegte Maps erfüllen das automatisch." % [res, TerrainField.EDITOR_MAX_RESOLUTION], + true) + return false + if not _refresh_from_doc(): + return false + active = true + _stroking = false + _ring.visible = false + return true + + +func deactivate() -> void: + active = false + if _stroking: + _end_stroke() + if _ring != null and is_instance_valid(_ring): + _ring.visible = false + if _ramp_line != null and is_instance_valid(_ramp_line): + _ramp_line.visible = false + + +## Nach Undo/Redo/Laden/Neue-Map: Feld neu dekodieren (Cache ist sonst alt). +func refresh_from_doc() -> bool: + if not _refresh_from_doc(): + return false + return true + + +func _refresh_from_doc() -> bool: + var terrain: Dictionary = main.doc.get("terrain", {}) + if terrain.is_empty(): + return false + _res = int(terrain.get("resolution", 0)) + _sx = float(terrain.get("size_x_m", 0.0)) + _sz = float(terrain.get("size_z_m", 0.0)) + _hmin = float(terrain.get("min_height_m", 0.0)) + _hmax = float(terrain.get("max_height_m", 0.0)) + if _res < 2 or _sx <= 0.0 or _sz <= 0.0 or _hmax <= _hmin: + return false + _heights = SvmapIO.decode_heights(terrain) + # Material-Schicht mit dekodieren (Karte t_55ed041a): der Live-Rebuild + # beim Heben/Senken/Glätten braucht sie, sonst fällt das Mesh während + # des Strichs auf alles-Gras zurück (Sand/Fels „verschwinden" live). + _mats = SvmapIO.decode_materials(terrain) + _mres = SvmapIO.material_resolution(terrain) + _load_mix(terrain) + return _heights.size() >= _res * _res + + +# ------------------------------------------------------------ Pinsel-Optionen + +func set_brush_mode(mode: int) -> void: + # Clamp bis PAINT (3) — der alte Clamp 0..2 hätte den Malen-Modus still + # zu Glätten degradiert (Karte t_55257792, Ziffer 4 = Malen). + brush_mode = clampi(mode, 0, MODE_LABELS.size() - 1) + _refresh_ring_mesh() + + +func set_radius(m: float) -> void: + brush_radius = clampf(m, 1.0, 64.0) # ab 1 m (Karte 25.10., feines Raster) + _refresh_ring_mesh() + + +## Misch-Schicht (Karte 25.10.) aus dem terrain-Block — fehlt sie, Nullen. +func _load_mix(terrain: Dictionary) -> void: + var mix := SvmapIO.decode_mix(terrain) + _mix_b = mix[0] + _mix_w = mix[1] + + +## Materialpunkt als ein int (a | b<<8 | w<<16) — Spiegeln kopiert alle drei. +func _punkt(idx: int) -> int: + return int(_mats[idx]) | (int(_mix_b[idx]) << 8) | (int(_mix_w[idx]) << 16) + + +## Anteil (0–255) des Materials m an einem gepackten Punkt. +static func _anteil(v: int, m: int) -> int: + var a := v & 0xFF + var b := (v >> 8) & 0xFF + var w := (v >> 16) & 0xFF + return (255 - w if a == m else 0) + (w if b == m else 0) + + +func _setze_punkt(idx: int, v: int) -> void: + _mats[idx] = v & 0xFF + _mix_b[idx] = (v >> 8) & 0xFF + _mix_w[idx] = (v >> 16) & 0xFF + + +## Regler „Kante: hart ↔ weich“ (nur Malen). +func set_weichheit(w: float) -> void: + brush_weichheit = clampf(w, 0.0, 1.0) + + +func set_falloff(f: float) -> void: + brush_falloff = clampf(f, 0.0, 1.0) + + +## Druck setzen (XR-Controller-Trigger 0–1 ruft das direkt; Pen/Maus laufen +## über _unhandled_input). Wert wird über TerrainField.pressure_of begrenzt. +func set_pressure(p: float) -> void: + pressure = TerrainField.pressure_of(p, 0.0) + + +func set_strength(mps: float) -> void: + brush_strength = clampf(mps, 1.0, 20.0) + + +func set_paint_material(id: int) -> void: + paint_material = clampi(id, 0, SvmapIO.MATERIAL_IDS.size() - 1) + _refresh_ring_mesh() + + +# --------------------------------------------------------------- Eingaben --- + +func _unhandled_input(event: InputEvent) -> void: + var p := MousePointer.from_input(event) + if p == null: + return + # Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll. + if p.kind == PointerEvent.Kind.MOVE: + p.pressure = TerrainField.pressure_of(p.pressure, _trigger_axis()) + if handle_pointer(p): + get_viewport().set_input_as_handled() + + +## Rechte Maustaste kommt hier nie an: sie gehört mit absolutem Vorrang dem +## Freelook-Flug (orbit_camera.gd, Karte t_2470de89), in jedem Werkzeugzustand. +func handle_pointer(p: PointerEvent) -> bool: + if not active: + return false + match p.kind: + PointerEvent.Kind.MOVE: + _update_cursor(p.position) + pressure = p.pressure + PointerEvent.Kind.PRESS: + # Karte t_2470de89: Strg = Invert-Modifier. Heben↔Senken + # tauschen (Blender/Photoshop-Konvention), im Malen-Modus + # malt Strg+Klick Gras zurück (alter Radierer-Weg). + var mode := brush_mode + var mat := -1 + if p.ctrl: + match brush_mode: + TerrainField.Mode.RAISE: + mode = TerrainField.Mode.LOWER + TerrainField.Mode.LOWER: + mode = TerrainField.Mode.RAISE + TerrainField.Mode.PAINT: + mat = 0 # Gras zurückmalen + _begin_stroke(mode, mat) + return true + PointerEvent.Kind.RELEASE: + _end_stroke() + return true + return false + + +## Gamepad-Trigger (rechts) des ersten Pads; 0 ohne Pad. Nur Eingabe-Lesen, +## keine OS-Annahme. +func _trigger_axis() -> float: + var pads := Input.get_connected_joypads() + if pads.is_empty(): + return 0.0 + return Input.get_joy_axis(pads[0], JOY_AXIS_TRIGGER_RIGHT) + + +func _update_cursor(viewport_pos: Vector2) -> void: + var hit: Variant = TerrainField.raycast(_heights, _res, _sx, _sz, + camera.project_ray_origin(viewport_pos), camera.project_ray_normal(viewport_pos)) + if hit == null: + _cursor_valid = false + if _ring != null: + _ring.visible = false + return + _cursor_valid = true + _cursor = hit as Vector3 + var half_x := _sx * 0.5 + var half_z := _sz * 0.5 + _cursor.x = clampf(_cursor.x, -half_x, half_x) + _cursor.z = clampf(_cursor.z, -half_z, half_z) + # Ring minimal über dem Gelände, damit er nicht im Mesh verschwindet. + _ring.position = Vector3(_cursor.x, _height_at_stroke(_cursor.x, _cursor.z) + 0.3, _cursor.z) + _ring.visible = true + + +func _height_at_stroke(x: float, z: float) -> float: + return TerrainField.height_at(_heights, _res, _sx, _sz, x, z) + + +# ----------------------------------------------------------------- Stroke --- + +func _begin_stroke(mode: int, material_id: int = -1) -> void: + if _stroking: + return + if not _cursor_valid: + main.call("_set_status", + "Der Pinsel braucht einen Punkt AUF dem Gelände — führe die Maus über die Karte und halte dann die Maustaste gedrückt.", + true) + return + _stroke_mode = mode + _stroke_material = paint_material if material_id < 0 else material_id + _stroking = true + if mode == TerrainField.Mode.FLATTEN: + _flatten_target = _height_at_stroke(_cursor.x, _cursor.z) + elif mode == TerrainField.Mode.RAMP: + _ramp_a = Vector2(_cursor.x, _cursor.z) + _ramp_ha = _height_at_stroke(_cursor.x, _cursor.z) + _ramp_b = _ramp_a + # Im Malen-Modus lebt die Material-Schicht während des Strichs + # (dekodiert — Legacy-Maps ohne Schicht starten mit alles-Gras). + if mode == TerrainField.Mode.PAINT: + _mats = SvmapIO.decode_materials(main.doc.get("terrain", {})) + _mres = SvmapIO.material_resolution(main.doc.get("terrain", {})) + _load_mix(main.doc.get("terrain", {})) + _refresh_ring_mesh() + main.call("_set_status", "%s läuft — Maustaste gedrückt halten. Beim Loslassen wird der ganze Strich EIN Rückgängig-Schritt (Strg+Z)." % _stroke_label()) + + +## Lesbarer Stroke-Name: Heben/Senken/Glätten bzw. „Malen (Sand)". +func _stroke_label() -> String: + if _stroke_mode == TerrainField.Mode.PAINT: + return "Malen (%s)" % SvmapIO.MATERIAL_IDS[clampi(_stroke_material, 0, SvmapIO.MATERIAL_IDS.size() - 1)] + return MODE_LABELS[_stroke_mode] + + +func _process(delta: float) -> void: + if not _stroking or not _cursor_valid: + return + if _stroke_mode == TerrainField.Mode.PAINT: + var painted := _paint_step(_cursor.x, _cursor.z) + if painted: + # Karte t_7a0a6b79: Malen lebt im Sub-Rect — update_terrain bekommt + # das betroffene Zellrechteck (inkl. Spiegelzellen) und leitet nur + # die Splat-Region neu ab; das Mesh selbst bleibt unangetastet + # (Vertex-Farben hängen NICHT vom Material ab). + if view_builder.has_method("set_terrain_mix"): + view_builder.call("set_terrain_mix", _mix_b, _mix_w) + view_builder.call("update_terrain", _heights, _mats, _splat_rect) + _ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3 + return + if _stroke_mode == TerrainField.Mode.RAMP: + # Rampe wirkt erst beim Loslassen — hier nur der Endpunkt + Ring-Hinweis. + _ramp_b = Vector2(_cursor.x, _cursor.z) + _update_ramp_line() + return + var amount := _step_amount(_stroke_mode, delta) + # Während des Strichs NICHT auf die Format-Range clampen — die zieht der + # Range-Expander danach nach (sonst friert Heben/Senken an der Kante ein). + var changed := _height_step(_cursor.x, _cursor.z, amount) + if changed: + _expand_range_if_needed() + # Karte t_55ed041a: Material-Schicht MITGEBEN — ohne sie hat der + # Live-Rebuild alles als Gras gerendert (Sand/Fels „verschwanden" + # während des Heben/Senk-Strichs und kehrten erst am Stroke-Ende + # mit dem Vollrebuild zurück). + view_builder.call("update_terrain", _heights, _mats) + _ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3 + + +## Stärke dieses Frames: Meter (Heben/Senken) bzw. Mischfaktor 0–1 (Glätten/ +## Einebnen) — skaliert mit dem Druck. +func _step_amount(mode: int, dt: float) -> float: + match mode: + TerrainField.Mode.SMOOTH: + return clampf(brush_strength / SMOOTH_RATE_DIVISOR * dt * pressure, 0.0, 1.0) + TerrainField.Mode.FLATTEN: + return clampf(brush_strength / FLATTEN_RATE_DIVISOR * dt * pressure, 0.0, 1.0) + return brush_strength * dt * pressure + + +## Höhe spannt das Höhenfeld automatisch auf, wenn der Pinsel über die +## Format-Grenzen (min/max_height) hinaus will — sonst wäre Senken auf einer +## frisch angelegten (flachen) Map unmöglich und langes Heben würde clampen. +## Die Range gehört zum terrain-Block → Undo stellt sie mit zurück. Spannweite +## bleibt ≤ 500 m (V6). +func _expand_range_if_needed() -> void: + var lo := TerrainField._min_h(_heights) + var hi := TerrainField._max_h(_heights) + if lo < _hmin: + _hmin = floorf(lo - 1.0) + if hi > _hmax: + _hmax = ceilf(hi + 1.0) + if _hmax - _hmin > 500.0: + if lo < _hmin: + _hmin = _hmax - 500.0 + else: + _hmax = _hmin + 500.0 + + +func _end_stroke() -> void: + if not _stroking: + return + _stroking = false + if _stroke_mode == TerrainField.Mode.RAMP: + _ramp_line.visible = false + if not _ramp_apply(): + main.call("_set_status", "Rampe: Start und Ende liegen zu nah beieinander — Klick, ziehen, loslassen.") + return # nichts geändert → kein leerer Undo-Eintrag + # Doc erst jetzt ändern: der GANZE Strich ist ein History-Eintrag. + var before: Dictionary = main.history.snapshot(["terrain"]) + var terrain: Dictionary = main.doc.get("terrain", {}) + if _stroke_mode == TerrainField.Mode.PAINT: + terrain["materials_r8_b64"] = SvmapIO.encode_materials(_mats) + SvmapIO.encode_mix(terrain, _mix_b, _mix_w) + main.history.commit("Boden gemalt (%s)" % SvmapIO.MATERIAL_IDS[clampi(_stroke_material, 0, SvmapIO.MATERIAL_IDS.size() - 1)], + before, main.history.snapshot(["terrain"])) + else: + terrain["min_height_m"] = _hmin + terrain["max_height_m"] = _hmax + terrain["heightmap_r16_b64"] = TerrainField.encode(_heights, _hmin, _hmax) + main.history.commit("Boden geformt (%s)" % MODE_LABELS[_stroke_mode], + before, main.history.snapshot(["terrain"])) + main.call("_on_terrain_stroke_committed") + + +## Rampe anwenden (Strichende): Start-Höhe = Höhe beim Klick, End-Höhe = Höhe +## unter dem Endpunkt. Breite = 2 × Radius. Symmetrie wie bei den Pinseln. +func _ramp_apply() -> bool: + if _ramp_a.distance_to(_ramp_b) < 1.0: + return false + var hb := _height_at_stroke(_ramp_b.x, _ramp_b.y) + var width := brush_radius * 2.0 + var snap := {} + var g := _world_to_grid(_ramp_a.x, _ramp_a.y) + if _sym_active(): + var g2 := _world_to_grid(_ramp_b.x, _ramp_b.y) + var r := _brush_region(g).merge(_brush_region(g2)) + snap = _snap_heights(r) + var changed := TerrainField.apply_ramp(_heights, _res, _sx, _sz, + _ramp_a, _ramp_b, _ramp_ha, hb, width, brush_falloff) + if changed and not snap.is_empty(): + _mirror_heights(snap, g) + if changed: + _expand_range_if_needed() + view_builder.call("update_terrain", _heights, _mats) + return changed + + +## Rampen-Vorschau: gelbe Linie vom Startpunkt zum Cursor (auf Geländehöhe). +func _update_ramp_line() -> void: + var im := _ramp_line.mesh as ImmediateMesh + im.clear_surfaces() + im.surface_begin(Mesh.PRIMITIVE_LINES) + im.surface_add_vertex(Vector3(_ramp_a.x, _ramp_ha + 0.5, _ramp_a.y)) + im.surface_add_vertex(Vector3(_ramp_b.x, _height_at_stroke(_ramp_b.x, _ramp_b.y) + 0.5, _ramp_b.y)) + im.surface_end() + _ramp_line.visible = true + + +## Programmgesteuerter Stroke (Demo/Screenshot-Hook): macht exakt, was eine +## Maus auch tun würde — inkl. History-Eintrag. +func demo_stroke(x: float, z: float, mode: int, seconds: float) -> void: + _refresh_from_doc() + _cursor = Vector3(x, 0.0, z) + _cursor_valid = true + _begin_stroke(mode) + var steps := int(ceil(seconds / 0.05)) + var dt := seconds / float(maxi(steps, 1)) + var save_x := _cursor.x + var save_z := _cursor.z + for i in steps: + var amount := _step_amount(mode, dt) + if mode == TerrainField.Mode.PAINT: + _paint_step(save_x, save_z) + continue + _height_step(save_x, save_z, amount) + _expand_range_if_needed() + _end_stroke() + _refresh_from_doc() + + +## Demo-Hook: Pinsel-Ring sichtbar an einer Weltposition parken (für +## Screenshots ohne echte Maus-Ereignisse). +func demo_park_brush(x: float, z: float) -> void: + _refresh_ring_mesh() + _cursor = Vector3(x, 0.0, z) + _cursor_valid = true + _ring.position = Vector3(x, _height_at_stroke(x, z) + 0.3, z) + _ring.visible = true + + +# ------------------------------------------------------------- Symmetrie ---- +## M2 (Karte t_f9eff526): Im Symmetrie-Modus (main.sym_mode ≠ AUS) schreibt +## jeder Strich seine Zellen EXAKT gespiegelt in die andere Kartenseite — +## Höhe ist ein Skalar → 1:1 identisch übernommen, Material ebenso. Die +## Zell-Abbildung kommt aus M1 (SymmetryCore.mirror_cell), nichts neu erfunden. +## +## Ablauf pro Pinsel-Schritt: +## 1. Pinsel EINMAL an der Strich-Position anwenden — bewusst NICHT zweimal +## (Strichpunkt + Spiegelpunkt): sonst bekämen Zellen im Überlappband den +## Effekt doppelt und an der Rastermitte entstünde ein Grat. +## 2. Die seit dem Snapshot geänderten Zellen des Pinsel-Rechtecks ermitteln +## und jede Änderung in die gespiegelte Schwesterzelle kopieren. +## Liegt der Strich genau auf der Mitte/Achse, bildet mirror_cell die Zellen +## auf sich selbst bzw. auf Partner mit IDENTISCHEm Wert ab (spiegelsymmetrischer +## Fußabdruck, gleicher Falloff-Abstand) → nichts wird gedoppelt. +## Überlappen Fußabdruck und Spiegelbild (Strich nahe der Mitte), gewinnt pro +## Zellpaar die Seite, die NÄHER am Strichpunkt liegt — die andere Hälfte ist +## exakte Kopie der Strich-Seite; bei exaktem Gleichstand entscheidet der +## kleinere Grid-Index (deterministisch, Row-major-Verarbeitung). +## Bei Modus AUS: keine Snapshot-/Kopierarbeit — exakt das alte Verhalten. + +## Zusätzlicher Rand um die grobe Pinsel-Region (TerrainField arbeitet mit +## floor/ceil ± 1; hier großzügig ± 2 — der Snapshot-Vergleich entscheidet +## ohnehin zellgenau, was wirklich geändert wurde). +const SYM_REGION_MARGIN := 2 + +func _sym_mode() -> int: + if main == null: + return SymmetryCore.Mode.OFF + # .get statt direkter Property: Haupts ohne sym_mode-Feld (z. B. alte + # Test-Stubs, Tools außerhalb des Editors) fallen auf AUS zurück — + # exakt das Verhalten vor M2. + var mode: Variant = main.get("sym_mode") + if mode == null or not (mode is int): + return SymmetryCore.Mode.OFF + return int(mode) + + +func _sym_active() -> bool: + return _sym_mode() != SymmetryCore.Mode.OFF + + +## Welt-X/Z → Raster-Koordinaten (gleiche Konvention wie TerrainField.height_at: +## Index 0 = −X/−Z-Ecke, x-Index läuft MIT +WeltX). +func _world_to_grid(x: float, z: float) -> Vector2: + return _world_to_grid_r(x, z, _res) + + +## Wie _world_to_grid, aber auf einem beliebigen Raster (Material: _mres). +func _world_to_grid_r(x: float, z: float, r: int) -> Vector2: + var last := float(r - 1) + return Vector2((x / _sx + 0.5) * last, (z / _sz + 0.5) * last) + + +## Grobes Rechteck um die Raster-Position (g), das den Pinsel-Fußabdruck +## jedes Modus garantiert abdeckt (Radius in Zellen + Margin). +func _brush_region(g: Vector2) -> Rect2i: + return _brush_region_r(g, _res) + + +## Wie _brush_region, auf dem Raster r (Material-Schicht: _mres). +func _brush_region_r(g: Vector2, r: int) -> Rect2i: + var cell_x := _sx / float(r - 1) + var cell_z := _sz / float(r - 1) + var rx := brush_radius / cell_x + float(SYM_REGION_MARGIN) + var rz := brush_radius / cell_z + float(SYM_REGION_MARGIN) + var x0 := maxi(int(floorf(g.x - rx)), 0) + var x1 := mini(int(ceilf(g.x + rx)), r - 1) + var z0 := maxi(int(floorf(g.y - rz)), 0) + var z1 := mini(int(ceilf(g.y + rz)), r - 1) + return Rect2i(x0, z0, x1 - x0 + 1, z1 - z0 + 1) + + +## Abstand einer Rasterzelle zum Strichpunkt in Metern (gleiche Zell-Metrik +## wie TerrainField.apply_brush: Zellindex-Abstand × Zellgröße). +func _cell_dist(pos: Vector2i, g: Vector2, cell_x: float, cell_z: float) -> float: + var dx := (float(pos.x) - g.x) * cell_x + var dz := (float(pos.y) - g.y) * cell_z + return sqrt(dx * dx + dz * dz) + + +## Ein Pinsel-Schritt HÖHE (Heben/Senken/Glätten) inkl. Spiegel-Kopie. +## amount: Meter (Heben/Senken) bzw. Mischfaktor 0–1 (Glätten) — unverändert +## zur bisherigen apply_brush-Semantik; ohne Clamp (Range-Expander zieht nach). +func _height_step(x: float, z: float, amount: float) -> bool: + var g := _world_to_grid(x, z) + var snap := {} + if _sym_active(): + snap = _snap_heights(_brush_region(g)) + var changed := TerrainField.apply_brush(_heights, _res, _sx, _sz, + -100000.0, 100000.0, x, z, brush_radius, _stroke_mode, amount, + brush_falloff, _flatten_target) + if changed and not snap.is_empty(): + _mirror_heights(snap, g) + return changed + + +## Ein Pinsel-Schritt MATERIAL (Malen) inkl. Spiegel-Kopie. +func _paint_step(x: float, z: float) -> bool: + # Material lebt auf seinem eigenen (feinen) Raster _mres (Karte 24.10.). + var g := _world_to_grid_r(x, z, _mres) + # VOR dem Pinsel: Zellrect des Fußabdrucks merken (deckt Spiegelzellen mit) + # — der Live-Splat-Refresh (t_7a0a6b79) leitet genau diese Region neu ab. + _splat_rect = _splat_region(g) + var snap := {} + if _sym_active() and _mats.size() >= _mres * _mres: + snap = _snap_mats(_brush_region_r(g, _mres)) + var changed := TerrainField.apply_material_brush_mix(_mats, _mix_b, _mix_w, _mres, + _sx, _sz, x, z, brush_radius, _stroke_material, brush_weichheit, pressure) + if changed and not snap.is_empty(): + _mirror_mats(snap, g) + return changed + + +## Snapshot {Vector2i(x, z) → Höhe} über das Pinsel-Rechteck (NUR bei +## Symmetrie AN erzeugt — bei AUS bleibt es bei exakt dem alten Verhalten). +func _snap_heights(rect: Rect2i) -> Dictionary: + var snap := {} + for z: int in range(rect.position.y, rect.end.y): + for x: int in range(rect.position.x, rect.end.x): + snap[Vector2i(x, z)] = _heights[z * _res + x] + return snap + + +## Spiegel-Pass für die Höhe: jede seit dem Snapshot geänderte Zelle wird in +## ihre gespiegelte Schwester kopiert (Strich-Seite ist Master, siehe oben). +func _mirror_heights(snap: Dictionary, g: Vector2) -> void: + var sym := _sym_mode() + var cell_x := _sx / float(_res - 1) + var cell_z := _sz / float(_res - 1) + var done := {} + var positions: Array = snap.keys() + # Row-major (kleinster Index zuerst): der Gleichstand-Tie-Break „kleinerer + # Index gewinnt“ ist damit deterministisch, und ein Paar wird genau einmal + # an seinem kleineren Index entschieden. + positions.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: + return (a.y * _res + a.x) < (b.y * _res + b.x)) + for pos: Vector2i in positions: + if done.has(pos): + continue + var idx: int = pos.y * _res + pos.x + var mirror: Vector2i = SymmetryCore.mirror_cell(sym, pos.x, pos.y, _res) + done[pos] = true + if mirror == pos: + continue # Selbstabbildung (Rastermitte/Achse) — nichts zu kopieren + var midx: int = mirror.y * _res + mirror.x + var value: float = _heights[idx] + if snap.has(mirror): + # Beide Seiten wurden vom Pinsel geändert (Fußabdruck überlappt + # sein Spiegelbild): näher am Strichpunkt gewinnt, Gleichstand + # → kleinere Index (== diese pos, da Row-major zuerst kam). + if _cell_dist(mirror, g, cell_x, cell_z) < _cell_dist(pos, g, cell_x, cell_z): + value = _heights[midx] + _heights[idx] = value + _heights[midx] = value + done[mirror] = true + + +## Snapshot {Vector2i(x, z) → Material-ID} über das Pinsel-Rechteck. +func _snap_mats(rect: Rect2i) -> Dictionary: + var snap := {} + for z: int in range(rect.position.y, rect.end.y): + for x: int in range(rect.position.x, rect.end.x): + snap[Vector2i(x, z)] = _punkt(z * _mres + x) + return snap + + +## Spiegel-Pass für die Material-Schicht (identische Paar-Logik wie _mirror_heights). +func _mirror_mats(snap: Dictionary, g: Vector2) -> void: + var sym := _sym_mode() + var cell_x := _sx / float(_mres - 1) + var cell_z := _sz / float(_mres - 1) + var done := {} + var positions: Array = snap.keys() + positions.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: + return (a.y * _mres + a.x) < (b.y * _mres + b.x)) + # Karte 25.10. (Sly: harte gerade Kanten mit Symmetrie): NUR Punkte spiegeln, + # die dieser Pinsel-Schritt wirklich verändert hat. Vorher wurde das ganze + # Snapshot-Quadrat auf die Gegenseite kopiert — weiche Verläufe drüben + # wurden an den Quadratkanten hart abgeschnitten. + for pos: Vector2i in positions: + if done.has(pos): + continue + done[pos] = true + var idx: int = pos.y * _mres + pos.x + if _punkt(idx) == int(snap[pos]): + continue # nicht bemalt → Gegenseite bleibt unangetastet + var mirror: Vector2i = SymmetryCore.mirror_cell(sym, pos.x, pos.y, _mres) + if mirror == pos: + continue + var midx: int = mirror.y * _mres + mirror.x + var value: int = _punkt(idx) + if snap.has(mirror) and _punkt(midx) != int(snap[mirror]): + # Beide Seiten bemalt (Pinsel überlappt sein Spiegelbild, z. B. nahe + # der Kartenmitte): der Wert mit dem GRÖSSEREN Anteil des Mal- + # Materials gewinnt. Vorher „näher am Strichpunkt“ — das zog entlang + # der Mittelsenkrechte eine harte gerade Kante durch weiche Verläufe + # (Sly 25.10.). Max ist stetig und symmetrisch. + if _anteil(_punkt(midx), _stroke_material) > _anteil(value, _stroke_material): + value = _punkt(midx) + _setze_punkt(idx, value) + done[mirror] = true + _setze_punkt(midx, value) + + +# ----------------------------------------------------------- Ring-Optik ----- + +func _refresh_ring_mesh() -> void: + if _ring == null: + return + if _ring_radius == brush_radius and _ring_mode == brush_mode: + return + _ring_radius = brush_radius + _ring_mode = brush_mode + var torus := TorusMesh.new() + torus.inner_radius = brush_radius - 0.25 + torus.outer_radius = brush_radius + 0.25 + torus.rings = 48 + torus.ring_segments = 8 + _ring.mesh = torus + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + match brush_mode: + TerrainField.Mode.RAISE: + mat.albedo_color = Color(0.4, 1.0, 0.45, 0.9) + TerrainField.Mode.LOWER: + mat.albedo_color = Color(1.0, 0.45, 0.3, 0.9) + TerrainField.Mode.PAINT: + # Ring in der Farbe des gewählten Materials (Karte t_603ec1a8). + var mc: int = clampi(paint_material, 0, SvmapIO.MATERIAL_IDS.size() - 1) + mat.albedo_color = (terrain_mesh_color(mc) as Color) + mat.albedo_color.a = 0.9 + TerrainField.Mode.FLATTEN: + mat.albedo_color = Color(1.0, 0.85, 0.3, 0.9) + TerrainField.Mode.RAMP: + mat.albedo_color = Color(0.85, 0.5, 1.0, 0.9) + _: + mat.albedo_color = Color(0.4, 0.7, 1.0, 0.9) + mat.emission_enabled = true + mat.emission = (mat.albedo_color as Color) * 0.5 + _ring.material_override = mat + + +## Material-ID → Renderer-Farbe (gleiche Palette wie terrain_mesh.gd). +func terrain_mesh_color(id: int) -> Color: + match id: + 1: + return Color(0.78, 0.70, 0.45) # Sand + 2: + return Color(0.45, 0.44, 0.42) # Fels + _: + return Color(0.30, 0.45, 0.22) # Gras + + +# ------------------------------------------------ Live-Splat-Sub-Rect (Karte +# t_7a0a6b79): Zellrechteck des letzten Mal-Schritts für das Live-Update der +# Splat-Gewichtstextur. Am Dateiende angefügt (Karten-Regel). + +## Zellrect des letzten Mal-Schritts (Pinsel-Fußabdruck ∪ gespiegelte +## Schwesterzellen) — vom Stroke-Code als 3. Parameter an update_terrain +## (view_builder) weitergereicht. +var _splat_rect := Rect2i() + + +## Zellrect des Pinsel-Fußabdrucks (∪ Spiegelzellen): dieselben Region- +## Grenzen wie apply_material_brush, mit dem SYM_REGION_MARGIN-Rand — der +## deckt auch die Kernel-Nachbarschaft der Splat-Ableitung mit ab. +## g und Ergebnis im Material-Raster (_mres) — so rechnet auch die Splat. +func _splat_region(g: Vector2) -> Rect2i: + var rect := _brush_region_r(g, _mres) + var sym := _sym_mode() + if sym == SymmetryCore.Mode.OFF: + return rect + var a := SymmetryCore.mirror_cell(sym, rect.position.x, rect.position.y, _mres) + var b := SymmetryCore.mirror_cell(sym, rect.end.x - 1, rect.end.y - 1, _mres) + return rect.merge(Rect2i(mini(a.x, b.x), mini(a.y, b.y), + absi(a.x - b.x) + 1, absi(a.y - b.y) + 1)) diff --git a/editor/terrain_tool.gd.uid b/editor/terrain_tool.gd.uid new file mode 100644 index 0000000..101a4a2 --- /dev/null +++ b/editor/terrain_tool.gd.uid @@ -0,0 +1 @@ +uid://bo7va0ktrfvrj diff --git a/editor/test_run.gd b/editor/test_run.gd new file mode 100644 index 0000000..1dbf3c2 --- /dev/null +++ b/editor/test_run.gd @@ -0,0 +1,110 @@ +class_name TestRun +extends RefCounted +## ALT (Karte t_1cdc1a72): dieser Client-Start ist seit dem Lauf-Modus im +## Map-Builder selbst NICHT mehr der Standard-Weg — „▶ Karte testen" startet +## jetzt IN DIESER APP (core/editor/playtest/, kein Client-Ordner, kein +## zweiter Prozess, kein VR). Der Rückbau dieser Datei kommt in einer +## späteren Karte; bis dahin bleibt die Export-/Such-Logik als Fallback +## erhalten und getestet (tests/test_testrun.gd). +## +## Ursprünglicher Zweck (Karte t_b84af40f): Export der AKTUELLEN Map nach Temp +## + Start des scopedvr-client als EIGENES Fenster im Testlauf-Modus +## (--testmap ). Der Editor bleibt offen — der Client ist ein eigener +## Prozess; das Spiel beendet sich selbst sauber (ESC = „Zurück zum Editor"). +## +## Client-Suche (erste Treffer-Zeile gewinnt): +## 1. Konfigurationsdatei user://scopedvr_editor.cfg, Abschnitt [test]: +## client_dir="/pfad/zu/scopedvr-client" +## 2. Klon neben dem Editor: /../scopedvr-client +## (erkennbar an project.godot im Ordner). +## +## Godot-Binary für den Start: das Binary, unter dem der Editor selbst läuft +## (OS.get_executable_path — Sly startet den Editor über den Godot-Editor, +## also kann DAS auch den Client starten). Läuft der Editor irgendwann als +## exportierte App, muss [test] godot_path gesetzt werden. +## +## Alles static + testbar ohne UI (tests/test_testrun.gd). + +const CFG_PATH := "user://scopedvr_editor.cfg" +const TESTRUN_DIR := "user://testrun" + + +static func load_cfg() -> ConfigFile: + var cfg := ConfigFile.new() + if FileAccess.file_exists(CFG_PATH): + cfg.load(CFG_PATH) # Fehler → Default-Logik greift weiter unten. + return cfg + + +## Liefert den Client-Projektordner (mit project.godot) oder "". +static func find_client(project_dir: String, cfg: ConfigFile = null) -> String: + var candidates: Array = [] + if cfg != null: + var configured := str(cfg.get_value("test", "client_dir", "")) + # Explizit gesetzter Pfad gewinnt, sobald der ORDNER existiert + # (bewusst ohne project.godot-Check — wer ihn konfiguriert, weiß es besser). + if not configured.is_empty() and DirAccess.dir_exists_absolute(configured): + return configured + var parent := project_dir.simplify_path().get_base_dir() + candidates.append(parent.path_join("scopedvr-client")) + for candidate in candidates: + var dir := str(candidate).simplify_path() + if FileAccess.file_exists(dir.path_join("project.godot")): + return dir + return "" + + +## Godot-Binary zum Starten des Clients (Editor-Binary oder Konfiguration). +static func find_godot(cfg: ConfigFile = null) -> String: + var exe := OS.get_executable_path() + if exe.get_file().to_lower().contains("godot"): + return exe + if cfg != null: + return str(cfg.get_value("test", "godot_path", "")) + return "" + + +## Deterministischer Temp-Pfad aus dem Map-Namen (erneuter Testlauf +## überschreibt die alte Export-Datei — kein Müll-Berg). +static func temp_map_path(doc: Dictionary) -> String: + var raw := str(doc.get("info", {}).get("name", "")).strip_edges() + if raw.is_empty(): + raw = "karte" + var safe := "" + for ch in raw: + var c := ch.to_lower() + if (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or ch == "_" or ch == "-" or ch == " ": + safe += ch + else: + safe += "_" + safe = safe.strip_edges().replace(" ", "_").rstrip("_") + if safe.is_empty(): + safe = "karte" + return TESTRUN_DIR.path_join(safe + ".svmap") + + +## Exportiert das AKTUELLE Dokument (auch ungespeicherte Maps!) nach Temp. +## Rückgabe: {ok, errors, path} — path ist globalisiert (für den Client-Prozess). +static func export_temp_map(doc: Dictionary) -> Dictionary: + var rel := temp_map_path(doc) + DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(TESTRUN_DIR)) + var result := SvmapIO.save_to_file(rel, doc) + result["path"] = ProjectSettings.globalize_path(rel) + return result + + +## Hand-Haltungs-Tool: liegt im Client-Projekt, weil es dessen Rig und Waffen +## direkt benutzt (kein VR, eigenes Fenster). +static func hand_pose_args(client_dir: String) -> PackedStringArray: + return PackedStringArray(["--xr-mode", "off", "--path", client_dir, + "res://tools/hand_pose_tool/hand_pose_tool.tscn"]) + + +## Import-Lauf für den Client, nötig nach neuem Stand (neue Modelle/Skripte). +static func import_args(client_dir: String) -> PackedStringArray: + return PackedStringArray(["--headless", "--path", client_dir, "--import"]) + + +## Argumentliste für den Client-Start (getestet: exakt diese Form). +static func launch_args(client_dir: String, map_path: String) -> PackedStringArray: + return PackedStringArray(["--path", client_dir, "--", "--testmap", map_path, "--mock-master"]) diff --git a/editor/test_run.gd.uid b/editor/test_run.gd.uid new file mode 100644 index 0000000..4ba94ac --- /dev/null +++ b/editor/test_run.gd.uid @@ -0,0 +1 @@ +uid://cd3gmuvpf5ldv diff --git a/editor/updater.gd b/editor/updater.gd new file mode 100644 index 0000000..17a62df --- /dev/null +++ b/editor/updater.gd @@ -0,0 +1,428 @@ +class_name EditorUpdater +extends RefCounted +## Editor-Selbstupdate (Karte t_d95f087a) — Update-Check über Git, Update- +## Ausführung (git pull + Godot-Import-Lauf) und plattformneutrale Restart- +## Auflösung. Alles static und ohne UI (tests/test_updater.gd). +## +## Regeln (Karte + Sly-Ansatz „das Programm sich selbst updaten lassen“): +## - Transport = Git (Klon des Editors), KEIN Binary-Download. +## - Keine Linux-Pfade hartkodiert: git (default "git") und ein optionaler +## Restart-Befehl kommen aus user://scopedvr_editor.cfg, Abschnitt [update] +## (git_command / restart_command). Ohne Konfiguration: unter Linux wird +## erkannt, ob eine systemd-User-Unit „map-editor“ existiert — dann klingelt +## ein Transient-Timer-Kick (systemd-run) den Unit-Neustart (der Kick läuft +## im EIGENEN Scope und überlebt die Stop-Phase). Sonst: der Editor startet +## sich selbst neu (mode "self", self_restart_args). +## - Import-Lauf-Lektion 17.10.: ein --import-Lauf kann mit Exit-Code 0 +## ENDEN und trotzdem Parse-/Script-Fehler enthalten (class_name-Cache). +## Die Ausgabe wird deshalb zusätzlich gescannt — bei Fehlern wird NICHT +## neu gestartet (Abbruch mit Meldung), sonst würde Sly einen Editor ohne +## Map-Ansicht sehen. +## - Offline / Tunnel weg → status "unreachable" → die UI bleibt still +## (kein Banner, kein Fehler-Geballer). +## - Git darf NIE interaktiv werden (Karte t_1457e935): kein GUI-Credential- +## Dialog mitten im Editor! Jeder git-Lauf läuft deshalb mit +## credential.helper=store und abgeschaltetem Askpass (spawn_argv) — +## fehlen die Zugangsdaten, endet der Lauf sofort mit einer klaren +## Fehlermeldung (status "auth_failed"), nie mit einem Prompt oder Hang. +## Hintergrund 17.10.: der systemd-User-Manager exportiert SSH_ASKPASS +## (ksshaskpass) — ein nackter git-Spawn hat damit mitten im Editor das +## Login-Fenster geöffnet. GIT_ASKPASS-/core.askPass-Override schlägt das +## geerbte SSH_ASKPASS (verifiziert am PC, 17.10.). + +const BRANCH := "main" +const UNIT_NAME := "map-editor" + +## Statuszeilen-Text für den auth_failed-Pfad (UI + Tests). +const AUTH_ERROR_TEXT := "Updates nicht prüfbar — Zugangsdaten fehlen (kein Login-Dialog geöffnet)." + + +## Belt-and-braces (Karte t_1457e935): argv für einen NICHT-interaktiven +## git-Lauf bauen. Rückgabe: [0] = Programm, Rest = Argumente. +## - "-c credential.helper=" leert ERST alle geerbten Helfer (sonst öffnet +## z. B. der Windows-Credential-Manager sein GUI-Fenster), danach +## "-c credential.helper=store": ausschließlich der Credential-Store +## (~/.git-credentials per HOME). +## - POSIX: Spawn über /usr/bin/env mit GIT_TERMINAL_PROMPT=0 und +## GIT_ASKPASS=/bin/true — das überstimmt auch ein GEERBTES SSH_ASKPASS +## (KDE/GNOME-Login-Dialog!). HOME kommt aus der eigenen Umgebung +## (OS.get_environment, kein Hardcode) und wird nur mitgegeben, wenn da. +## - Fallback ohne env-Wrapper (env fehlt / kein HOME): trotzdem kein Prompt +## möglich, weil core.askPass=/bin/true das Askpass-Fallback abschaltet. +static func spawn_argv(git_exe: String, project_dir: String, args: PackedStringArray) -> PackedStringArray: + var git_args := PackedStringArray(["-C", project_dir, + "-c", "credential.helper=", "-c", "credential.helper=store"]) + if not OS.has_feature("windows"): + git_args.append_array(PackedStringArray(["-c", "core.askPass=/bin/true"])) + git_args.append_array(args) + var home := OS.get_environment("HOME") + if not OS.has_feature("windows") and FileAccess.file_exists("/usr/bin/env") \ + and not home.is_empty(): + var out := PackedStringArray(["/usr/bin/env", + "GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=/bin/true", "HOME=" + home]) + out.append(git_exe) + out.append_array(git_args) + return out + var out := PackedStringArray([git_exe]) + out.append_array(git_args) + return out + + +## Ein Git-Kommando im Projektordner ausführen — IMMER nicht-interaktiv +## (spawn_argv). Rückgabe {ok, code, output} (output = stdout+stderr gemischt, +## damit Fehlerklassifikation sehen kann, was git gesagt hat). +static func git_run(git_exe: String, project_dir: String, args: PackedStringArray) -> Dictionary: + var argv := spawn_argv(git_exe, project_dir, args) + var exe := str(argv[0]) + var rest := argv.slice(1) + var output: Array = [] + var code := OS.execute(exe, rest, output, true) + var text := "" + for line in output: + text += str(line) + return {"ok": code == 0, "code": code, "output": text} + + +## Pure Prüfung (testbar): deutet die git-Ausgabe auf FEHLENDE Zugangsdaten? +## (Askpass ist abgeschaltet — „could not read Username“ ist dann der +## dokumentierte Nicht-Interaktiv-Fehler, kein Anlass für einen Retry.) +static func looks_like_auth_error(output: String) -> bool: + return output.contains("could not read Username") \ + or output.contains("Authentication failed") \ + or output.contains("terminal prompts disabled") \ + or output.contains("Unauthorized") + + +## Git-Binary: Konfiguration (user://scopedvr_editor.cfg → [update] +## git_command) gewinnt, sonst das Standard-"git" (PATH). +static func git_exe(cfg: ConfigFile = null) -> String: + if cfg != null: + var custom := str(cfg.get_value("update", "git_command", "")) + if not custom.is_empty(): + return custom + return "git" + + +## Lokaler HEAD-SHA. "" = kein Git-Repository (oder git nicht gefunden). +static func head_sha(git_exe: String, project_dir: String) -> String: + var r := git_run(git_exe, project_dir, PackedStringArray(["rev-parse", "HEAD"])) + if not bool(r["ok"]): + return "" + var text := str(r["output"]).strip_edges() + if text.is_empty(): + return "" + return text.split("\n")[0].strip_edges() + + +## Remote-SHA per git ls-remote (cheap, lesend — keine lokalen Referenzen +## verändert). "" = nicht erreichbar / Branch fehlt / Credentials fehlen. +static func remote_sha(git_exe: String, project_dir: String, branch: String) -> String: + var r := git_run(git_exe, project_dir, PackedStringArray([ + "ls-remote", "origin", "refs/heads/%s" % branch])) + if not bool(r["ok"]): + return "" + var text := str(r["output"]).strip_edges() + if text.is_empty(): + return "" + return sha_aus_ls_remote(text, branch) + + +## Update-Check: lokaler HEAD vs. origin/main. +## Rückgabe {status, local, remote, error} — status: +## "update_available" | "up_to_date" | "unreachable" | "error" | "auth_failed" +## "error" = kein Repo/git nicht da (Editor-spezifisches Problem) +## "unreachable" = Origin nicht erreichbar (offline, Tunnel weg) +## "auth_failed" = Zugangsdaten fehlen → UI zeigt klaren Hinweis, NIEMALS +## einen Login-Prompt (Karte t_1457e935). +static func check_update(git_exe: String, project_dir: String) -> Dictionary: + var local := head_sha(git_exe, project_dir) + if local.is_empty(): + return {"status": "error", "local": "", "remote": "", + "error": "Kein Git-Repository (oder git nicht gefunden)."} + var r := git_run(git_exe, project_dir, PackedStringArray([ + "ls-remote", "origin", "refs/heads/%s" % BRANCH])) + if not bool(r["ok"]): + var text := str(r["output"]) + if looks_like_auth_error(text): + return {"status": "auth_failed", "local": local, "remote": "", + "error": AUTH_ERROR_TEXT} + return {"status": "unreachable", "local": local, "remote": "", + "error": "Origin nicht erreichbar (offline oder Tunnel weg)."} + var text2 := str(r["output"]).strip_edges() + if text2.is_empty(): + return {"status": "unreachable", "local": local, "remote": "", + "error": "Origin nicht erreichbar (offline oder Tunnel weg)."} + var remote := sha_aus_ls_remote(text2, BRANCH) + if remote.is_empty(): + # Kein echter Commit in der Ausgabe (nur Warnungen/leer) — NICHT als + # Update deuten, sonst hängt das Banner endlos (Karte t_50a07d4b-Live-Bug). + return {"status": "unreachable", "local": local, "remote": "", + "error": "Origin lieferte keinen Commit (nur Warnungen)."} + if local == remote: + return {"status": "up_to_date", "local": local, "remote": remote, "error": ""} + # Befund 25.10.: „Update verfügbar“ blieb nach dem Update stehen, weil nur + # HEAD != origin/main geprüft wurde. Steht der Klon auf einem anderen + # Branch oder VOR main (z. B. Feature-Branch), ist das KEIN Update: + # nur melden, wenn der Remote-Commit NICHT schon in HEAD steckt. + if remote_in_head(git_exe, project_dir, remote): + return {"status": "up_to_date", "local": local, "remote": remote, "error": ""} + return {"status": "update_available", "local": local, "remote": remote, "error": ""} + + +## Ist `remote` schon Vorfahre von HEAD (Klon gleich oder VORAUS)? Unbekannter +## Commit (Exit 128) = noch nicht geholt = echtes Update → false. +static func remote_in_head(git_exe: String, project_dir: String, remote: String) -> bool: + var r := git_run(git_exe, project_dir, PackedStringArray([ + "merge-base", "--is-ancestor", remote, "HEAD"])) + return bool(r["ok"]) + + +## Aus der gemischten ls-remote-Ausgabe den Commit für `branch` lesen. +## WARUM: `git_run` mischt stdout+stderr, und der git-Client schreibt bei einer +## HTTP-Umleitung zuerst "warning: Umleitung nach ..." — die alte Fassung nahm +## stumpf die ERSTE Zeile und hielt "warning" für den Ziel-Commit. Ergebnis war +## ein Banner "Update verfügbar: → warning", das nie verschwand, obwohl +## der Pull korrekt lief (live belegt 20.10., Klon stand auf origin/main). +## Regeln: nur Zeilen "\t" zählen, sha = echter Hex-String. +static func sha_aus_ls_remote(text: String, branch: String) -> String: + var ziel := "refs/heads/%s" % branch + var locker := "" + for zeile in text.split("\n"): + var z := zeile.strip_edges() + if z.is_empty(): + continue + var klein := z.to_lower() + if klein.begins_with("warning") or klein.begins_with("hint") \ + or klein.begins_with("fatal") or klein.begins_with("error"): + continue + var teile := z.split("\t") + if teile.size() < 2: + continue + var sha := teile[0].strip_edges() + if not ist_sha(sha): + continue + if teile[1].strip_edges() == ziel: + return sha + if locker.is_empty(): + locker = sha + return locker + + +## Sieht der Text wie ein Git-Objekt-Hash aus (7-40 Hex-Zeichen)? +static func ist_sha(text: String) -> bool: + var t := text.strip_edges() + if t.length() < 7 or t.length() > 40: + return false + for i in t.length(): + var c := t[i] + if c >= "0" and c <= "9": + continue + if c >= "a" and c <= "f": + continue + if c >= "A" and c <= "F": + continue + return false + return true + + +## Kurzer SHA für Anzeigen (7 Zeichen, wie git log --oneline). +static func short_sha(sha: String) -> String: + return sha.substr(0, 7) + + +## Banner-Text (pure, testbar). +static func banner_text(local_sha: String, remote_sha: String) -> String: + return "🔄 Update verfügbar: %s → %s" % [short_sha(local_sha), short_sha(remote_sha)] + + +## Klartext-Status für die Statuszeile (Karte t_f73a1cc5): Wenn lokale +## (meist von Godot erzeugte) Dateien das Update blockieren WÜRDEN, legt der +## Pull sie selbst beiseite — Sly liest diesen Satz statt einer rohen Git- +## Zeile („Bitte verschieben oder entfernen Sie diese…“). +const AUTO_STASH_INFO_TEXT := "Update blockiert: eigene erzeugte Dateien — werden automatisch beiseitegelegt und das Update läuft weiter." +## Ehrliche Meldung, wenn das Zurückholen kollidiert ist (Stash bleibt als +## Sicherung liegen; Godot baut die erzeugten Dateien beim Import neu). +const STASH_KEPT_TEXT := "Erzeugte Dateien bleiben beiseitegelegt — beim nächsten Start neu aufgebaut (Sicherungs-Stash bleibt liegen, nichts verloren)." +## Kennung der eigenen Auto-Stashes (Verwerfen alter Einträge, damit sie +## sich nicht anhäufen). +const AUTO_STASH_MESSAGE := "auto: Selbst-Update" + + +## Update ausführen: git pull --ff-only (Klon zieht nur nach, dreht nichts +## um). Vorher werden lokal Erzeugtes/Geändertes AUTOMATISCH beiseitegelegt +## (Karte t_f73a1cc5): `git stash push -u` erfasst GEÄNDERT **und** UNTRACKED +## Dateien (`--autostash` allein reicht nicht — es lässt untracked liegen; +## genau die haben am 19.10. den Pull blockiert). Nach dem Pull wird das +## Beiseitegelegte zurückgeholt; kollidiert das (Typfall: lose `.import`- +## Datei trifft auf dieselbe Datei neu getrackt), bleibt der Stash als +## Sicherung liegen und die Statuszeile bekommt den Klartext — NIE ein halb +## gepoppter Zustand. Rückgabe {ok, output, errors, info} (info = Klartext +## fürs Statuszeile-Fenster, leer wenn nichts besonderes passiert ist). +static func pull(git_exe: String, project_dir: String) -> Dictionary: + var stash_info := _stash_local_changes(git_exe, project_dir) + # Gezielt origin/main ziehen (nicht den Upstream des aktuellen Branches). + var r := git_run(git_exe, project_dir, PackedStringArray(["pull", "--ff-only", "origin", BRANCH])) + var errors: Array = [] + var text := str(r["output"]).strip_edges() + var info := "" + if not bool(r["ok"]): + # Pull fehlgeschlagen (z. B. offline): Beiseitegelegtes zurückholen, + # damit nichts „verschwindet", wenn der Fehler nur am Netz lag. + if not stash_info.is_empty(): + _restore_stash(git_exe, project_dir) + errors.append("git pull --ff-only fehlgeschlagen (Exit-Code %s):" % str(r["code"])) + if not text.is_empty(): + errors.append(text) + else: + errors.append("(keine Ausgabe — git nicht gefunden oder Verbindung sofort weg)") + if looks_like_auth_error(text): + errors.append("Zugangsdaten fehlen — es wird bewusst KEIN Login-Dialog geöffnet (Credential-Store prüfen).") + else: + if not stash_info.is_empty(): + info = AUTO_STASH_INFO_TEXT + var restored := _restore_stash(git_exe, project_dir) + if not bool(restored["ok"]): + info += "\n" + STASH_KEPT_TEXT + # Der Map-Kern ist ein Submodul: ein Pull allein holt ihn nicht nach. + var sub := git_run(git_exe, project_dir, PackedStringArray(["submodule", "update", "--init", "--recursive"])) + if not bool(sub["ok"]): + errors.append("git submodule update fehlgeschlagen (Exit-Code %s):" % str(sub["code"])) + errors.append(str(sub["output"]).strip_edges()) + if looks_like_auth_error(str(sub["output"])): + errors.append("Zugangsdaten fehlen — es wird bewusst KEIN Login-Dialog geöffnet (Credential-Store prüfen).") + return {"ok": false, "output": str(sub["output"]), "errors": errors, "info": info} + return {"ok": bool(r["ok"]), "output": str(r["output"]), "errors": errors, "info": info} + + +## Lokales Beiseitelegen vor dem Pull (Karte t_f73a1cc5). Rückgabe: Referenz +## auf den eigenen Auto-Stash (z. B. "stash@{0}") oder "" wenn es nichts zu +## beiseitelegen gab / der Stash nicht angelegt werden konnte. +## - Zuerst alte EIGENE Auto-Stashes verwerfen (gleiche Kennung) — sie +## enthalten nur erzeugte Dateien und dürfen sich nicht anhäufen. +## - "No local changes to save" endet ebenfalls mit Exit 0 → NICHT am Text +## matchen, sondern prüfen, ob ein Eintrag mit unserer Kennung entstand. +static func _stash_local_changes(git_exe: String, project_dir: String) -> String: + _drop_auto_stashes(git_exe, project_dir) + var r := git_run(git_exe, project_dir, PackedStringArray([ + "stash", "push", "-u", "-m", AUTO_STASH_MESSAGE])) + if not bool(r["ok"]): + return "" + return _auto_stash_ref(git_exe, project_dir) + + +## Referenz des eigenen Auto-Stashes ("stash@{N}") oder "". +static func _auto_stash_ref(git_exe: String, project_dir: String) -> String: + var r := git_run(git_exe, project_dir, PackedStringArray([ + "stash", "list", "--pretty=format:%gd|%s"])) + if not bool(r["ok"]): + return "" + for line in str(r["output"]).split("\n"): + var parts := str(line).strip_edges().split("|", true, 1) + if parts.size() == 2 and str(parts[1]).contains(AUTO_STASH_MESSAGE): + return str(parts[0]) + return "" + + +## Alle EIGENEN Auto-Stashes (Kennung AUTO_STASH_MESSAGE) verwerfen — von +## hinten, weil die Indizes sich beim Drop verschieben. +static func _drop_auto_stashes(git_exe: String, project_dir: String) -> void: + var r := git_run(git_exe, project_dir, PackedStringArray([ + "stash", "list", "--pretty=format:%gd|%s"])) + if not bool(r["ok"]): + return + var refs: Array = [] + for line in str(r["output"]).split("\n"): + var parts := str(line).strip_edges().split("|", true, 1) + if parts.size() == 2 and str(parts[1]).contains(AUTO_STASH_MESSAGE): + refs.append(str(parts[0])) + refs.reverse() + for ref in refs: + git_run(git_exe, project_dir, PackedStringArray(["stash", "drop", ref])) + + +## Beiseitegelegtes zurückholen. apply (nicht pop), damit die Referenz nicht +## am Stack verschiebt; bei Erfolg wird der Eintrag gedroppt. Kollidiert das +## Zurückholen (Typfall: erzeugte Datei ist inzwischen getrackt), wird mit +## `git reset --hard` der halb gepoppte Zustand weggeräumt und der Stash ALS +## SICHERUNG LIEGEN GELASSEN — kein Datenverlust, Godot erzeugt die Dateien +## beim nächsten Import-Lauf neu. Bewusst KEIN `git clean`: untracked +## Nutzdaten (z. B. Maps/) dürfen nie angefasst werden. +## Rückgabe {ok, kept, output}. +static func _restore_stash(git_exe: String, project_dir: String) -> Dictionary: + var ref := _auto_stash_ref(git_exe, project_dir) + if ref.is_empty(): + return {"ok": true, "kept": false, "output": ""} + var r := git_run(git_exe, project_dir, PackedStringArray(["stash", "apply", ref])) + if bool(r["ok"]): + git_run(git_exe, project_dir, PackedStringArray(["stash", "drop", ref])) + return {"ok": true, "kept": false, "output": str(r["output"])} + git_run(git_exe, project_dir, PackedStringArray(["reset", "--hard"])) + return {"ok": false, "kept": true, "output": str(r["output"])} + + +## Import-Lauf: --headless --path --import. Muss NACH dem +## Pull laufen (neue class_name-Dateien in den Cache), FEHLSCHLAG = Update- +## Abbruch OHNE Neustart (Lektion 17.10.). Rückgabe {ok, errors, output}. +static func run_import(godot_exe: String, project_dir: String) -> Dictionary: + var args := PackedStringArray(["--headless", "--path", project_dir, "--import"]) + var output: Array = [] + var code := OS.execute(godot_exe, args, output, true) + var errors := import_errors(output) + if code != 0: + errors.append("Import-Lauf fehlgeschlagen (Exit-Code %s)" % str(code)) + return {"ok": errors.is_empty(), "errors": errors, "output": _output_text(output)} + + +## Pure Prüfung (testbar): enthält die Import-Ausgabe Parse-/Script-Fehler? +## Exit-Code 0 allein beweist NICHT einen sauberen Import (Lektion 17.10.). +static func import_errors(output: Array) -> Array: + var errors: Array = [] + for line in output: + var text := str(line) + if text.contains("Parse Error") or text.contains("SCRIPT ERROR"): + errors.append(text.strip_edges()) + return errors + + +static func _output_text(output: Array) -> String: + var text := "" + for line in output: + text += str(line) + return text + + +## Restart-Auflösung (plattformneutral, getestet): +## 1. cfg [update] restart_command gesetzt → mode "command", argv = an +## Leerzeichen gesplittet. (z. B. systemd-Unit-Kick, Batch-Skript — jede +## Plattform setzt hier ihren eigenen Befehl ein.) +## 2. Linux + systemd-User-Unit „map-editor“ existiert → Transient-Timer- +## Kick (systemd-run, 3 s): der Kick läuft in einem EIGENEN Scope und +## wird beim Stoppen des Editors (Unit-Neustart) nicht mitgekillt. +## 3. Sonst: mode "self" — der Editor startet sich selbst neu (gleiche +## Godot-Binary, --editor --path ) und beendet sich danach. +static func resolve_restart(cfg: ConfigFile = null) -> Dictionary: + if cfg != null: + var cmd := str(cfg.get_value("update", "restart_command", "")) + if not cmd.is_empty(): + return {"mode": "command", "argv": PackedStringArray(cmd.split(" ", false))} + if OS.has_feature("linux"): + var probe: Array = [] + var code := OS.execute("systemctl", PackedStringArray([ + "--user", "list-unit-files", UNIT_NAME + ".service", "--no-legend"]), + probe, true) + var text := "" + for line in probe: + text += str(line) + if code == 0 and not text.strip_edges().is_empty(): + return {"mode": "command", "argv": PackedStringArray([ + "systemd-run", "--user", "--on-active=3s", + "systemctl", "--user", "restart", UNIT_NAME + ".service"])} + return {"mode": "self", "argv": PackedStringArray()} + + +## Argumente für den Selbst-Restart: dieselbe Godot-Binary startet die +## Map-Editor-APP über deren main.tscn (kein "--editor" — das würde den +## Godot-Editor-Shell öffnen, nicht unsere App; Live-Befund 17.10.: +## Nach dem Selbst-Update ging der Godot-Editor auf statt dem Map Editor). +static func self_restart_args(project_dir: String) -> PackedStringArray: + return PackedStringArray(["--path", project_dir]) \ No newline at end of file diff --git a/editor/updater.gd.uid b/editor/updater.gd.uid new file mode 100644 index 0000000..3edbb95 --- /dev/null +++ b/editor/updater.gd.uid @@ -0,0 +1 @@ +uid://nxdbipruop30 diff --git a/library/svmap_wasser.gd b/library/svmap_wasser.gd index 755f89b..ec5e21f 100644 --- a/library/svmap_wasser.gd +++ b/library/svmap_wasser.gd @@ -34,7 +34,7 @@ extends RefCounted ## byteidentisch, siehe tools/sync_wasser.sh im Map-Editor). const SHADER_KANDIDATEN: Array[String] = [ "res://shaders/wasser.gdshader", # scopedvr-client - "res://src/viewport/wasser.gdshader", # scopedvr-map-editor (Spiegel) + "res://core/viewport/wasser.gdshader", # scopedvr-map-editor (Spiegel) ] ## Plausibilitätsbereich des svmap-Feldes `water_level` (Meter, y-Höhe). diff --git a/scenes/main.tscn b/scenes/main.tscn new file mode 100644 index 0000000..b999e5e --- /dev/null +++ b/scenes/main.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://core/ui/main.gd" id="1_main"] + +[node name="Main" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_main") diff --git a/server/admin_client.gd b/server/admin_client.gd new file mode 100644 index 0000000..ccc41b2 --- /dev/null +++ b/server/admin_client.gd @@ -0,0 +1,112 @@ +class_name AdminClient +extends Node +## Client der Match-Server-Admin-API v1 (Doku: docs/admin-api.md im +## scopedvr-match-server-Repo). Knoten in den Baum hängen, dann +## `var res = await client.call_api("GET", "/status")`. +## +## Antwort: {ok: bool (HTTP 2xx), status: int, body: Dictionary, transport_error: String} +## transport_error ist deutsch und leer, wenn der Server geantwortet hat. +## Das Token geht NUR als Header raus und wird nie geloggt oder zurückgegeben. + +const TIMEOUT_S := 30.0 +const MAX_RESPONSE_BYTES := 8 * 1024 * 1024 + +var host: String = "" +var port: int = 24580 +var tls: bool = false +var token: String = "" + + +## Server-Eintrag aus dem ServerStore übernehmen. +func use(entry: Dictionary) -> void: + host = str(entry.get("host", "")) + port = int(entry.get("port", 24580)) + tls = bool(entry.get("tls", false)) + token = str(entry.get("token", "")) + + +static func base_url(p_host: String, p_port: int, p_tls: bool) -> String: + var h := p_host + if h.contains(":") and not h.begins_with("["): + h = "[%s]" % h # IPv6 + return "%s://%s:%d/api/v1" % ["https" if p_tls else "http", h, p_port] + + +## Prüft die Eingabe für „Server hinzufügen“. Leerer Text = ok. +static func check_address(p_host: String, p_port: int) -> String: + var h := p_host.strip_edges() + if h.is_empty(): + return "Bitte eine Adresse eingeben (IP oder Name)." + if h.contains("/") or h.contains(" ") or h.contains("://"): + return "Nur die Adresse eintragen, ohne http:// und ohne Schrägstriche (z. B. 192.168.1.20)." + if p_port < 1 or p_port > 65535: + return "Der Port muss zwischen 1 und 65535 liegen." + return "" + + +func call_api(method: String, path: String, body: Variant = null, query: Dictionary = {}, raw: PackedByteArray = PackedByteArray()) -> Dictionary: + var err := check_address(host, port) + if err != "": + return _transport_fail(err) + var url := base_url(host, port, tls) + path + if not query.is_empty(): + var parts: PackedStringArray = [] + for k in query.keys(): + parts.append("%s=%s" % [str(k).uri_encode(), str(query[k]).uri_encode()]) + url += "?" + "&".join(parts) + var headers: PackedStringArray = ["Accept: application/json"] + if token != "": + headers.append("Authorization: Bearer " + token) + var payload := raw + if body != null: + payload = JSON.stringify(body).to_utf8_buffer() + headers.append("Content-Type: application/json") + elif not raw.is_empty(): + headers.append("Content-Type: application/octet-stream") + var http := HTTPRequest.new() + http.timeout = TIMEOUT_S + http.body_size_limit = MAX_RESPONSE_BYTES + http.accept_gzip = false + add_child(http) + var m := _method(method) + var rerr := http.request_raw(url, headers, m, payload) + if rerr != OK: + http.queue_free() + return _transport_fail("Anfrage konnte nicht gestartet werden (Fehler %d)." % rerr) + var done: Array = await http.request_completed + http.queue_free() + var result: int = done[0] + if result != HTTPRequest.RESULT_SUCCESS: + return _transport_fail(_result_text(result)) + var code: int = done[1] + var parsed: Variant = JSON.parse_string((done[3] as PackedByteArray).get_string_from_utf8()) + var out: Dictionary = parsed if parsed is Dictionary else {} + return {"ok": code >= 200 and code < 300, "status": code, "body": out, "transport_error": ""} + + +static func _method(m: String) -> int: + match m.to_upper(): + "POST": return HTTPClient.METHOD_POST + "PUT": return HTTPClient.METHOD_PUT + "PATCH": return HTTPClient.METHOD_PATCH + "DELETE": return HTTPClient.METHOD_DELETE + return HTTPClient.METHOD_GET + + +static func _transport_fail(msg: String) -> Dictionary: + return {"ok": false, "status": 0, "body": {}, "transport_error": msg} + + +static func _result_text(result: int) -> String: + match result: + HTTPRequest.RESULT_CANT_CONNECT, HTTPRequest.RESULT_CONNECTION_ERROR: + return "Keine Verbindung zum Server. Läuft er, ist die Admin-API eingeschaltet, und stimmen Adresse und Port?" + HTTPRequest.RESULT_CANT_RESOLVE: + return "Die Adresse wurde nicht gefunden. Tippfehler?" + HTTPRequest.RESULT_TIMEOUT: + return "Der Server antwortet nicht (Zeitüberschreitung)." + HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR: + return "Sichere Verbindung (https) fehlgeschlagen. Gibt der Server wirklich https her?" + HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED: + return "Die Antwort des Servers ist zu groß." + return "Verbindungsfehler (Code %d)." % result diff --git a/server/admin_client.gd.uid b/server/admin_client.gd.uid new file mode 100644 index 0000000..5985b57 --- /dev/null +++ b/server/admin_client.gd.uid @@ -0,0 +1 @@ +uid://dd3mjb50m63hm diff --git a/server/admin_texte.gd b/server/admin_texte.gd new file mode 100644 index 0000000..c7adf5b --- /dev/null +++ b/server/admin_texte.gd @@ -0,0 +1,101 @@ +class_name AdminTexte +extends RefCounted +## Verständliche deutsche Texte zu Admin-API-Antworten (kein Fachchinesisch, +## kein rohes JSON) – inklusive der Fehlerliste (422) beim Map-Upload. + +const TUNNEL_HINWEIS := "Tipp: Die Admin-API hört standardmäßig nur auf dem Server selbst (localhost). Von außen erreichst du sie sicher über einen SSH-Tunnel (z. B. ssh -L 24580:127.0.0.1:24580 benutzer@server, dann hier Adresse 127.0.0.1) oder über einen Reverse-Proxy mit https. Die Verbindung selbst ist sonst unverschlüsselt." + +## Erklärungen für häufige Prüfregeln der Map (Server und Editor nutzen dieselben IDs). +const REGEL_HILFE := { + "V1": "Die Datei ist zu groß.", + "V2": "Die Datei ist keine gültige Map-Datei.", + "V3": "Format-Version passt nicht – Editor und Server müssen zusammenpassen.", + "V4": "Ein Pflichtbereich der Map fehlt.", + "V6": "Das Terrain ist zu groß oder zu detailreich.", + "V8": "Zu viele Objekte oder zu viele Polygone – Objekte entfernen.", + "V11": "Pro Team werden mindestens 4 Spawn-Punkte gebraucht (Spawn-Zonen malen).", + "V14": "Die Map ragt über die erlaubte Größe hinaus.", +} + + +## Antwort → Text für den Nutzer ("" bei Erfolg ohne Besonderheiten). +static func erklaere(res: Dictionary) -> String: + if str(res.get("transport_error", "")) != "": + return str(res["transport_error"]) + "\n\n" + TUNNEL_HINWEIS + var status := int(res.get("status", 0)) + var body: Dictionary = res.get("body", {}) + var server_text := str(body.get("error", "")) + match status: + 401: + return "Das Token stimmt nicht (oder fehlt). Es steht beim Server in der Admin-Datei (state_file) – bitte neu eintragen." + 429: + return "Zu viele falsche Token-Versuche. Der Server sperrt dich kurz – bitte in ein paar Minuten erneut versuchen." + 404: + if str(body.get("code", "")) == "not_found": + return "Diese Funktion kennt der Server nicht. Ist er auf dem neuesten Stand?" + 413: + return "Die Datei ist größer als der Server erlaubt. " + server_text + 422: + return erklaere_map_fehler(body) + if server_text != "": + return server_text + if status >= 200 and status < 300: + return "" + return "Unerwartete Antwort vom Server (Status %d)." % status + + +## Fehlerliste einer abgelehnten Map als Aufzählung mit Hilfetexten. +static func erklaere_map_fehler(body: Dictionary) -> String: + var errs: Array = body.get("errors", []) + var out := "Der Server hat die Map abgelehnt" + out += " (%d Problem%s):\n" % [errs.size(), "" if errs.size() == 1 else "e"] if not errs.is_empty() else ".\n" + var hints := {} + for e in errs: + var t := str(e) + out += "• " + t + "\n" + var rule := t.split(":")[0].strip_edges() + for k in REGEL_HILFE.keys(): + if rule == k or rule.begins_with(k) and not rule.substr(k.length(), 1).is_valid_int(): + hints[k] = REGEL_HILFE[k] + if not hints.is_empty(): + out += "\nSo behebst du es:\n" + for k in hints.keys(): + out += "• %s: %s\n" % [k, hints[k]] + return out.strip_edges() + + +## Dateiname für den Upload: a–z, 0–9, _ und -, höchstens 40 Zeichen (wie der Server). +static func slugify(text: String) -> String: + var t := text.strip_edges().to_lower() + for pair in [["ä", "ae"], ["ö", "oe"], ["ü", "ue"], ["ß", "ss"]]: + t = t.replace(pair[0], pair[1]) + var out := "" + for c in t: + out += c if ((c >= "a" and c <= "z") or (c >= "0" and c <= "9") or c == "-") else "_" + while out.contains("__"): + out = out.replace("__", "_") + out = out.lstrip("_-").rstrip("_") + return out.substr(0, 40) + + +## Wirkung einer Einstellung in Klartext. +static func wirkung(effect: String) -> String: + match effect: + "live": return "sofort" + "next_map": return "ab der nächsten Map" + "restart": return "nach Server-Neustart" + return effect + + +static func neustart_zeitueberschreitung(seconds: int) -> String: + return "Der Server ist nach %d s nicht zurück. Er kommt nur von selbst wieder, wenn Docker (Restart-Policy), systemd oder tools/start_server.* ihn gestartet haben." % seconds + + +static func sperrdauer_text(minutes: int) -> String: + if minutes <= 0: + return "dauerhaft" + if minutes < 60: + return "%d Minuten" % minutes + if minutes < 1440: + return "%d Stunden" % (minutes / 60) + return "%d Tage" % (minutes / 1440) diff --git a/server/admin_texte.gd.uid b/server/admin_texte.gd.uid new file mode 100644 index 0000000..88dcc8e --- /dev/null +++ b/server/admin_texte.gd.uid @@ -0,0 +1 @@ +uid://baspf3qu07cwi diff --git a/server/map_push.gd b/server/map_push.gd new file mode 100644 index 0000000..28c0260 --- /dev/null +++ b/server/map_push.gd @@ -0,0 +1,40 @@ +class_name MapPush +extends RefCounted +## „Map auf Server pushen“: aktuelle Map lokal prüfen und signieren +## (SvmapIO.to_signed_text), dann per Admin-API hochladen. UI-frei und testbar. + +## opts: {name: String ("" = aus Map-Name), overwrite: bool, add_to_rotation: bool} +## Rückgabe: {ok, stage ("lokal"|"upload"), text (verständliche Meldung), +## id (Server-Map-ID), exists (bool: 409 – Nachfrage nötig), status} +static func push(client: AdminClient, doc: Dictionary, opts: Dictionary = {}) -> Dictionary: + var signed := SvmapIO.to_signed_text(doc) + if not bool(signed["ok"]): + var lines := "Die Map ist noch nicht gültig und wurde nicht hochgeladen:\n" + for e in signed["errors"]: + lines += "• %s\n" % str(e) + return {"ok": false, "stage": "lokal", "text": lines.strip_edges(), "id": "", "exists": false, "status": 0} + var info: Dictionary = doc.get("info", {}) + var slug := str(opts.get("name", "")).strip_edges() + if slug == "": + slug = AdminTexte.slugify(str(info.get("name", ""))) + if slug == "": + return {"ok": false, "stage": "lokal", "text": "Die Map braucht einen Namen (Tab „Map“), bevor sie hochgeladen werden kann.", "id": "", "exists": false, "status": 0} + var bytes: PackedByteArray = str(signed["text"]).to_utf8_buffer() + var query := {"name": slug} + if bool(opts.get("overwrite", false)): + query["overwrite"] = "1" + if bool(opts.get("add_to_rotation", false)): + query["add_to_rotation"] = "1" + var res: Dictionary = await client.call_api("POST", "/maps", null, query, bytes) + var body: Dictionary = res.get("body", {}) + if bool(res.get("ok", false)): + var msg := "Hochgeladen als „%s“ (%s, %d Objekte)." % [str(body.get("name", slug)), _groesse(bytes.size()), int(body.get("objects", 0))] + if bool(body.get("replaced", false)): + msg += " Die vorherige Version wurde ersetzt." + return {"ok": true, "stage": "upload", "text": msg, "id": str(body.get("id", "")), "exists": false, "status": 200} + var exists := int(res.get("status", 0)) == 409 and str(body.get("code", "")) == "exists" + return {"ok": false, "stage": "upload", "text": AdminTexte.erklaere(res), "id": "", "exists": exists, "status": int(res.get("status", 0))} + + +static func _groesse(n: int) -> String: + return "%.1f MB" % (n / 1048576.0) if n >= 1048576 else "%d KB" % maxi(1, n / 1024) diff --git a/server/map_push.gd.uid b/server/map_push.gd.uid new file mode 100644 index 0000000..f15652d --- /dev/null +++ b/server/map_push.gd.uid @@ -0,0 +1 @@ +uid://cmuts8c8t2d5i diff --git a/server/server_store.gd b/server/server_store.gd new file mode 100644 index 0000000..06eb350 --- /dev/null +++ b/server/server_store.gd @@ -0,0 +1,114 @@ +class_name ServerStore +extends RefCounted +## Gespeicherte Admin-Server des Editors (Name, Adresse, Port, https, Token). +## +## Das Token liegt NICHT im Klartext auf der Platte: die Datei ist mit einem +## aus Rechner-ID + Nutzerordner abgeleiteten Schlüssel verschlüsselt. Das hält +## Zufallsleser, Cloud-Sync und Screenshots von Ordnern raus – gegen Schadsoftware +## mit Zugriff auf deinen Benutzer schützt es nicht (dafür gibt es keine +## plattformübergreifende Lösung ohne Passwort-Manager). Tokens werden nie geloggt. + +const DEFAULT_PATH := "user://admin_server.dat" +const DEFAULT_PORT := 24580 +const MAX_SERVERS := 50 + +var path: String = DEFAULT_PATH +var servers: Array = [] # [{id, name, host, port, tls, token}] +var last_error: String = "" + + +func _init(p: String = DEFAULT_PATH) -> void: + path = p + + +func _key() -> String: + return ("svr-editor-admin|%s|%s" % [OS.get_unique_id(), OS.get_user_data_dir()]).sha256_text() + + +func load_store() -> void: + servers.clear() + last_error = "" + if not FileAccess.file_exists(path): + return + var fa := FileAccess.open_encrypted_with_pass(path, FileAccess.READ, _key()) + if fa == null: + last_error = "Gespeicherte Server konnten nicht gelesen werden (andere Installation oder beschädigte Datei)." + return + var parsed: Variant = JSON.parse_string(fa.get_as_text()) + if parsed is Array: + for e in parsed: + if e is Dictionary and str(e.get("host", "")) != "": + servers.append({ + "id": str(e.get("id", _new_id())), "name": str(e.get("name", e["host"])), + "host": str(e["host"]), "port": int(e.get("port", DEFAULT_PORT)), + "tls": bool(e.get("tls", false)), "token": str(e.get("token", ""))}) + + +func save_store() -> bool: + var fa := FileAccess.open_encrypted_with_pass(path, FileAccess.WRITE, _key()) + if fa == null: + last_error = "Server-Liste konnte nicht gespeichert werden (Fehler %d)." % FileAccess.get_open_error() + return false + fa.store_string(JSON.stringify(servers)) + return true + + +## Neuer oder aktualisierter Eintrag. Rückgabe: id ("" = Eingabe ungültig → last_error). +func upsert(entry: Dictionary) -> String: + var err := AdminClient.check_address(str(entry.get("host", "")), int(entry.get("port", 0))) + if err != "": + last_error = err + return "" + var e := { + "id": str(entry.get("id", "")), "name": str(entry.get("name", "")).strip_edges(), + "host": str(entry["host"]).strip_edges(), "port": int(entry["port"]), + "tls": bool(entry.get("tls", false)), "token": str(entry.get("token", "")).strip_edges()} + if e["name"] == "": + e["name"] = "%s:%d" % [e["host"], e["port"]] + if e["id"] == "": + if servers.size() >= MAX_SERVERS: + last_error = "Maximal %d gespeicherte Server." % MAX_SERVERS + return "" + e["id"] = _new_id() + servers.append(e) + else: + var i := index_of(e["id"]) + if i < 0: + servers.append(e) + else: + servers[i] = e + save_store() + return e["id"] + + +func remove(id: String) -> bool: + var i := index_of(id) + if i < 0: + return false + servers.remove_at(i) + save_store() + return true + + +func index_of(id: String) -> int: + for i in servers.size(): + if str(servers[i]["id"]) == id: + return i + return -1 + + +func get_entry(id: String) -> Dictionary: + var i := index_of(id) + return servers[i].duplicate() if i >= 0 else {} + + +## Für Anzeige/Logs: nie das Token. +func public_view(id: String) -> Dictionary: + var e := get_entry(id) + e.erase("token") + e["has_token"] = str(get_entry(id).get("token", "")) != "" + return e + + +static func _new_id() -> String: + return "s%d_%d" % [int(Time.get_unix_time_from_system()), randi() % 100000] diff --git a/server/server_store.gd.uid b/server/server_store.gd.uid new file mode 100644 index 0000000..2a1a4cc --- /dev/null +++ b/server/server_store.gd.uid @@ -0,0 +1 @@ +uid://dkwfi2v8nu03w diff --git a/shaders/selection_outline.gdshader b/shaders/selection_outline.gdshader new file mode 100644 index 0000000..60d7f07 --- /dev/null +++ b/shaders/selection_outline.gdshader @@ -0,0 +1,40 @@ +shader_type spatial; +render_mode unshaded, cull_front, shadows_disabled, blend_mix; + +// Außenhüllen-Kontur (U6, Karte t_639135dd): dezente Auswahl-Markierung +// statt Vollflächen-Orange. Das Grundmaterial (inkl. Textur) bleibt komplett +// sichtbar — die Kontur lebt als eigene Hülle um das Modell: +// +// VERTEX += NORMAL * breite_m bläht das Volumen entlang der Normalen auf, +// cull_front rendert nur die RÜCKseiten der Hülle. Sichtbar bleibt darum +// genau der schmale Saum, der außerhalb der Silhouette liegt (der Rest +// wird vom Objekt selbst per Tiefentest verdeckt) — die klassische +// Inverted-Hull-Outline-Technik. +// +// Farbton, Deckkraft und Breite kommen aus dem Theme (theme/editor_theme.tres, +// Abschnitt "Editor") — der Shader selbst kennt keine festen Farben. +// +// Gestrichelte Variante (Spiegel-Zwilling): streifen_hz > 0 unterteilt die +// Kontur in Bänder entlang der Welthöhe — dünn + gestrichelt statt voll. + +uniform vec4 linien_farbe : source_color = vec4(1.0, 0.6, 0.15, 0.95); +uniform float breite_m : hint_range(0.0, 1.0) = 0.08; +uniform float streifen_hz : hint_range(0.0, 40.0) = 0.0; + +varying vec3 welt_pos; + +void vertex() { + VERTEX += NORMAL * breite_m; + welt_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; +} + +void fragment() { + if (streifen_hz > 0.5) { + // Gestrichelt (Zwilling): Bänder entlang der Welthöhe, ~55 % Strich. + if (fract(welt_pos.y * streifen_hz) > 0.55) { + discard; + } + } + ALBEDO = linien_farbe.rgb; + ALPHA = linien_farbe.a; +} diff --git a/shaders/selection_outline.gdshader.uid b/shaders/selection_outline.gdshader.uid new file mode 100644 index 0000000..ae9b0ac --- /dev/null +++ b/shaders/selection_outline.gdshader.uid @@ -0,0 +1 @@ +uid://b1wjgw8l4w77m diff --git a/svmap/group_state.gd b/svmap/group_state.gd index e6c2d01..0eaee95 100644 --- a/svmap/group_state.gd +++ b/svmap/group_state.gd @@ -1,7 +1,7 @@ extends RefCounted class_name GroupState ## Gruppen-ZUSTAND über das Objekt-Feld "group" hinaus (Karte t_a4bf9b8c, U3 — -## Gruppen im Szenen-Baum). GroupOps (src/editor/group_ops.gd) besitzt die +## Gruppen im Szenen-Baum). GroupOps (core/editor/group_ops.gd) besitzt die ## Zugehörigkeit (Optionales Objekt-Feld "group", int ≥ 0) — diese Datei ergänzt ## NUR ADDITIV, was der Baum additionally braucht: ## diff --git a/theme/editor_theme.tres b/theme/editor_theme.tres new file mode 100644 index 0000000..569c234 --- /dev/null +++ b/theme/editor_theme.tres @@ -0,0 +1,281 @@ +[gd_resource type="Theme" load_steps=23 format=3] + +[sub_resource type="StyleBoxFlat" id="panel"] +bg_color = Color(0.12, 0.14, 0.17, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="tab_sel"] +bg_color = Color(0.24, 0.3, 0.36, 1) +border_color = Color(0.35, 0.55, 0.7, 1) +border_width_top = 2 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 4.0 +content_margin_bottom = 4.0 + +[sub_resource type="StyleBoxFlat" id="tab_hover"] +bg_color = Color(0.19, 0.24, 0.29, 1) +border_color = Color(0.35, 0.55, 0.7, 0.4) +border_width_top = 2 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 4.0 +content_margin_bottom = 4.0 + +[sub_resource type="StyleBoxFlat" id="tab_unsel"] +bg_color = Color(0.15, 0.18, 0.21, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 4.0 +content_margin_bottom = 4.0 + +[sub_resource type="StyleBoxFlat" id="tabbar"] +bg_color = Color(0.1, 0.12, 0.14, 1) + +[sub_resource type="StyleBoxFlat" id="btn_normal"] +bg_color = Color(0.19, 0.23, 0.27, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="btn_hover"] +bg_color = Color(0.25, 0.31, 0.37, 1) +border_color = Color(0.35, 0.55, 0.7, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="btn_pressed"] +bg_color = Color(0.14, 0.19, 0.24, 1) +border_color = Color(0.35, 0.55, 0.7, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="btn_disabled"] +bg_color = Color(0.16, 0.17, 0.19, 1) +border_color = Color(0.24, 0.26, 0.29, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="btn_focus"] +draw_center = false +border_color = Color(0.55, 0.8, 1, 0.85) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="line_normal"] +bg_color = Color(0.09, 0.11, 0.13, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="line_focus"] +bg_color = Color(0.1, 0.13, 0.15, 1) +border_color = Color(0.55, 0.8, 1, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="tooltip"] +bg_color = Color(0.08, 0.09, 0.11, 0.96) +border_color = Color(0.35, 0.55, 0.7, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 6.0 +content_margin_right = 6.0 +content_margin_top = 4.0 +content_margin_bottom = 4.0 + +[sub_resource type="StyleBoxFlat" id="split_grab"] +bg_color = Color(0.22, 0.27, 0.32, 1) +corner_radius_top_left = 2 +corner_radius_top_right = 2 +corner_radius_bottom_right = 2 +corner_radius_bottom_left = 2 + +[sub_resource type="StyleBoxFlat" id="split_grab_hover"] +bg_color = Color(0.35, 0.55, 0.7, 0.6) +corner_radius_top_left = 2 +corner_radius_top_right = 2 +corner_radius_bottom_right = 2 +corner_radius_bottom_left = 2 + +[sub_resource type="StyleBoxFlat" id="scroll_bg"] +bg_color = Color(0.1, 0.12, 0.14, 0.4) + +[sub_resource type="StyleBoxFlat" id="scroll_grab"] +bg_color = Color(0.42, 0.49, 0.56, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="scroll_grab_hover"] +bg_color = Color(0.55, 0.63, 0.7, 0.8) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="scroll_grab_press"] +bg_color = Color(0.55, 0.8, 1, 0.85) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="progress_bg"] +bg_color = Color(0.09, 0.11, 0.13, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="progress_fill"] +bg_color = Color(0.55, 0.8, 1, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxEmpty" id="scroll_empty"] + +[resource] +default_font_size = 16 +Editor/colors/accent = Color(0.55, 0.8, 1, 1) +Editor/colors/status_ok = Color(0.75, 0.95, 0.75, 1) +Editor/colors/status_error = Color(1, 0.6, 0.5, 1) +Editor/colors/muted = Color(1, 1, 1, 0.74) +Editor/colors/field = Color(1, 1, 1, 0.9) +Editor/colors/title = Color(1, 1, 1, 0.82) +Editor/colors/warning = Color(1, 0.62, 0.3, 1) +Editor/colors/badge_outline = Color(0, 0, 0, 1) +Editor/colors/help_bg = Color(0.09, 0.11, 0.13, 0.97) +Editor/colors/help_border = Color(0.35, 0.55, 0.7, 1) +Editor/colors/update_bg = Color(0.36, 0.25, 0.06, 0.97) +Editor/colors/update_border = Color(0.95, 0.72, 0.15, 1) +Editor/colors/rubber_fill = Color(0.45, 0.8, 1, 0.16) +Editor/colors/rubber_line = Color(0.45, 0.8, 1, 0.9) +Editor/colors/selection_outline = Color(1, 0.6, 0.15, 0.95) +Editor/colors/selection_outline_hover = Color(1, 0.6, 0.15, 0.45) +Editor/colors/selection_outline_partner = Color(1, 0.78, 0.4, 0.85) +Editor/constants/selection_outline_width_mm = 80 +Editor/constants/selection_outline_width_hover_mm = 45 +Editor/constants/selection_outline_width_partner_mm = 45 +PanelContainer/styles/panel = SubResource("panel") +TabContainer/styles/panel = SubResource("panel") +TabContainer/styles/tab_selected = SubResource("tab_sel") +TabContainer/styles/tab_hovered = SubResource("tab_hover") +TabContainer/styles/tab_unselected = SubResource("tab_unsel") +TabContainer/styles/tabbar_background = SubResource("tabbar") +TabContainer/colors/font_selected_color = Color(1, 1, 1, 1) +TabContainer/colors/font_unselected_color = Color(1, 1, 1, 0.88) +Button/styles/normal = SubResource("btn_normal") +Button/styles/hover = SubResource("btn_hover") +Button/styles/pressed = SubResource("btn_pressed") +Button/styles/disabled = SubResource("btn_disabled") +Button/styles/focus = SubResource("btn_focus") +Button/colors/font_hover_color = Color(1, 1, 1, 1) +Button/colors/font_pressed_color = Color(0.8, 0.9, 1, 1) +Button/colors/font_disabled_color = Color(1, 1, 1, 0.5) +LineEdit/styles/normal = SubResource("line_normal") +LineEdit/styles/focus = SubResource("line_focus") +LineEdit/colors/font_color = Color(1, 1, 1, 1) +LineEdit/colors/font_placeholder_color = Color(1, 1, 1, 0.58) +HSplitContainer/styles/grabber_area = SubResource("split_grab") +HSplitContainer/styles/grabber_area_highlight = SubResource("split_grab_hover") +VSplitContainer/styles/grabber_area = SubResource("split_grab") +VSplitContainer/styles/grabber_area_highlight = SubResource("split_grab_hover") +ScrollContainer/styles/panel = SubResource("scroll_empty") +VScrollBar/styles/scroll = SubResource("scroll_bg") +VScrollBar/styles/grabber = SubResource("scroll_grab") +VScrollBar/styles/grabber_highlight = SubResource("scroll_grab_hover") +VScrollBar/styles/grabber_pressed = SubResource("scroll_grab_press") +HScrollBar/styles/scroll = SubResource("scroll_bg") +HScrollBar/styles/grabber = SubResource("scroll_grab") +HScrollBar/styles/grabber_highlight = SubResource("scroll_grab_hover") +HScrollBar/styles/grabber_pressed = SubResource("scroll_grab_press") +ProgressBar/styles/background = SubResource("progress_bg") +ProgressBar/styles/fill = SubResource("progress_fill") +ProgressBar/colors/font_color = Color(1, 1, 1, 1) +HSlider/styles/grabber_area = SubResource("progress_fill") +HSlider/styles/grabber_area_highlight = SubResource("progress_fill") +TooltipPanel/styles/panel = SubResource("tooltip") +TooltipLabel/colors/font_color = Color(1, 1, 1, 1) diff --git a/thumbs/acacia_003.png b/thumbs/acacia_003.png new file mode 100644 index 0000000..3875411 Binary files /dev/null and b/thumbs/acacia_003.png differ diff --git a/thumbs/acacia_003.png.import b/thumbs/acacia_003.png.import new file mode 100644 index 0000000..f6713f4 --- /dev/null +++ b/thumbs/acacia_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsnwxasr4kxnw" +path="res://.godot/imported/acacia_003.png-49a9ced50b30b6d6eaf8fde6f7c9611f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/acacia_003.png" +dest_files=["res://.godot/imported/acacia_003.png-49a9ced50b30b6d6eaf8fde6f7c9611f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/acacia_005.png b/thumbs/acacia_005.png new file mode 100644 index 0000000..3c10af8 Binary files /dev/null and b/thumbs/acacia_005.png differ diff --git a/thumbs/acacia_005.png.import b/thumbs/acacia_005.png.import new file mode 100644 index 0000000..94bf388 --- /dev/null +++ b/thumbs/acacia_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpng4qbmhgaxn" +path="res://.godot/imported/acacia_005.png-f44002b723f89b248078d6195e099cbf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/acacia_005.png" +dest_files=["res://.godot/imported/acacia_005.png-f44002b723f89b248078d6195e099cbf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/acacia_006.png b/thumbs/acacia_006.png new file mode 100644 index 0000000..8ac1e72 Binary files /dev/null and b/thumbs/acacia_006.png differ diff --git a/thumbs/acacia_006.png.import b/thumbs/acacia_006.png.import new file mode 100644 index 0000000..f26595c --- /dev/null +++ b/thumbs/acacia_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0vjob86u0mkh" +path="res://.godot/imported/acacia_006.png-1b9ce5d662b2c0fa9ce9be7d19023a1a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/acacia_006.png" +dest_files=["res://.godot/imported/acacia_006.png-1b9ce5d662b2c0fa9ce9be7d19023a1a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/air_defense_001.png b/thumbs/air_defense_001.png new file mode 100644 index 0000000..e24da7b Binary files /dev/null and b/thumbs/air_defense_001.png differ diff --git a/thumbs/air_defense_001.png.import b/thumbs/air_defense_001.png.import new file mode 100644 index 0000000..3b92a70 --- /dev/null +++ b/thumbs/air_defense_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxjkjfi837uo7" +path="res://.godot/imported/air_defense_001.png-eb0e8e33303a3e8d166565a1dbaca56b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/air_defense_001.png" +dest_files=["res://.godot/imported/air_defense_001.png-eb0e8e33303a3e8d166565a1dbaca56b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/air_defense_002.png b/thumbs/air_defense_002.png new file mode 100644 index 0000000..5a6c9c9 Binary files /dev/null and b/thumbs/air_defense_002.png differ diff --git a/thumbs/air_defense_002.png.import b/thumbs/air_defense_002.png.import new file mode 100644 index 0000000..b322dec --- /dev/null +++ b/thumbs/air_defense_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmkfqnti7k861" +path="res://.godot/imported/air_defense_002.png-2f8aa6e0069f96ad4eeacf95eb7535f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/air_defense_002.png" +dest_files=["res://.godot/imported/air_defense_002.png-2f8aa6e0069f96ad4eeacf95eb7535f5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/air_defense_platform_001.png b/thumbs/air_defense_platform_001.png new file mode 100644 index 0000000..a676ea6 Binary files /dev/null and b/thumbs/air_defense_platform_001.png differ diff --git a/thumbs/air_defense_platform_001.png.import b/thumbs/air_defense_platform_001.png.import new file mode 100644 index 0000000..88f27be --- /dev/null +++ b/thumbs/air_defense_platform_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cyvumt7k7tfkt" +path="res://.godot/imported/air_defense_platform_001.png-160acbcde1d7fef8e625c7afbec5977a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/air_defense_platform_001.png" +dest_files=["res://.godot/imported/air_defense_platform_001.png-160acbcde1d7fef8e625c7afbec5977a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/air_defense_platform_002.png b/thumbs/air_defense_platform_002.png new file mode 100644 index 0000000..e33a5d1 Binary files /dev/null and b/thumbs/air_defense_platform_002.png differ diff --git a/thumbs/air_defense_platform_002.png.import b/thumbs/air_defense_platform_002.png.import new file mode 100644 index 0000000..30b0620 --- /dev/null +++ b/thumbs/air_defense_platform_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blm2ew1gswl1e" +path="res://.godot/imported/air_defense_platform_002.png-dcb2adbe864891c2a6ca48f38513fd29.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/air_defense_platform_002.png" +dest_files=["res://.godot/imported/air_defense_platform_002.png-dcb2adbe864891c2a6ca48f38513fd29.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/antenna_001.png b/thumbs/antenna_001.png new file mode 100644 index 0000000..765a196 Binary files /dev/null and b/thumbs/antenna_001.png differ diff --git a/thumbs/antenna_001.png.import b/thumbs/antenna_001.png.import new file mode 100644 index 0000000..c8f376b --- /dev/null +++ b/thumbs/antenna_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cu2km1kitwe32" +path="res://.godot/imported/antenna_001.png-384a2221c556e630d3471ecce14aaa32.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/antenna_001.png" +dest_files=["res://.godot/imported/antenna_001.png-384a2221c556e630d3471ecce14aaa32.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/apc_001.png b/thumbs/apc_001.png new file mode 100644 index 0000000..d48571c Binary files /dev/null and b/thumbs/apc_001.png differ diff --git a/thumbs/apc_001.png.import b/thumbs/apc_001.png.import new file mode 100644 index 0000000..ffd1c28 --- /dev/null +++ b/thumbs/apc_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckp5nasktsw6d" +path="res://.godot/imported/apc_001.png-d008eeb8b81737eb9d55844b93c0ebbc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/apc_001.png" +dest_files=["res://.godot/imported/apc_001.png-d008eeb8b81737eb9d55844b93c0ebbc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/apc_002.png b/thumbs/apc_002.png new file mode 100644 index 0000000..d262e0a Binary files /dev/null and b/thumbs/apc_002.png differ diff --git a/thumbs/apc_002.png.import b/thumbs/apc_002.png.import new file mode 100644 index 0000000..0b9026d --- /dev/null +++ b/thumbs/apc_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b75b4yhnejh2m" +path="res://.godot/imported/apc_002.png-7b6a91012ea309e1a9cb9b866229ce8b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/apc_002.png" +dest_files=["res://.godot/imported/apc_002.png-7b6a91012ea309e1a9cb9b866229ce8b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/apc_cap.png b/thumbs/apc_cap.png new file mode 100644 index 0000000..675b864 Binary files /dev/null and b/thumbs/apc_cap.png differ diff --git a/thumbs/apc_cap.png.import b/thumbs/apc_cap.png.import new file mode 100644 index 0000000..042d9ac --- /dev/null +++ b/thumbs/apc_cap.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://08m553i6la3k" +path="res://.godot/imported/apc_cap.png-232f17eb7efe055b948255afbdd4217f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/apc_cap.png" +dest_files=["res://.godot/imported/apc_cap.png-232f17eb7efe055b948255afbdd4217f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/apc_cap_1.png b/thumbs/apc_cap_1.png new file mode 100644 index 0000000..aa217f7 Binary files /dev/null and b/thumbs/apc_cap_1.png differ diff --git a/thumbs/apc_cap_1.png.import b/thumbs/apc_cap_1.png.import new file mode 100644 index 0000000..47d6ac7 --- /dev/null +++ b/thumbs/apc_cap_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkxw5r6shmdeo" +path="res://.godot/imported/apc_cap_1.png-9b6fee694e04ad2228678a4684329f4b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/apc_cap_1.png" +dest_files=["res://.godot/imported/apc_cap_1.png-9b6fee694e04ad2228678a4684329f4b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/apc_weapon.png b/thumbs/apc_weapon.png new file mode 100644 index 0000000..5cea823 Binary files /dev/null and b/thumbs/apc_weapon.png differ diff --git a/thumbs/apc_weapon.png.import b/thumbs/apc_weapon.png.import new file mode 100644 index 0000000..b051a5e --- /dev/null +++ b/thumbs/apc_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clmsnievmhcdv" +path="res://.godot/imported/apc_weapon.png-9ea6fa1fb0eee874cd8c3d13ac62b73f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/apc_weapon.png" +dest_files=["res://.godot/imported/apc_weapon.png-9ea6fa1fb0eee874cd8c3d13ac62b73f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barracks_001.png b/thumbs/barracks_001.png new file mode 100644 index 0000000..6c723e1 Binary files /dev/null and b/thumbs/barracks_001.png differ diff --git a/thumbs/barracks_001.png.import b/thumbs/barracks_001.png.import new file mode 100644 index 0000000..2e21676 --- /dev/null +++ b/thumbs/barracks_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhqfuk76plea1" +path="res://.godot/imported/barracks_001.png-12946249c6dd7571051e0a5b3c7cecad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barracks_001.png" +dest_files=["res://.godot/imported/barracks_001.png-12946249c6dd7571051e0a5b3c7cecad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_001.png b/thumbs/barrel_001.png new file mode 100644 index 0000000..6cf0f22 Binary files /dev/null and b/thumbs/barrel_001.png differ diff --git a/thumbs/barrel_001.png.import b/thumbs/barrel_001.png.import new file mode 100644 index 0000000..fb3d14d --- /dev/null +++ b/thumbs/barrel_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8d3x37mwv6xr" +path="res://.godot/imported/barrel_001.png-bef224f28b42bc3c1cb70c9153b02176.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_001.png" +dest_files=["res://.godot/imported/barrel_001.png-bef224f28b42bc3c1cb70c9153b02176.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_002.png b/thumbs/barrel_002.png new file mode 100644 index 0000000..25079d5 Binary files /dev/null and b/thumbs/barrel_002.png differ diff --git a/thumbs/barrel_002.png.import b/thumbs/barrel_002.png.import new file mode 100644 index 0000000..1dfb055 --- /dev/null +++ b/thumbs/barrel_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x036pdk80s03" +path="res://.godot/imported/barrel_002.png-7062eaf20902780bd584d834de43d6da.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_002.png" +dest_files=["res://.godot/imported/barrel_002.png-7062eaf20902780bd584d834de43d6da.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_003.png b/thumbs/barrel_003.png new file mode 100644 index 0000000..286909d Binary files /dev/null and b/thumbs/barrel_003.png differ diff --git a/thumbs/barrel_003.png.import b/thumbs/barrel_003.png.import new file mode 100644 index 0000000..fab904b --- /dev/null +++ b/thumbs/barrel_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clandxfaibniy" +path="res://.godot/imported/barrel_003.png-5fcd83720a21d80d1a51cd0c221f64ca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_003.png" +dest_files=["res://.godot/imported/barrel_003.png-5fcd83720a21d80d1a51cd0c221f64ca.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_004.png b/thumbs/barrel_004.png new file mode 100644 index 0000000..09fda6d Binary files /dev/null and b/thumbs/barrel_004.png differ diff --git a/thumbs/barrel_004.png.import b/thumbs/barrel_004.png.import new file mode 100644 index 0000000..0e23c04 --- /dev/null +++ b/thumbs/barrel_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://r3uuorqbq6g7" +path="res://.godot/imported/barrel_004.png-abdeeae3e15f27c4a137ea172a5702f8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_004.png" +dest_files=["res://.godot/imported/barrel_004.png-abdeeae3e15f27c4a137ea172a5702f8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_005.png b/thumbs/barrel_005.png new file mode 100644 index 0000000..17ad7ec Binary files /dev/null and b/thumbs/barrel_005.png differ diff --git a/thumbs/barrel_005.png.import b/thumbs/barrel_005.png.import new file mode 100644 index 0000000..0bcee6b --- /dev/null +++ b/thumbs/barrel_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1bivgvwdvioh" +path="res://.godot/imported/barrel_005.png-57073d19efec96bde302d98dd1310518.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_005.png" +dest_files=["res://.godot/imported/barrel_005.png-57073d19efec96bde302d98dd1310518.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_006.png b/thumbs/barrel_006.png new file mode 100644 index 0000000..b0a863e Binary files /dev/null and b/thumbs/barrel_006.png differ diff --git a/thumbs/barrel_006.png.import b/thumbs/barrel_006.png.import new file mode 100644 index 0000000..0e83108 --- /dev/null +++ b/thumbs/barrel_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3vp6eyi4g2bj" +path="res://.godot/imported/barrel_006.png-bf1e1fbc93af64203f6371ddc251cc96.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_006.png" +dest_files=["res://.godot/imported/barrel_006.png-bf1e1fbc93af64203f6371ddc251cc96.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_007.png b/thumbs/barrel_007.png new file mode 100644 index 0000000..3f3e77b Binary files /dev/null and b/thumbs/barrel_007.png differ diff --git a/thumbs/barrel_007.png.import b/thumbs/barrel_007.png.import new file mode 100644 index 0000000..92e56c5 --- /dev/null +++ b/thumbs/barrel_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4atq8e72m7my" +path="res://.godot/imported/barrel_007.png-6bb01fa7d72694d810267f50d2dc9207.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_007.png" +dest_files=["res://.godot/imported/barrel_007.png-6bb01fa7d72694d810267f50d2dc9207.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_008.png b/thumbs/barrel_008.png new file mode 100644 index 0000000..d2ecc9e Binary files /dev/null and b/thumbs/barrel_008.png differ diff --git a/thumbs/barrel_008.png.import b/thumbs/barrel_008.png.import new file mode 100644 index 0000000..1c199ed --- /dev/null +++ b/thumbs/barrel_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co0hauu3itiu3" +path="res://.godot/imported/barrel_008.png-925ddab443ff5e9880e8fed9403e62d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_008.png" +dest_files=["res://.godot/imported/barrel_008.png-925ddab443ff5e9880e8fed9403e62d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_009.png b/thumbs/barrel_009.png new file mode 100644 index 0000000..1988fe1 Binary files /dev/null and b/thumbs/barrel_009.png differ diff --git a/thumbs/barrel_009.png.import b/thumbs/barrel_009.png.import new file mode 100644 index 0000000..5f6cb14 --- /dev/null +++ b/thumbs/barrel_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwb0huh22ist2" +path="res://.godot/imported/barrel_009.png-e043f7c4f9963d112dec1b21b2a559c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_009.png" +dest_files=["res://.godot/imported/barrel_009.png-e043f7c4f9963d112dec1b21b2a559c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_010.png b/thumbs/barrel_010.png new file mode 100644 index 0000000..cb94d3e Binary files /dev/null and b/thumbs/barrel_010.png differ diff --git a/thumbs/barrel_010.png.import b/thumbs/barrel_010.png.import new file mode 100644 index 0000000..21bdd19 --- /dev/null +++ b/thumbs/barrel_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://nwboqq7bq5cc" +path="res://.godot/imported/barrel_010.png-ed0ca45716a8fbc4149d53c7fbdd2318.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_010.png" +dest_files=["res://.godot/imported/barrel_010.png-ed0ca45716a8fbc4149d53c7fbdd2318.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_011.png b/thumbs/barrel_011.png new file mode 100644 index 0000000..1f70721 Binary files /dev/null and b/thumbs/barrel_011.png differ diff --git a/thumbs/barrel_011.png.import b/thumbs/barrel_011.png.import new file mode 100644 index 0000000..6b30511 --- /dev/null +++ b/thumbs/barrel_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf2orfs8g27pk" +path="res://.godot/imported/barrel_011.png-957b808a7a32d2d91071d777b4ec67af.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_011.png" +dest_files=["res://.godot/imported/barrel_011.png-957b808a7a32d2d91071d777b4ec67af.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_012.png b/thumbs/barrel_012.png new file mode 100644 index 0000000..b538937 Binary files /dev/null and b/thumbs/barrel_012.png differ diff --git a/thumbs/barrel_012.png.import b/thumbs/barrel_012.png.import new file mode 100644 index 0000000..4b16cba --- /dev/null +++ b/thumbs/barrel_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dddfloskgqjui" +path="res://.godot/imported/barrel_012.png-38f5a2063860cb1d2faca6ddc1c98da0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_012.png" +dest_files=["res://.godot/imported/barrel_012.png-38f5a2063860cb1d2faca6ddc1c98da0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrel_013.png b/thumbs/barrel_013.png new file mode 100644 index 0000000..a85c05d Binary files /dev/null and b/thumbs/barrel_013.png differ diff --git a/thumbs/barrel_013.png.import b/thumbs/barrel_013.png.import new file mode 100644 index 0000000..a0b6c9b --- /dev/null +++ b/thumbs/barrel_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8wcuxv0v56js" +path="res://.godot/imported/barrel_013.png-fbb3bded70190f8bf9c6b2f0790f15f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrel_013.png" +dest_files=["res://.godot/imported/barrel_013.png-fbb3bded70190f8bf9c6b2f0790f15f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_001.png b/thumbs/barrier_001.png new file mode 100644 index 0000000..f061b98 Binary files /dev/null and b/thumbs/barrier_001.png differ diff --git a/thumbs/barrier_001.png.import b/thumbs/barrier_001.png.import new file mode 100644 index 0000000..dd3cbb3 --- /dev/null +++ b/thumbs/barrier_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drn6mij5p6hv5" +path="res://.godot/imported/barrier_001.png-03e35352f861807cedd8f27bcb81da2e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_001.png" +dest_files=["res://.godot/imported/barrier_001.png-03e35352f861807cedd8f27bcb81da2e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_002.png b/thumbs/barrier_002.png new file mode 100644 index 0000000..5251114 Binary files /dev/null and b/thumbs/barrier_002.png differ diff --git a/thumbs/barrier_002.png.import b/thumbs/barrier_002.png.import new file mode 100644 index 0000000..7bfd6ed --- /dev/null +++ b/thumbs/barrier_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbhbgk7y8j54p" +path="res://.godot/imported/barrier_002.png-70a9b4c4a6cd81f603b1ab063fe157d2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_002.png" +dest_files=["res://.godot/imported/barrier_002.png-70a9b4c4a6cd81f603b1ab063fe157d2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_003.png b/thumbs/barrier_003.png new file mode 100644 index 0000000..22d927a Binary files /dev/null and b/thumbs/barrier_003.png differ diff --git a/thumbs/barrier_003.png.import b/thumbs/barrier_003.png.import new file mode 100644 index 0000000..062c734 --- /dev/null +++ b/thumbs/barrier_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccvfh73fkfikq" +path="res://.godot/imported/barrier_003.png-616cb65cc5947621f3ffa5b1eb50ee51.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_003.png" +dest_files=["res://.godot/imported/barrier_003.png-616cb65cc5947621f3ffa5b1eb50ee51.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_004.png b/thumbs/barrier_004.png new file mode 100644 index 0000000..7109612 Binary files /dev/null and b/thumbs/barrier_004.png differ diff --git a/thumbs/barrier_004.png.import b/thumbs/barrier_004.png.import new file mode 100644 index 0000000..6e3baf5 --- /dev/null +++ b/thumbs/barrier_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgc3wwy6b41b0" +path="res://.godot/imported/barrier_004.png-956d7320130d7ba7733eeaea925df58e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_004.png" +dest_files=["res://.godot/imported/barrier_004.png-956d7320130d7ba7733eeaea925df58e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_005.png b/thumbs/barrier_005.png new file mode 100644 index 0000000..88e1dda Binary files /dev/null and b/thumbs/barrier_005.png differ diff --git a/thumbs/barrier_005.png.import b/thumbs/barrier_005.png.import new file mode 100644 index 0000000..fb1ef8f --- /dev/null +++ b/thumbs/barrier_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmkxussbxmumo" +path="res://.godot/imported/barrier_005.png-270d3266428b02a1bd0bb2fb38555a3f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_005.png" +dest_files=["res://.godot/imported/barrier_005.png-270d3266428b02a1bd0bb2fb38555a3f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_006.png b/thumbs/barrier_006.png new file mode 100644 index 0000000..bea628a Binary files /dev/null and b/thumbs/barrier_006.png differ diff --git a/thumbs/barrier_006.png.import b/thumbs/barrier_006.png.import new file mode 100644 index 0000000..9f95373 --- /dev/null +++ b/thumbs/barrier_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjhuexnsl151p" +path="res://.godot/imported/barrier_006.png-47b429dd4fc10cb121ea4cf49550c28a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_006.png" +dest_files=["res://.godot/imported/barrier_006.png-47b429dd4fc10cb121ea4cf49550c28a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_007.png b/thumbs/barrier_007.png new file mode 100644 index 0000000..e719e05 Binary files /dev/null and b/thumbs/barrier_007.png differ diff --git a/thumbs/barrier_007.png.import b/thumbs/barrier_007.png.import new file mode 100644 index 0000000..67f5c23 --- /dev/null +++ b/thumbs/barrier_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://shriffj4a6cq" +path="res://.godot/imported/barrier_007.png-68d1b037d2d03e758c74ec70ccc61e91.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_007.png" +dest_files=["res://.godot/imported/barrier_007.png-68d1b037d2d03e758c74ec70ccc61e91.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_008.png b/thumbs/barrier_008.png new file mode 100644 index 0000000..b18ee67 Binary files /dev/null and b/thumbs/barrier_008.png differ diff --git a/thumbs/barrier_008.png.import b/thumbs/barrier_008.png.import new file mode 100644 index 0000000..146ff73 --- /dev/null +++ b/thumbs/barrier_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgio2t6y3iuik" +path="res://.godot/imported/barrier_008.png-bdf1979ff38855e76f8c7a5e87b6501c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_008.png" +dest_files=["res://.godot/imported/barrier_008.png-bdf1979ff38855e76f8c7a5e87b6501c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_009.png b/thumbs/barrier_009.png new file mode 100644 index 0000000..805fa6f Binary files /dev/null and b/thumbs/barrier_009.png differ diff --git a/thumbs/barrier_009.png.import b/thumbs/barrier_009.png.import new file mode 100644 index 0000000..a965782 --- /dev/null +++ b/thumbs/barrier_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btki2yug25x23" +path="res://.godot/imported/barrier_009.png-3c16dc1b0959a72b0bf96d780288c218.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_009.png" +dest_files=["res://.godot/imported/barrier_009.png-3c16dc1b0959a72b0bf96d780288c218.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_010.png b/thumbs/barrier_010.png new file mode 100644 index 0000000..73eabdb Binary files /dev/null and b/thumbs/barrier_010.png differ diff --git a/thumbs/barrier_010.png.import b/thumbs/barrier_010.png.import new file mode 100644 index 0000000..26743ea --- /dev/null +++ b/thumbs/barrier_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjs7nqt4rl4qr" +path="res://.godot/imported/barrier_010.png-f6f1216f1256d287e14df4fbbd041a2f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_010.png" +dest_files=["res://.godot/imported/barrier_010.png-f6f1216f1256d287e14df4fbbd041a2f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_011.png b/thumbs/barrier_011.png new file mode 100644 index 0000000..506a77a Binary files /dev/null and b/thumbs/barrier_011.png differ diff --git a/thumbs/barrier_011.png.import b/thumbs/barrier_011.png.import new file mode 100644 index 0000000..1d87fd4 --- /dev/null +++ b/thumbs/barrier_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j0rac170fjwn" +path="res://.godot/imported/barrier_011.png-787b75d54921f93ff80ac93815d9255f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_011.png" +dest_files=["res://.godot/imported/barrier_011.png-787b75d54921f93ff80ac93815d9255f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_012.png b/thumbs/barrier_012.png new file mode 100644 index 0000000..19e3578 Binary files /dev/null and b/thumbs/barrier_012.png differ diff --git a/thumbs/barrier_012.png.import b/thumbs/barrier_012.png.import new file mode 100644 index 0000000..a0ccdc4 --- /dev/null +++ b/thumbs/barrier_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://e6r8uhiswdsx" +path="res://.godot/imported/barrier_012.png-b678eb3183ae7deda1606fedf775bb0a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_012.png" +dest_files=["res://.godot/imported/barrier_012.png-b678eb3183ae7deda1606fedf775bb0a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_013.png b/thumbs/barrier_013.png new file mode 100644 index 0000000..22de458 Binary files /dev/null and b/thumbs/barrier_013.png differ diff --git a/thumbs/barrier_013.png.import b/thumbs/barrier_013.png.import new file mode 100644 index 0000000..f929383 --- /dev/null +++ b/thumbs/barrier_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6bx8ua6gpxf2" +path="res://.godot/imported/barrier_013.png-8c2d7408af6bc2f36f75eb2f036dcebb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_013.png" +dest_files=["res://.godot/imported/barrier_013.png-8c2d7408af6bc2f36f75eb2f036dcebb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_014.png b/thumbs/barrier_014.png new file mode 100644 index 0000000..653844f Binary files /dev/null and b/thumbs/barrier_014.png differ diff --git a/thumbs/barrier_014.png.import b/thumbs/barrier_014.png.import new file mode 100644 index 0000000..074771b --- /dev/null +++ b/thumbs/barrier_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kbu8pt3aad80" +path="res://.godot/imported/barrier_014.png-1a8cda90769d4eb687690001194ef470.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_014.png" +dest_files=["res://.godot/imported/barrier_014.png-1a8cda90769d4eb687690001194ef470.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_015.png b/thumbs/barrier_015.png new file mode 100644 index 0000000..f53e531 Binary files /dev/null and b/thumbs/barrier_015.png differ diff --git a/thumbs/barrier_015.png.import b/thumbs/barrier_015.png.import new file mode 100644 index 0000000..74ab93b --- /dev/null +++ b/thumbs/barrier_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ss1sahmrwvw5" +path="res://.godot/imported/barrier_015.png-2182ad990fcd3150c85f3e8938d6e0ab.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_015.png" +dest_files=["res://.godot/imported/barrier_015.png-2182ad990fcd3150c85f3e8938d6e0ab.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_016.png b/thumbs/barrier_016.png new file mode 100644 index 0000000..1affe04 Binary files /dev/null and b/thumbs/barrier_016.png differ diff --git a/thumbs/barrier_016.png.import b/thumbs/barrier_016.png.import new file mode 100644 index 0000000..d83fe73 --- /dev/null +++ b/thumbs/barrier_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtqjw5f0m65x7" +path="res://.godot/imported/barrier_016.png-6f97a1ba7f0cb4986885e33be7473746.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_016.png" +dest_files=["res://.godot/imported/barrier_016.png-6f97a1ba7f0cb4986885e33be7473746.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/barrier_017.png b/thumbs/barrier_017.png new file mode 100644 index 0000000..54d1840 Binary files /dev/null and b/thumbs/barrier_017.png differ diff --git a/thumbs/barrier_017.png.import b/thumbs/barrier_017.png.import new file mode 100644 index 0000000..06eb2c2 --- /dev/null +++ b/thumbs/barrier_017.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4s5d1sbxon0h" +path="res://.godot/imported/barrier_017.png-67e57f6067a5efc0f4371a84ecb2363f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/barrier_017.png" +dest_files=["res://.godot/imported/barrier_017.png-67e57f6067a5efc0f4371a84ecb2363f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_boden.png b/thumbs/bau_boden.png new file mode 100644 index 0000000..553f004 Binary files /dev/null and b/thumbs/bau_boden.png differ diff --git a/thumbs/bau_boden.png.import b/thumbs/bau_boden.png.import new file mode 100644 index 0000000..5508187 --- /dev/null +++ b/thumbs/bau_boden.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://df5wy3ry0lc4m" +path="res://.godot/imported/bau_boden.png-16f161cbea0d6be194e9e46a2048ae39.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_boden.png" +dest_files=["res://.godot/imported/bau_boden.png-16f161cbea0d6be194e9e46a2048ae39.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_bruestung.png b/thumbs/bau_bruestung.png new file mode 100644 index 0000000..ef51bc7 Binary files /dev/null and b/thumbs/bau_bruestung.png differ diff --git a/thumbs/bau_bruestung.png.import b/thumbs/bau_bruestung.png.import new file mode 100644 index 0000000..a4d4526 --- /dev/null +++ b/thumbs/bau_bruestung.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8vfg7jkteqqi" +path="res://.godot/imported/bau_bruestung.png-3d23f421ebb8de803823c2627b133f08.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_bruestung.png" +dest_files=["res://.godot/imported/bau_bruestung.png-3d23f421ebb8de803823c2627b133f08.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_ecke_aussen.png b/thumbs/bau_dach_ecke_aussen.png new file mode 100644 index 0000000..b9d320b Binary files /dev/null and b/thumbs/bau_dach_ecke_aussen.png differ diff --git a/thumbs/bau_dach_ecke_aussen.png.import b/thumbs/bau_dach_ecke_aussen.png.import new file mode 100644 index 0000000..4d1e3b3 --- /dev/null +++ b/thumbs/bau_dach_ecke_aussen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djm3tfu1hf0wu" +path="res://.godot/imported/bau_dach_ecke_aussen.png-8540e959d5c1b3c2880a370a7918ed4c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_ecke_aussen.png" +dest_files=["res://.godot/imported/bau_dach_ecke_aussen.png-8540e959d5c1b3c2880a370a7918ed4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_ecke_innen.png b/thumbs/bau_dach_ecke_innen.png new file mode 100644 index 0000000..cd24ae4 Binary files /dev/null and b/thumbs/bau_dach_ecke_innen.png differ diff --git a/thumbs/bau_dach_ecke_innen.png.import b/thumbs/bau_dach_ecke_innen.png.import new file mode 100644 index 0000000..e8ce937 --- /dev/null +++ b/thumbs/bau_dach_ecke_innen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drk0drrumym47" +path="res://.godot/imported/bau_dach_ecke_innen.png-1756bf2940942fa841e1fa279989f121.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_ecke_innen.png" +dest_files=["res://.godot/imported/bau_dach_ecke_innen.png-1756bf2940942fa841e1fa279989f121.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_giebel.png b/thumbs/bau_dach_giebel.png new file mode 100644 index 0000000..385910f Binary files /dev/null and b/thumbs/bau_dach_giebel.png differ diff --git a/thumbs/bau_dach_giebel.png.import b/thumbs/bau_dach_giebel.png.import new file mode 100644 index 0000000..7126d96 --- /dev/null +++ b/thumbs/bau_dach_giebel.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcja1snb22xuj" +path="res://.godot/imported/bau_dach_giebel.png-2fa317ef54f706102717076165b19ca6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_giebel.png" +dest_files=["res://.godot/imported/bau_dach_giebel.png-2fa317ef54f706102717076165b19ca6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_giebel_4.png b/thumbs/bau_dach_giebel_4.png new file mode 100644 index 0000000..385910f Binary files /dev/null and b/thumbs/bau_dach_giebel_4.png differ diff --git a/thumbs/bau_dach_giebel_4.png.import b/thumbs/bau_dach_giebel_4.png.import new file mode 100644 index 0000000..19913fd --- /dev/null +++ b/thumbs/bau_dach_giebel_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bt6lbs34ymm1a" +path="res://.godot/imported/bau_dach_giebel_4.png-f5b5c10da45fdf3193cbf9e3d48df649.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_giebel_4.png" +dest_files=["res://.godot/imported/bau_dach_giebel_4.png-f5b5c10da45fdf3193cbf9e3d48df649.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_giebel_6.png b/thumbs/bau_dach_giebel_6.png new file mode 100644 index 0000000..385910f Binary files /dev/null and b/thumbs/bau_dach_giebel_6.png differ diff --git a/thumbs/bau_dach_giebel_6.png.import b/thumbs/bau_dach_giebel_6.png.import new file mode 100644 index 0000000..ad9ebef --- /dev/null +++ b/thumbs/bau_dach_giebel_6.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbvqftv8ptl76" +path="res://.godot/imported/bau_dach_giebel_6.png-3e64c0259033a9acd1db41d449df7982.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_giebel_6.png" +dest_files=["res://.godot/imported/bau_dach_giebel_6.png-3e64c0259033a9acd1db41d449df7982.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_giebel_ende.png b/thumbs/bau_dach_giebel_ende.png new file mode 100644 index 0000000..43f9adf Binary files /dev/null and b/thumbs/bau_dach_giebel_ende.png differ diff --git a/thumbs/bau_dach_giebel_ende.png.import b/thumbs/bau_dach_giebel_ende.png.import new file mode 100644 index 0000000..c6a9d54 --- /dev/null +++ b/thumbs/bau_dach_giebel_ende.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4mwoant504rw" +path="res://.godot/imported/bau_dach_giebel_ende.png-c95a748350e9d9a3ab4f408ee390f044.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_giebel_ende.png" +dest_files=["res://.godot/imported/bau_dach_giebel_ende.png-c95a748350e9d9a3ab4f408ee390f044.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_plateau.png b/thumbs/bau_dach_plateau.png new file mode 100644 index 0000000..997db21 Binary files /dev/null and b/thumbs/bau_dach_plateau.png differ diff --git a/thumbs/bau_dach_plateau.png.import b/thumbs/bau_dach_plateau.png.import new file mode 100644 index 0000000..b9bb981 --- /dev/null +++ b/thumbs/bau_dach_plateau.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd36il31xybcr" +path="res://.godot/imported/bau_dach_plateau.png-6e69eb7af00bc7c0bf5ef85f249900a2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_plateau.png" +dest_files=["res://.godot/imported/bau_dach_plateau.png-6e69eb7af00bc7c0bf5ef85f249900a2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_pyramide.png b/thumbs/bau_dach_pyramide.png new file mode 100644 index 0000000..548e550 Binary files /dev/null and b/thumbs/bau_dach_pyramide.png differ diff --git a/thumbs/bau_dach_pyramide.png.import b/thumbs/bau_dach_pyramide.png.import new file mode 100644 index 0000000..adce5f5 --- /dev/null +++ b/thumbs/bau_dach_pyramide.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc3wyq1p7b1nm" +path="res://.godot/imported/bau_dach_pyramide.png-01db43c636390d1a8148124f6269839d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_pyramide.png" +dest_files=["res://.godot/imported/bau_dach_pyramide.png-01db43c636390d1a8148124f6269839d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_pyramide_4.png b/thumbs/bau_dach_pyramide_4.png new file mode 100644 index 0000000..548e550 Binary files /dev/null and b/thumbs/bau_dach_pyramide_4.png differ diff --git a/thumbs/bau_dach_pyramide_4.png.import b/thumbs/bau_dach_pyramide_4.png.import new file mode 100644 index 0000000..9256b6a --- /dev/null +++ b/thumbs/bau_dach_pyramide_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y5g22t4cndf0" +path="res://.godot/imported/bau_dach_pyramide_4.png-63d9288171351a9022d670cdc8d3b721.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_pyramide_4.png" +dest_files=["res://.godot/imported/bau_dach_pyramide_4.png-63d9288171351a9022d670cdc8d3b721.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_pyramide_6.png b/thumbs/bau_dach_pyramide_6.png new file mode 100644 index 0000000..548e550 Binary files /dev/null and b/thumbs/bau_dach_pyramide_6.png differ diff --git a/thumbs/bau_dach_pyramide_6.png.import b/thumbs/bau_dach_pyramide_6.png.import new file mode 100644 index 0000000..aadd2e0 --- /dev/null +++ b/thumbs/bau_dach_pyramide_6.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cao1art7cdwnm" +path="res://.godot/imported/bau_dach_pyramide_6.png-be41aa37fd0a6516700239e68f4ebfef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_pyramide_6.png" +dest_files=["res://.godot/imported/bau_dach_pyramide_6.png-be41aa37fd0a6516700239e68f4ebfef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_dach_schraege.png b/thumbs/bau_dach_schraege.png new file mode 100644 index 0000000..8475dd5 Binary files /dev/null and b/thumbs/bau_dach_schraege.png differ diff --git a/thumbs/bau_dach_schraege.png.import b/thumbs/bau_dach_schraege.png.import new file mode 100644 index 0000000..93e122d --- /dev/null +++ b/thumbs/bau_dach_schraege.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chval4fuxxbsh" +path="res://.godot/imported/bau_dach_schraege.png-f2c5e27e91d7dd98421746f2b6a6528e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_dach_schraege.png" +dest_files=["res://.godot/imported/bau_dach_schraege.png-f2c5e27e91d7dd98421746f2b6a6528e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_fundament.png b/thumbs/bau_fundament.png new file mode 100644 index 0000000..6a80337 Binary files /dev/null and b/thumbs/bau_fundament.png differ diff --git a/thumbs/bau_fundament.png.import b/thumbs/bau_fundament.png.import new file mode 100644 index 0000000..bdae5a7 --- /dev/null +++ b/thumbs/bau_fundament.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bduavlchxv6xj" +path="res://.godot/imported/bau_fundament.png-0dea38d83ccd1ba7e0fcae47946bc4fc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_fundament.png" +dest_files=["res://.godot/imported/bau_fundament.png-0dea38d83ccd1ba7e0fcae47946bc4fc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_giebelwand.png b/thumbs/bau_giebelwand.png new file mode 100644 index 0000000..c85449d Binary files /dev/null and b/thumbs/bau_giebelwand.png differ diff --git a/thumbs/bau_giebelwand.png.import b/thumbs/bau_giebelwand.png.import new file mode 100644 index 0000000..bc575ec --- /dev/null +++ b/thumbs/bau_giebelwand.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpm4yqyeuqgip" +path="res://.godot/imported/bau_giebelwand.png-6f47eead8ef4e4f2dc7761e51dab1f95.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_giebelwand.png" +dest_files=["res://.godot/imported/bau_giebelwand.png-6f47eead8ef4e4f2dc7761e51dab1f95.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_giebelwand_4.png b/thumbs/bau_giebelwand_4.png new file mode 100644 index 0000000..c85449d Binary files /dev/null and b/thumbs/bau_giebelwand_4.png differ diff --git a/thumbs/bau_giebelwand_4.png.import b/thumbs/bau_giebelwand_4.png.import new file mode 100644 index 0000000..72324a2 --- /dev/null +++ b/thumbs/bau_giebelwand_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvf3l17nuahbv" +path="res://.godot/imported/bau_giebelwand_4.png-989acd1e4ae544ba449fb56df08bb768.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_giebelwand_4.png" +dest_files=["res://.godot/imported/bau_giebelwand_4.png-989acd1e4ae544ba449fb56df08bb768.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_giebelwand_6.png b/thumbs/bau_giebelwand_6.png new file mode 100644 index 0000000..c85449d Binary files /dev/null and b/thumbs/bau_giebelwand_6.png differ diff --git a/thumbs/bau_giebelwand_6.png.import b/thumbs/bau_giebelwand_6.png.import new file mode 100644 index 0000000..0bb97a2 --- /dev/null +++ b/thumbs/bau_giebelwand_6.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crpyxcrmio21s" +path="res://.godot/imported/bau_giebelwand_6.png-7acc00e2ab09315326295b282c28f162.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_giebelwand_6.png" +dest_files=["res://.godot/imported/bau_giebelwand_6.png-7acc00e2ab09315326295b282c28f162.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_pfeiler.png b/thumbs/bau_pfeiler.png new file mode 100644 index 0000000..4f8b898 Binary files /dev/null and b/thumbs/bau_pfeiler.png differ diff --git a/thumbs/bau_pfeiler.png.import b/thumbs/bau_pfeiler.png.import new file mode 100644 index 0000000..de67f74 --- /dev/null +++ b/thumbs/bau_pfeiler.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4elt0wtyqfc3" +path="res://.godot/imported/bau_pfeiler.png-78000420ee08732111d3d7771d689d09.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_pfeiler.png" +dest_files=["res://.godot/imported/bau_pfeiler.png-78000420ee08732111d3d7771d689d09.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_treppe.png b/thumbs/bau_treppe.png new file mode 100644 index 0000000..e20cf3e Binary files /dev/null and b/thumbs/bau_treppe.png differ diff --git a/thumbs/bau_treppe.png.import b/thumbs/bau_treppe.png.import new file mode 100644 index 0000000..b1f7520 --- /dev/null +++ b/thumbs/bau_treppe.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drcqrvbsee3tf" +path="res://.godot/imported/bau_treppe.png-7f9b2e6783e1060189b445b811aa2cb7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_treppe.png" +dest_files=["res://.godot/imported/bau_treppe.png-7f9b2e6783e1060189b445b811aa2cb7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_wand.png b/thumbs/bau_wand.png new file mode 100644 index 0000000..ed18606 Binary files /dev/null and b/thumbs/bau_wand.png differ diff --git a/thumbs/bau_wand.png.import b/thumbs/bau_wand.png.import new file mode 100644 index 0000000..55ccebb --- /dev/null +++ b/thumbs/bau_wand.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cot5xrbwq6bvs" +path="res://.godot/imported/bau_wand.png-d50b92c7e96fb6280b87f1258860da3d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_wand.png" +dest_files=["res://.godot/imported/bau_wand.png-d50b92c7e96fb6280b87f1258860da3d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_wand_fenster.png b/thumbs/bau_wand_fenster.png new file mode 100644 index 0000000..d154316 Binary files /dev/null and b/thumbs/bau_wand_fenster.png differ diff --git a/thumbs/bau_wand_fenster.png.import b/thumbs/bau_wand_fenster.png.import new file mode 100644 index 0000000..9d6357e --- /dev/null +++ b/thumbs/bau_wand_fenster.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://br3bdm1noh1po" +path="res://.godot/imported/bau_wand_fenster.png-8f8bb67e677ac7226b79fd546a17fc66.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_wand_fenster.png" +dest_files=["res://.godot/imported/bau_wand_fenster.png-8f8bb67e677ac7226b79fd546a17fc66.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_wand_scharte.png b/thumbs/bau_wand_scharte.png new file mode 100644 index 0000000..9f08d68 Binary files /dev/null and b/thumbs/bau_wand_scharte.png differ diff --git a/thumbs/bau_wand_scharte.png.import b/thumbs/bau_wand_scharte.png.import new file mode 100644 index 0000000..32cfabc --- /dev/null +++ b/thumbs/bau_wand_scharte.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5stjyq6slxl4" +path="res://.godot/imported/bau_wand_scharte.png-187acd33d82b876cb3b6e952247d1b1e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_wand_scharte.png" +dest_files=["res://.godot/imported/bau_wand_scharte.png-187acd33d82b876cb3b6e952247d1b1e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bau_wand_tuer.png b/thumbs/bau_wand_tuer.png new file mode 100644 index 0000000..39cc930 Binary files /dev/null and b/thumbs/bau_wand_tuer.png differ diff --git a/thumbs/bau_wand_tuer.png.import b/thumbs/bau_wand_tuer.png.import new file mode 100644 index 0000000..aa20ee3 --- /dev/null +++ b/thumbs/bau_wand_tuer.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0dbwpq57hlgy" +path="res://.godot/imported/bau_wand_tuer.png-e0bc303944974e8e43d90a6dd3edda23.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bau_wand_tuer.png" +dest_files=["res://.godot/imported/bau_wand_tuer.png-e0bc303944974e8e43d90a6dd3edda23.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/blade_001.png b/thumbs/blade_001.png new file mode 100644 index 0000000..0c6982a Binary files /dev/null and b/thumbs/blade_001.png differ diff --git a/thumbs/blade_001.png.import b/thumbs/blade_001.png.import new file mode 100644 index 0000000..bf516c4 --- /dev/null +++ b/thumbs/blade_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2xp4qw84nfm6" +path="res://.godot/imported/blade_001.png-cbdaed38d8adfd2461ab8cee3d131574.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/blade_001.png" +dest_files=["res://.godot/imported/blade_001.png-cbdaed38d8adfd2461ab8cee3d131574.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/blade_002.png b/thumbs/blade_002.png new file mode 100644 index 0000000..9cc8d8f Binary files /dev/null and b/thumbs/blade_002.png differ diff --git a/thumbs/blade_002.png.import b/thumbs/blade_002.png.import new file mode 100644 index 0000000..7de0373 --- /dev/null +++ b/thumbs/blade_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8hxsbrheb3w0" +path="res://.godot/imported/blade_002.png-7d1f50fa246404478314c6a3e39bdc87.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/blade_002.png" +dest_files=["res://.godot/imported/blade_002.png-7d1f50fa246404478314c6a3e39bdc87.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_001.png b/thumbs/box_001.png new file mode 100644 index 0000000..1a59233 Binary files /dev/null and b/thumbs/box_001.png differ diff --git a/thumbs/box_001.png.import b/thumbs/box_001.png.import new file mode 100644 index 0000000..ed4d21e --- /dev/null +++ b/thumbs/box_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4ax75gpoe6am" +path="res://.godot/imported/box_001.png-80d654883d154afec915da6f3e7b9e34.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_001.png" +dest_files=["res://.godot/imported/box_001.png-80d654883d154afec915da6f3e7b9e34.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_002.png b/thumbs/box_002.png new file mode 100644 index 0000000..f4e1347 Binary files /dev/null and b/thumbs/box_002.png differ diff --git a/thumbs/box_002.png.import b/thumbs/box_002.png.import new file mode 100644 index 0000000..d16b00a --- /dev/null +++ b/thumbs/box_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlcrn3bwh1cbf" +path="res://.godot/imported/box_002.png-ecd6dbcfe2551793255ec183f817b34b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_002.png" +dest_files=["res://.godot/imported/box_002.png-ecd6dbcfe2551793255ec183f817b34b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_003.png b/thumbs/box_003.png new file mode 100644 index 0000000..d6e68f7 Binary files /dev/null and b/thumbs/box_003.png differ diff --git a/thumbs/box_003.png.import b/thumbs/box_003.png.import new file mode 100644 index 0000000..de39e46 --- /dev/null +++ b/thumbs/box_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcsubl8p7c8p5" +path="res://.godot/imported/box_003.png-6bc8a4ffcbd97d4e9f23ae1331c26107.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_003.png" +dest_files=["res://.godot/imported/box_003.png-6bc8a4ffcbd97d4e9f23ae1331c26107.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_004.png b/thumbs/box_004.png new file mode 100644 index 0000000..7db894c Binary files /dev/null and b/thumbs/box_004.png differ diff --git a/thumbs/box_004.png.import b/thumbs/box_004.png.import new file mode 100644 index 0000000..e3b6cff --- /dev/null +++ b/thumbs/box_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7infi8h1h28x" +path="res://.godot/imported/box_004.png-681cf2a7b653e95bfcf7efada4c26bb6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_004.png" +dest_files=["res://.godot/imported/box_004.png-681cf2a7b653e95bfcf7efada4c26bb6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_005.png b/thumbs/box_005.png new file mode 100644 index 0000000..8acc2b9 Binary files /dev/null and b/thumbs/box_005.png differ diff --git a/thumbs/box_005.png.import b/thumbs/box_005.png.import new file mode 100644 index 0000000..277301f --- /dev/null +++ b/thumbs/box_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfe5j170txkck" +path="res://.godot/imported/box_005.png-06f91eff984c454019cbf8211521e664.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_005.png" +dest_files=["res://.godot/imported/box_005.png-06f91eff984c454019cbf8211521e664.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_006.png b/thumbs/box_006.png new file mode 100644 index 0000000..ff094d0 Binary files /dev/null and b/thumbs/box_006.png differ diff --git a/thumbs/box_006.png.import b/thumbs/box_006.png.import new file mode 100644 index 0000000..04ec5fd --- /dev/null +++ b/thumbs/box_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcykj0beb31kr" +path="res://.godot/imported/box_006.png-5ba357d8c76465ff6e00378ddb8b4e3f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_006.png" +dest_files=["res://.godot/imported/box_006.png-5ba357d8c76465ff6e00378ddb8b4e3f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_007.png b/thumbs/box_007.png new file mode 100644 index 0000000..dd70ef2 Binary files /dev/null and b/thumbs/box_007.png differ diff --git a/thumbs/box_007.png.import b/thumbs/box_007.png.import new file mode 100644 index 0000000..10b3ae7 --- /dev/null +++ b/thumbs/box_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0sk3hgbb6daa" +path="res://.godot/imported/box_007.png-46862eb0de1e4e97c4d9df57a884fc7a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_007.png" +dest_files=["res://.godot/imported/box_007.png-46862eb0de1e4e97c4d9df57a884fc7a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_008.png b/thumbs/box_008.png new file mode 100644 index 0000000..4b7af5e Binary files /dev/null and b/thumbs/box_008.png differ diff --git a/thumbs/box_008.png.import b/thumbs/box_008.png.import new file mode 100644 index 0000000..2bbd9fc --- /dev/null +++ b/thumbs/box_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2ynvj116h1ra" +path="res://.godot/imported/box_008.png-7bf97d200ebd04f21c42353d62fe9f65.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_008.png" +dest_files=["res://.godot/imported/box_008.png-7bf97d200ebd04f21c42353d62fe9f65.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_009.png b/thumbs/box_009.png new file mode 100644 index 0000000..478d3d7 Binary files /dev/null and b/thumbs/box_009.png differ diff --git a/thumbs/box_009.png.import b/thumbs/box_009.png.import new file mode 100644 index 0000000..6ea33b1 --- /dev/null +++ b/thumbs/box_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://e1pnnjhgpfjr" +path="res://.godot/imported/box_009.png-68efd81731b5630f01a92276f8213060.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_009.png" +dest_files=["res://.godot/imported/box_009.png-68efd81731b5630f01a92276f8213060.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_010.png b/thumbs/box_010.png new file mode 100644 index 0000000..53f9470 Binary files /dev/null and b/thumbs/box_010.png differ diff --git a/thumbs/box_010.png.import b/thumbs/box_010.png.import new file mode 100644 index 0000000..a227f03 --- /dev/null +++ b/thumbs/box_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpw1i2soeg0oo" +path="res://.godot/imported/box_010.png-80d0a7d6d1be33149f6e5747e3e0297c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_010.png" +dest_files=["res://.godot/imported/box_010.png-80d0a7d6d1be33149f6e5747e3e0297c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_011.png b/thumbs/box_011.png new file mode 100644 index 0000000..7478731 Binary files /dev/null and b/thumbs/box_011.png differ diff --git a/thumbs/box_011.png.import b/thumbs/box_011.png.import new file mode 100644 index 0000000..a81fddf --- /dev/null +++ b/thumbs/box_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0y6x2hlgl1f1" +path="res://.godot/imported/box_011.png-0b4dd9817af85a16eeb47d3714b0a53e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_011.png" +dest_files=["res://.godot/imported/box_011.png-0b4dd9817af85a16eeb47d3714b0a53e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_012.png b/thumbs/box_012.png new file mode 100644 index 0000000..999ab7a Binary files /dev/null and b/thumbs/box_012.png differ diff --git a/thumbs/box_012.png.import b/thumbs/box_012.png.import new file mode 100644 index 0000000..aa8bfd6 --- /dev/null +++ b/thumbs/box_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwvro43pb88ye" +path="res://.godot/imported/box_012.png-7bbc57f804e492415843e69b221cd02b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_012.png" +dest_files=["res://.godot/imported/box_012.png-7bbc57f804e492415843e69b221cd02b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_013.png b/thumbs/box_013.png new file mode 100644 index 0000000..662f207 Binary files /dev/null and b/thumbs/box_013.png differ diff --git a/thumbs/box_013.png.import b/thumbs/box_013.png.import new file mode 100644 index 0000000..41fe49a --- /dev/null +++ b/thumbs/box_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4wos88frshbo" +path="res://.godot/imported/box_013.png-8fb350f7b7ca866e686c1633a186457f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_013.png" +dest_files=["res://.godot/imported/box_013.png-8fb350f7b7ca866e686c1633a186457f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_014.png b/thumbs/box_014.png new file mode 100644 index 0000000..b4bdaa7 Binary files /dev/null and b/thumbs/box_014.png differ diff --git a/thumbs/box_014.png.import b/thumbs/box_014.png.import new file mode 100644 index 0000000..f03bf5f --- /dev/null +++ b/thumbs/box_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clqa7fvr1ijvo" +path="res://.godot/imported/box_014.png-ee7ed02ace3c25e82c08415d6baaafb5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_014.png" +dest_files=["res://.godot/imported/box_014.png-ee7ed02ace3c25e82c08415d6baaafb5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_015.png b/thumbs/box_015.png new file mode 100644 index 0000000..e71162c Binary files /dev/null and b/thumbs/box_015.png differ diff --git a/thumbs/box_015.png.import b/thumbs/box_015.png.import new file mode 100644 index 0000000..e82d4b0 --- /dev/null +++ b/thumbs/box_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8ni3pgocqepn" +path="res://.godot/imported/box_015.png-86ea2accb1bfd48d8e5318f9eb8c0400.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_015.png" +dest_files=["res://.godot/imported/box_015.png-86ea2accb1bfd48d8e5318f9eb8c0400.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_016.png b/thumbs/box_016.png new file mode 100644 index 0000000..1f5eeea Binary files /dev/null and b/thumbs/box_016.png differ diff --git a/thumbs/box_016.png.import b/thumbs/box_016.png.import new file mode 100644 index 0000000..9a9759d --- /dev/null +++ b/thumbs/box_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uoyv3t570mmc" +path="res://.godot/imported/box_016.png-df24f45c3486224667e7ec5060ba241a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_016.png" +dest_files=["res://.godot/imported/box_016.png-df24f45c3486224667e7ec5060ba241a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_017.png b/thumbs/box_017.png new file mode 100644 index 0000000..d034d28 Binary files /dev/null and b/thumbs/box_017.png differ diff --git a/thumbs/box_017.png.import b/thumbs/box_017.png.import new file mode 100644 index 0000000..76a7142 --- /dev/null +++ b/thumbs/box_017.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://exse4rpkggsj" +path="res://.godot/imported/box_017.png-8cb0240c67fbcd8d9ad515cb1bf9502c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_017.png" +dest_files=["res://.godot/imported/box_017.png-8cb0240c67fbcd8d9ad515cb1bf9502c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_018.png b/thumbs/box_018.png new file mode 100644 index 0000000..da779e1 Binary files /dev/null and b/thumbs/box_018.png differ diff --git a/thumbs/box_018.png.import b/thumbs/box_018.png.import new file mode 100644 index 0000000..0281e02 --- /dev/null +++ b/thumbs/box_018.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5jfr0tnkerkx" +path="res://.godot/imported/box_018.png-6f5beb2788189a96a38142383f02ee0b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_018.png" +dest_files=["res://.godot/imported/box_018.png-6f5beb2788189a96a38142383f02ee0b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_019.png b/thumbs/box_019.png new file mode 100644 index 0000000..465096d Binary files /dev/null and b/thumbs/box_019.png differ diff --git a/thumbs/box_019.png.import b/thumbs/box_019.png.import new file mode 100644 index 0000000..2f0912a --- /dev/null +++ b/thumbs/box_019.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b24ievpudu6v6" +path="res://.godot/imported/box_019.png-86bb2b2552a540186dfe64da8a30778f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_019.png" +dest_files=["res://.godot/imported/box_019.png-86bb2b2552a540186dfe64da8a30778f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_020.png b/thumbs/box_020.png new file mode 100644 index 0000000..aae9193 Binary files /dev/null and b/thumbs/box_020.png differ diff --git a/thumbs/box_020.png.import b/thumbs/box_020.png.import new file mode 100644 index 0000000..098c4c2 --- /dev/null +++ b/thumbs/box_020.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cq1agqq8wtxyl" +path="res://.godot/imported/box_020.png-7aa5369e6ab82b79cd7588a38d89e8c5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_020.png" +dest_files=["res://.godot/imported/box_020.png-7aa5369e6ab82b79cd7588a38d89e8c5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_021.png b/thumbs/box_021.png new file mode 100644 index 0000000..ffaa67a Binary files /dev/null and b/thumbs/box_021.png differ diff --git a/thumbs/box_021.png.import b/thumbs/box_021.png.import new file mode 100644 index 0000000..8e5c264 --- /dev/null +++ b/thumbs/box_021.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://yu780r8avr2w" +path="res://.godot/imported/box_021.png-5556164202d4f6eb4d033be1d31b7da9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_021.png" +dest_files=["res://.godot/imported/box_021.png-5556164202d4f6eb4d033be1d31b7da9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_022.png b/thumbs/box_022.png new file mode 100644 index 0000000..699a278 Binary files /dev/null and b/thumbs/box_022.png differ diff --git a/thumbs/box_022.png.import b/thumbs/box_022.png.import new file mode 100644 index 0000000..f2014fd --- /dev/null +++ b/thumbs/box_022.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bitte36hx4ir0" +path="res://.godot/imported/box_022.png-162937265a6b75365f67810a136bc825.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_022.png" +dest_files=["res://.godot/imported/box_022.png-162937265a6b75365f67810a136bc825.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_023.png b/thumbs/box_023.png new file mode 100644 index 0000000..33b31f2 Binary files /dev/null and b/thumbs/box_023.png differ diff --git a/thumbs/box_023.png.import b/thumbs/box_023.png.import new file mode 100644 index 0000000..b26727e --- /dev/null +++ b/thumbs/box_023.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnn6kx5wjwfvs" +path="res://.godot/imported/box_023.png-0021a13a259c0bdb73af1c8901f7de5a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_023.png" +dest_files=["res://.godot/imported/box_023.png-0021a13a259c0bdb73af1c8901f7de5a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_024.png b/thumbs/box_024.png new file mode 100644 index 0000000..ccbb693 Binary files /dev/null and b/thumbs/box_024.png differ diff --git a/thumbs/box_024.png.import b/thumbs/box_024.png.import new file mode 100644 index 0000000..557cfee --- /dev/null +++ b/thumbs/box_024.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://757ay5rftfjw" +path="res://.godot/imported/box_024.png-7848ede1748a4118e3ca745d41c4b747.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_024.png" +dest_files=["res://.godot/imported/box_024.png-7848ede1748a4118e3ca745d41c4b747.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_025.png b/thumbs/box_025.png new file mode 100644 index 0000000..8b0b6c9 Binary files /dev/null and b/thumbs/box_025.png differ diff --git a/thumbs/box_025.png.import b/thumbs/box_025.png.import new file mode 100644 index 0000000..5294902 --- /dev/null +++ b/thumbs/box_025.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnj3ti10yhqbg" +path="res://.godot/imported/box_025.png-69300549c2c18173259955c959cf24e1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_025.png" +dest_files=["res://.godot/imported/box_025.png-69300549c2c18173259955c959cf24e1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_026.png b/thumbs/box_026.png new file mode 100644 index 0000000..fa5ea10 Binary files /dev/null and b/thumbs/box_026.png differ diff --git a/thumbs/box_026.png.import b/thumbs/box_026.png.import new file mode 100644 index 0000000..b78413c --- /dev/null +++ b/thumbs/box_026.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jmp0uq7uyhjs" +path="res://.godot/imported/box_026.png-7b995186153400c99ebb3ea51ab9d44f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_026.png" +dest_files=["res://.godot/imported/box_026.png-7b995186153400c99ebb3ea51ab9d44f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_027.png b/thumbs/box_027.png new file mode 100644 index 0000000..09e9c22 Binary files /dev/null and b/thumbs/box_027.png differ diff --git a/thumbs/box_027.png.import b/thumbs/box_027.png.import new file mode 100644 index 0000000..4a99ed2 --- /dev/null +++ b/thumbs/box_027.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxgmxncmhneob" +path="res://.godot/imported/box_027.png-bb2e33ef6bbe6c959bd58186b10ce601.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_027.png" +dest_files=["res://.godot/imported/box_027.png-bb2e33ef6bbe6c959bd58186b10ce601.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_028.png b/thumbs/box_028.png new file mode 100644 index 0000000..5d8b202 Binary files /dev/null and b/thumbs/box_028.png differ diff --git a/thumbs/box_028.png.import b/thumbs/box_028.png.import new file mode 100644 index 0000000..d47c7ac --- /dev/null +++ b/thumbs/box_028.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6pypbdb50ecd" +path="res://.godot/imported/box_028.png-8a6e7139a5f4f2d5f7d54cddb747bac8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_028.png" +dest_files=["res://.godot/imported/box_028.png-8a6e7139a5f4f2d5f7d54cddb747bac8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_029.png b/thumbs/box_029.png new file mode 100644 index 0000000..f51d55e Binary files /dev/null and b/thumbs/box_029.png differ diff --git a/thumbs/box_029.png.import b/thumbs/box_029.png.import new file mode 100644 index 0000000..e19804f --- /dev/null +++ b/thumbs/box_029.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://eqotu2qidpun" +path="res://.godot/imported/box_029.png-c0b93d34fc6a9dd21f96f38847103314.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_029.png" +dest_files=["res://.godot/imported/box_029.png-c0b93d34fc6a9dd21f96f38847103314.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_030.png b/thumbs/box_030.png new file mode 100644 index 0000000..cd2721c Binary files /dev/null and b/thumbs/box_030.png differ diff --git a/thumbs/box_030.png.import b/thumbs/box_030.png.import new file mode 100644 index 0000000..40646fe --- /dev/null +++ b/thumbs/box_030.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bu4t76prticai" +path="res://.godot/imported/box_030.png-9378e40114a3e0b9a69e3aed4cc63966.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_030.png" +dest_files=["res://.godot/imported/box_030.png-9378e40114a3e0b9a69e3aed4cc63966.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/box_031.png b/thumbs/box_031.png new file mode 100644 index 0000000..9940540 Binary files /dev/null and b/thumbs/box_031.png differ diff --git a/thumbs/box_031.png.import b/thumbs/box_031.png.import new file mode 100644 index 0000000..39245d6 --- /dev/null +++ b/thumbs/box_031.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c56xlvkv4hmdq" +path="res://.godot/imported/box_031.png-e9b8a6c272dd4339adff75dad2ad4f83.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/box_031.png" +dest_files=["res://.godot/imported/box_031.png-e9b8a6c272dd4339adff75dad2ad4f83.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/building_001.png b/thumbs/building_001.png new file mode 100644 index 0000000..9afbb78 Binary files /dev/null and b/thumbs/building_001.png differ diff --git a/thumbs/building_001.png.import b/thumbs/building_001.png.import new file mode 100644 index 0000000..877b110 --- /dev/null +++ b/thumbs/building_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://biq11fg873opn" +path="res://.godot/imported/building_001.png-eede809ed875d37c26152dca5e9f0bdf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/building_001.png" +dest_files=["res://.godot/imported/building_001.png-eede809ed875d37c26152dca5e9f0bdf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bush_002.png b/thumbs/bush_002.png new file mode 100644 index 0000000..69b48c7 Binary files /dev/null and b/thumbs/bush_002.png differ diff --git a/thumbs/bush_002.png.import b/thumbs/bush_002.png.import new file mode 100644 index 0000000..b127ff1 --- /dev/null +++ b/thumbs/bush_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvwgcwf1tqtyi" +path="res://.godot/imported/bush_002.png-b49ef48e6bea3168514ff01c63e5211b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bush_002.png" +dest_files=["res://.godot/imported/bush_002.png-b49ef48e6bea3168514ff01c63e5211b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bush_006.png b/thumbs/bush_006.png new file mode 100644 index 0000000..c45ca70 Binary files /dev/null and b/thumbs/bush_006.png differ diff --git a/thumbs/bush_006.png.import b/thumbs/bush_006.png.import new file mode 100644 index 0000000..6e05c0d --- /dev/null +++ b/thumbs/bush_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfoy6lnosuawr" +path="res://.godot/imported/bush_006.png-cb51347701436de4d864aecaa0296c50.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bush_006.png" +dest_files=["res://.godot/imported/bush_006.png-cb51347701436de4d864aecaa0296c50.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/bush_007.png b/thumbs/bush_007.png new file mode 100644 index 0000000..60354a4 Binary files /dev/null and b/thumbs/bush_007.png differ diff --git a/thumbs/bush_007.png.import b/thumbs/bush_007.png.import new file mode 100644 index 0000000..6d50146 --- /dev/null +++ b/thumbs/bush_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ob0auhkag3in" +path="res://.godot/imported/bush_007.png-a56db181865515cd664eec22098e27ab.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/bush_007.png" +dest_files=["res://.godot/imported/bush_007.png-a56db181865515cd664eec22098e27ab.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_001.png b/thumbs/cactus_001.png new file mode 100644 index 0000000..d909f6c Binary files /dev/null and b/thumbs/cactus_001.png differ diff --git a/thumbs/cactus_001.png.import b/thumbs/cactus_001.png.import new file mode 100644 index 0000000..6921bba --- /dev/null +++ b/thumbs/cactus_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnpl6qkkcial3" +path="res://.godot/imported/cactus_001.png-21e54d38b277d9dd238142d5a4be6c16.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_001.png" +dest_files=["res://.godot/imported/cactus_001.png-21e54d38b277d9dd238142d5a4be6c16.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_002.png b/thumbs/cactus_002.png new file mode 100644 index 0000000..f75a6ec Binary files /dev/null and b/thumbs/cactus_002.png differ diff --git a/thumbs/cactus_002.png.import b/thumbs/cactus_002.png.import new file mode 100644 index 0000000..5adc971 --- /dev/null +++ b/thumbs/cactus_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmqr8fbm8aqi6" +path="res://.godot/imported/cactus_002.png-f019fa2642a4b5d6c604339bffaac9bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_002.png" +dest_files=["res://.godot/imported/cactus_002.png-f019fa2642a4b5d6c604339bffaac9bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_003.png b/thumbs/cactus_003.png new file mode 100644 index 0000000..6bb5b2e Binary files /dev/null and b/thumbs/cactus_003.png differ diff --git a/thumbs/cactus_003.png.import b/thumbs/cactus_003.png.import new file mode 100644 index 0000000..9f444a9 --- /dev/null +++ b/thumbs/cactus_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8x2ac0df6ge7" +path="res://.godot/imported/cactus_003.png-6c139d54c0012263761e734012076a12.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_003.png" +dest_files=["res://.godot/imported/cactus_003.png-6c139d54c0012263761e734012076a12.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_004.png b/thumbs/cactus_004.png new file mode 100644 index 0000000..de0ac2e Binary files /dev/null and b/thumbs/cactus_004.png differ diff --git a/thumbs/cactus_004.png.import b/thumbs/cactus_004.png.import new file mode 100644 index 0000000..062d99a --- /dev/null +++ b/thumbs/cactus_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cga1nrd847sbx" +path="res://.godot/imported/cactus_004.png-5533e8ff52546a669f4eeb90b902d0b2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_004.png" +dest_files=["res://.godot/imported/cactus_004.png-5533e8ff52546a669f4eeb90b902d0b2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_005.png b/thumbs/cactus_005.png new file mode 100644 index 0000000..ffec729 Binary files /dev/null and b/thumbs/cactus_005.png differ diff --git a/thumbs/cactus_005.png.import b/thumbs/cactus_005.png.import new file mode 100644 index 0000000..50f22a3 --- /dev/null +++ b/thumbs/cactus_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bedpnfe5jg3j7" +path="res://.godot/imported/cactus_005.png-3e47f7af849f5dc30fb739607a69d48c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_005.png" +dest_files=["res://.godot/imported/cactus_005.png-3e47f7af849f5dc30fb739607a69d48c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_006.png b/thumbs/cactus_006.png new file mode 100644 index 0000000..064342e Binary files /dev/null and b/thumbs/cactus_006.png differ diff --git a/thumbs/cactus_006.png.import b/thumbs/cactus_006.png.import new file mode 100644 index 0000000..8ab3c18 --- /dev/null +++ b/thumbs/cactus_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cg8x33vcnux8v" +path="res://.godot/imported/cactus_006.png-7cf8efb6b826298a4efb335706b42104.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_006.png" +dest_files=["res://.godot/imported/cactus_006.png-7cf8efb6b826298a4efb335706b42104.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_007.png b/thumbs/cactus_007.png new file mode 100644 index 0000000..fbb3074 Binary files /dev/null and b/thumbs/cactus_007.png differ diff --git a/thumbs/cactus_007.png.import b/thumbs/cactus_007.png.import new file mode 100644 index 0000000..b70068a --- /dev/null +++ b/thumbs/cactus_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lh3s5sksx31v" +path="res://.godot/imported/cactus_007.png-f2be442d19a53747f19ca104a4708390.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_007.png" +dest_files=["res://.godot/imported/cactus_007.png-f2be442d19a53747f19ca104a4708390.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_008.png b/thumbs/cactus_008.png new file mode 100644 index 0000000..2a86b7f Binary files /dev/null and b/thumbs/cactus_008.png differ diff --git a/thumbs/cactus_008.png.import b/thumbs/cactus_008.png.import new file mode 100644 index 0000000..2b1d2b8 --- /dev/null +++ b/thumbs/cactus_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brdj5ti0abs86" +path="res://.godot/imported/cactus_008.png-a590d884b399127c9a945f5264db79bb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_008.png" +dest_files=["res://.godot/imported/cactus_008.png-a590d884b399127c9a945f5264db79bb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_009.png b/thumbs/cactus_009.png new file mode 100644 index 0000000..7ef0227 Binary files /dev/null and b/thumbs/cactus_009.png differ diff --git a/thumbs/cactus_009.png.import b/thumbs/cactus_009.png.import new file mode 100644 index 0000000..e4b85c7 --- /dev/null +++ b/thumbs/cactus_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2lg8rvrhc70n" +path="res://.godot/imported/cactus_009.png-0475d5327c6d61d97845c24b518ca20a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_009.png" +dest_files=["res://.godot/imported/cactus_009.png-0475d5327c6d61d97845c24b518ca20a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_010.png b/thumbs/cactus_010.png new file mode 100644 index 0000000..a709ad9 Binary files /dev/null and b/thumbs/cactus_010.png differ diff --git a/thumbs/cactus_010.png.import b/thumbs/cactus_010.png.import new file mode 100644 index 0000000..6ff36e4 --- /dev/null +++ b/thumbs/cactus_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4x6us402quml" +path="res://.godot/imported/cactus_010.png-3c08002c79be4a2ebe234ec2747bb078.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_010.png" +dest_files=["res://.godot/imported/cactus_010.png-3c08002c79be4a2ebe234ec2747bb078.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_011.png b/thumbs/cactus_011.png new file mode 100644 index 0000000..506ff6d Binary files /dev/null and b/thumbs/cactus_011.png differ diff --git a/thumbs/cactus_011.png.import b/thumbs/cactus_011.png.import new file mode 100644 index 0000000..1ab9158 --- /dev/null +++ b/thumbs/cactus_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d23nmj61bcjq6" +path="res://.godot/imported/cactus_011.png-73e07c5440a25d1ec8de414c3f20db3f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_011.png" +dest_files=["res://.godot/imported/cactus_011.png-73e07c5440a25d1ec8de414c3f20db3f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_012.png b/thumbs/cactus_012.png new file mode 100644 index 0000000..21423fe Binary files /dev/null and b/thumbs/cactus_012.png differ diff --git a/thumbs/cactus_012.png.import b/thumbs/cactus_012.png.import new file mode 100644 index 0000000..d765fd6 --- /dev/null +++ b/thumbs/cactus_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buhs4733phpd" +path="res://.godot/imported/cactus_012.png-440144ff15e63cea0d978f8d93a000f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_012.png" +dest_files=["res://.godot/imported/cactus_012.png-440144ff15e63cea0d978f8d93a000f5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_013.png b/thumbs/cactus_013.png new file mode 100644 index 0000000..4271fcd Binary files /dev/null and b/thumbs/cactus_013.png differ diff --git a/thumbs/cactus_013.png.import b/thumbs/cactus_013.png.import new file mode 100644 index 0000000..7210472 --- /dev/null +++ b/thumbs/cactus_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dp6fb8u33pyit" +path="res://.godot/imported/cactus_013.png-44f3666210a7d2950e2645c830dd1348.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_013.png" +dest_files=["res://.godot/imported/cactus_013.png-44f3666210a7d2950e2645c830dd1348.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_014.png b/thumbs/cactus_014.png new file mode 100644 index 0000000..45abb17 Binary files /dev/null and b/thumbs/cactus_014.png differ diff --git a/thumbs/cactus_014.png.import b/thumbs/cactus_014.png.import new file mode 100644 index 0000000..5bc7c1f --- /dev/null +++ b/thumbs/cactus_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgkcadtfplqkl" +path="res://.godot/imported/cactus_014.png-4228d4a531ff0b68ee692366307eda30.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_014.png" +dest_files=["res://.godot/imported/cactus_014.png-4228d4a531ff0b68ee692366307eda30.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cactus_015.png b/thumbs/cactus_015.png new file mode 100644 index 0000000..2abe908 Binary files /dev/null and b/thumbs/cactus_015.png differ diff --git a/thumbs/cactus_015.png.import b/thumbs/cactus_015.png.import new file mode 100644 index 0000000..a532459 --- /dev/null +++ b/thumbs/cactus_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6myrk0su04vp" +path="res://.godot/imported/cactus_015.png-23df9f1d71ead0d346933735de944ed0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cactus_015.png" +dest_files=["res://.godot/imported/cactus_015.png-23df9f1d71ead0d346933735de944ed0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cart_001.png b/thumbs/cart_001.png new file mode 100644 index 0000000..7d9023a Binary files /dev/null and b/thumbs/cart_001.png differ diff --git a/thumbs/cart_001.png.import b/thumbs/cart_001.png.import new file mode 100644 index 0000000..a567cd3 --- /dev/null +++ b/thumbs/cart_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmx3er8wpq4qq" +path="res://.godot/imported/cart_001.png-94d8848f8cab717bad22aa9559dd82cd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cart_001.png" +dest_files=["res://.godot/imported/cart_001.png-94d8848f8cab717bad22aa9559dd82cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/checkpoint_001.png b/thumbs/checkpoint_001.png new file mode 100644 index 0000000..5a8ef88 Binary files /dev/null and b/thumbs/checkpoint_001.png differ diff --git a/thumbs/checkpoint_001.png.import b/thumbs/checkpoint_001.png.import new file mode 100644 index 0000000..2867bc2 --- /dev/null +++ b/thumbs/checkpoint_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cy2em5lkr3a0x" +path="res://.godot/imported/checkpoint_001.png-919be518bdbe81489903803bf8ecdeea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/checkpoint_001.png" +dest_files=["res://.godot/imported/checkpoint_001.png-919be518bdbe81489903803bf8ecdeea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cistern_001.png b/thumbs/cistern_001.png new file mode 100644 index 0000000..2acb3d2 Binary files /dev/null and b/thumbs/cistern_001.png differ diff --git a/thumbs/cistern_001.png.import b/thumbs/cistern_001.png.import new file mode 100644 index 0000000..6a95882 --- /dev/null +++ b/thumbs/cistern_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dn2k5xiv5c5n8" +path="res://.godot/imported/cistern_001.png-65f1f2b3074a18f2ab17db41b79ba065.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cistern_001.png" +dest_files=["res://.godot/imported/cistern_001.png-65f1f2b3074a18f2ab17db41b79ba065.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cistern_002.png b/thumbs/cistern_002.png new file mode 100644 index 0000000..6c8741c Binary files /dev/null and b/thumbs/cistern_002.png differ diff --git a/thumbs/cistern_002.png.import b/thumbs/cistern_002.png.import new file mode 100644 index 0000000..e319a3e --- /dev/null +++ b/thumbs/cistern_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0h16f3gjgeso" +path="res://.godot/imported/cistern_002.png-0caa10329e79f9a4848bfc4705ae5243.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cistern_002.png" +dest_files=["res://.godot/imported/cistern_002.png-0caa10329e79f9a4848bfc4705ae5243.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cistern_003.png b/thumbs/cistern_003.png new file mode 100644 index 0000000..875e22d Binary files /dev/null and b/thumbs/cistern_003.png differ diff --git a/thumbs/cistern_003.png.import b/thumbs/cistern_003.png.import new file mode 100644 index 0000000..c7dc1e5 --- /dev/null +++ b/thumbs/cistern_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdii624lhc7on" +path="res://.godot/imported/cistern_003.png-324a04044fa30e03cfc90b02fd620e35.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cistern_003.png" +dest_files=["res://.godot/imported/cistern_003.png-324a04044fa30e03cfc90b02fd620e35.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/cistern_004.png b/thumbs/cistern_004.png new file mode 100644 index 0000000..9689bf6 Binary files /dev/null and b/thumbs/cistern_004.png differ diff --git a/thumbs/cistern_004.png.import b/thumbs/cistern_004.png.import new file mode 100644 index 0000000..45179ce --- /dev/null +++ b/thumbs/cistern_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://sl043fjou35j" +path="res://.godot/imported/cistern_004.png-d0ff828236410f4031e08980a949d9c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/cistern_004.png" +dest_files=["res://.godot/imported/cistern_004.png-d0ff828236410f4031e08980a949d9c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/coil_001.png b/thumbs/coil_001.png new file mode 100644 index 0000000..7173f85 Binary files /dev/null and b/thumbs/coil_001.png differ diff --git a/thumbs/coil_001.png.import b/thumbs/coil_001.png.import new file mode 100644 index 0000000..a05d6cd --- /dev/null +++ b/thumbs/coil_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drouenbewaov" +path="res://.godot/imported/coil_001.png-0bb272e8824649ef05b9d63cfd9ef3dc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/coil_001.png" +dest_files=["res://.godot/imported/coil_001.png-0bb272e8824649ef05b9d63cfd9ef3dc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_001.png b/thumbs/concrete_wall_001.png new file mode 100644 index 0000000..d97369c Binary files /dev/null and b/thumbs/concrete_wall_001.png differ diff --git a/thumbs/concrete_wall_001.png.import b/thumbs/concrete_wall_001.png.import new file mode 100644 index 0000000..e4c2350 --- /dev/null +++ b/thumbs/concrete_wall_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw7neaaf768e5" +path="res://.godot/imported/concrete_wall_001.png-5e4688aac117b482d40326fd10db8843.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_001.png" +dest_files=["res://.godot/imported/concrete_wall_001.png-5e4688aac117b482d40326fd10db8843.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_002.png b/thumbs/concrete_wall_002.png new file mode 100644 index 0000000..3673415 Binary files /dev/null and b/thumbs/concrete_wall_002.png differ diff --git a/thumbs/concrete_wall_002.png.import b/thumbs/concrete_wall_002.png.import new file mode 100644 index 0000000..fd1b07e --- /dev/null +++ b/thumbs/concrete_wall_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxxeanim6i2j6" +path="res://.godot/imported/concrete_wall_002.png-a594c49a4d2091ad8ceacbc3df8c1dda.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_002.png" +dest_files=["res://.godot/imported/concrete_wall_002.png-a594c49a4d2091ad8ceacbc3df8c1dda.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_destroyed_001.png b/thumbs/concrete_wall_destroyed_001.png new file mode 100644 index 0000000..9201564 Binary files /dev/null and b/thumbs/concrete_wall_destroyed_001.png differ diff --git a/thumbs/concrete_wall_destroyed_001.png.import b/thumbs/concrete_wall_destroyed_001.png.import new file mode 100644 index 0000000..5dcb7de --- /dev/null +++ b/thumbs/concrete_wall_destroyed_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8wtbri6f8hp7" +path="res://.godot/imported/concrete_wall_destroyed_001.png-0a826909d48ea4004b84b85b94377352.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_destroyed_001.png" +dest_files=["res://.godot/imported/concrete_wall_destroyed_001.png-0a826909d48ea4004b84b85b94377352.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_destroyed_002.png b/thumbs/concrete_wall_destroyed_002.png new file mode 100644 index 0000000..a0d57dd Binary files /dev/null and b/thumbs/concrete_wall_destroyed_002.png differ diff --git a/thumbs/concrete_wall_destroyed_002.png.import b/thumbs/concrete_wall_destroyed_002.png.import new file mode 100644 index 0000000..32961e8 --- /dev/null +++ b/thumbs/concrete_wall_destroyed_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxylpd18bexn5" +path="res://.godot/imported/concrete_wall_destroyed_002.png-50fbcf82f27c8cd1ecc5456afb044e96.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_destroyed_002.png" +dest_files=["res://.godot/imported/concrete_wall_destroyed_002.png-50fbcf82f27c8cd1ecc5456afb044e96.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_destroyed_003.png b/thumbs/concrete_wall_destroyed_003.png new file mode 100644 index 0000000..11081dd Binary files /dev/null and b/thumbs/concrete_wall_destroyed_003.png differ diff --git a/thumbs/concrete_wall_destroyed_003.png.import b/thumbs/concrete_wall_destroyed_003.png.import new file mode 100644 index 0000000..a1c2d68 --- /dev/null +++ b/thumbs/concrete_wall_destroyed_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://n41mrea0dnoc" +path="res://.godot/imported/concrete_wall_destroyed_003.png-c5dfdb306f270c12e89978e6c10bc2ba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_destroyed_003.png" +dest_files=["res://.godot/imported/concrete_wall_destroyed_003.png-c5dfdb306f270c12e89978e6c10bc2ba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/concrete_wall_destroyed_004.png b/thumbs/concrete_wall_destroyed_004.png new file mode 100644 index 0000000..e48098d Binary files /dev/null and b/thumbs/concrete_wall_destroyed_004.png differ diff --git a/thumbs/concrete_wall_destroyed_004.png.import b/thumbs/concrete_wall_destroyed_004.png.import new file mode 100644 index 0000000..f11f6fa --- /dev/null +++ b/thumbs/concrete_wall_destroyed_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tua01wiox5nv" +path="res://.godot/imported/concrete_wall_destroyed_004.png-9305dddb356201bc9e244c9fbd0f369f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/concrete_wall_destroyed_004.png" +dest_files=["res://.godot/imported/concrete_wall_destroyed_004.png-9305dddb356201bc9e244c9fbd0f369f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/conditioner_001.png b/thumbs/conditioner_001.png new file mode 100644 index 0000000..dd61a9e Binary files /dev/null and b/thumbs/conditioner_001.png differ diff --git a/thumbs/conditioner_001.png.import b/thumbs/conditioner_001.png.import new file mode 100644 index 0000000..39a5285 --- /dev/null +++ b/thumbs/conditioner_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjwonhrlw4nsu" +path="res://.godot/imported/conditioner_001.png-0d9e1d8c07affcf7432163ab68dd8561.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/conditioner_001.png" +dest_files=["res://.godot/imported/conditioner_001.png-0d9e1d8c07affcf7432163ab68dd8561.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/conditioner_002.png b/thumbs/conditioner_002.png new file mode 100644 index 0000000..6fdc2f8 Binary files /dev/null and b/thumbs/conditioner_002.png differ diff --git a/thumbs/conditioner_002.png.import b/thumbs/conditioner_002.png.import new file mode 100644 index 0000000..bf449cf --- /dev/null +++ b/thumbs/conditioner_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5oj0uarsfby" +path="res://.godot/imported/conditioner_002.png-00b39c4959a878f1d2be8e7029b4434d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/conditioner_002.png" +dest_files=["res://.godot/imported/conditioner_002.png-00b39c4959a878f1d2be8e7029b4434d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/construction_001.png b/thumbs/construction_001.png new file mode 100644 index 0000000..f88ced7 Binary files /dev/null and b/thumbs/construction_001.png differ diff --git a/thumbs/construction_001.png.import b/thumbs/construction_001.png.import new file mode 100644 index 0000000..6a0e79c --- /dev/null +++ b/thumbs/construction_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw11b0cqn1ju7" +path="res://.godot/imported/construction_001.png-1658dd738fc09e0abb76d6f1990cee0d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/construction_001.png" +dest_files=["res://.godot/imported/construction_001.png-1658dd738fc09e0abb76d6f1990cee0d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/construction_002.png b/thumbs/construction_002.png new file mode 100644 index 0000000..4af02ed Binary files /dev/null and b/thumbs/construction_002.png differ diff --git a/thumbs/construction_002.png.import b/thumbs/construction_002.png.import new file mode 100644 index 0000000..9fc66f2 --- /dev/null +++ b/thumbs/construction_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbly31kixa54d" +path="res://.godot/imported/construction_002.png-19b8319ce5986c60d6a5bcc5e0035092.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/construction_002.png" +dest_files=["res://.godot/imported/construction_002.png-19b8319ce5986c60d6a5bcc5e0035092.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_001.png b/thumbs/container_001.png new file mode 100644 index 0000000..a30b6e0 Binary files /dev/null and b/thumbs/container_001.png differ diff --git a/thumbs/container_001.png.import b/thumbs/container_001.png.import new file mode 100644 index 0000000..aa0a2a9 --- /dev/null +++ b/thumbs/container_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfk3n0s55m3ik" +path="res://.godot/imported/container_001.png-07e0f052cdfc610dbf637e3ccef75399.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_001.png" +dest_files=["res://.godot/imported/container_001.png-07e0f052cdfc610dbf637e3ccef75399.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_001_offen1.png b/thumbs/container_001_offen1.png new file mode 100644 index 0000000..0a62d42 Binary files /dev/null and b/thumbs/container_001_offen1.png differ diff --git a/thumbs/container_001_offen1.png.import b/thumbs/container_001_offen1.png.import new file mode 100644 index 0000000..72b31db --- /dev/null +++ b/thumbs/container_001_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btjpsdieswfhj" +path="res://.godot/imported/container_001_offen1.png-66935bd29ba4922f0612c787c942d969.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_001_offen1.png" +dest_files=["res://.godot/imported/container_001_offen1.png-66935bd29ba4922f0612c787c942d969.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_001_offen2.png b/thumbs/container_001_offen2.png new file mode 100644 index 0000000..5c30fb4 Binary files /dev/null and b/thumbs/container_001_offen2.png differ diff --git a/thumbs/container_001_offen2.png.import b/thumbs/container_001_offen2.png.import new file mode 100644 index 0000000..3a0ecd8 --- /dev/null +++ b/thumbs/container_001_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bngbmgxfh4ft7" +path="res://.godot/imported/container_001_offen2.png-23f7fba7b090b984ce879d8f7e8e7326.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_001_offen2.png" +dest_files=["res://.godot/imported/container_001_offen2.png-23f7fba7b090b984ce879d8f7e8e7326.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_001_ohne.png b/thumbs/container_001_ohne.png new file mode 100644 index 0000000..b41182d Binary files /dev/null and b/thumbs/container_001_ohne.png differ diff --git a/thumbs/container_001_ohne.png.import b/thumbs/container_001_ohne.png.import new file mode 100644 index 0000000..c7b42f5 --- /dev/null +++ b/thumbs/container_001_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgdt3h13rqdug" +path="res://.godot/imported/container_001_ohne.png-d8c0286890584dfcc8ceaf1c8f7861ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_001_ohne.png" +dest_files=["res://.godot/imported/container_001_ohne.png-d8c0286890584dfcc8ceaf1c8f7861ad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_002.png b/thumbs/container_002.png new file mode 100644 index 0000000..08ccfc2 Binary files /dev/null and b/thumbs/container_002.png differ diff --git a/thumbs/container_002.png.import b/thumbs/container_002.png.import new file mode 100644 index 0000000..c38490d --- /dev/null +++ b/thumbs/container_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bendte7uy1vum" +path="res://.godot/imported/container_002.png-f0b804e2d7ff741ab075daea77821707.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_002.png" +dest_files=["res://.godot/imported/container_002.png-f0b804e2d7ff741ab075daea77821707.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_002_offen1.png b/thumbs/container_002_offen1.png new file mode 100644 index 0000000..6dd2d11 Binary files /dev/null and b/thumbs/container_002_offen1.png differ diff --git a/thumbs/container_002_offen1.png.import b/thumbs/container_002_offen1.png.import new file mode 100644 index 0000000..dced7f4 --- /dev/null +++ b/thumbs/container_002_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chcsxajuvgspk" +path="res://.godot/imported/container_002_offen1.png-f8c6cccce4944659897bc6a98e0d25d7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_002_offen1.png" +dest_files=["res://.godot/imported/container_002_offen1.png-f8c6cccce4944659897bc6a98e0d25d7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_002_offen2.png b/thumbs/container_002_offen2.png new file mode 100644 index 0000000..ed51212 Binary files /dev/null and b/thumbs/container_002_offen2.png differ diff --git a/thumbs/container_002_offen2.png.import b/thumbs/container_002_offen2.png.import new file mode 100644 index 0000000..09c7998 --- /dev/null +++ b/thumbs/container_002_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw56ic7fws26" +path="res://.godot/imported/container_002_offen2.png-d7b9fe96a10c3a4e692911bfd40e7240.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_002_offen2.png" +dest_files=["res://.godot/imported/container_002_offen2.png-d7b9fe96a10c3a4e692911bfd40e7240.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_002_ohne.png b/thumbs/container_002_ohne.png new file mode 100644 index 0000000..4a8ef89 Binary files /dev/null and b/thumbs/container_002_ohne.png differ diff --git a/thumbs/container_002_ohne.png.import b/thumbs/container_002_ohne.png.import new file mode 100644 index 0000000..f9372fb --- /dev/null +++ b/thumbs/container_002_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwc0jmnhnch4k" +path="res://.godot/imported/container_002_ohne.png-5fa21dd48f1897875695b301f7ccb2f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_002_ohne.png" +dest_files=["res://.godot/imported/container_002_ohne.png-5fa21dd48f1897875695b301f7ccb2f5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_003.png b/thumbs/container_003.png new file mode 100644 index 0000000..6a0794e Binary files /dev/null and b/thumbs/container_003.png differ diff --git a/thumbs/container_003.png.import b/thumbs/container_003.png.import new file mode 100644 index 0000000..68b218c --- /dev/null +++ b/thumbs/container_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dupngc2xv7it8" +path="res://.godot/imported/container_003.png-6602135b960e178eb163aba029d7287b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_003.png" +dest_files=["res://.godot/imported/container_003.png-6602135b960e178eb163aba029d7287b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_003_offen1.png b/thumbs/container_003_offen1.png new file mode 100644 index 0000000..12b3fc5 Binary files /dev/null and b/thumbs/container_003_offen1.png differ diff --git a/thumbs/container_003_offen1.png.import b/thumbs/container_003_offen1.png.import new file mode 100644 index 0000000..0930784 --- /dev/null +++ b/thumbs/container_003_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dprnj64lwdete" +path="res://.godot/imported/container_003_offen1.png-bc54f3aa06c7683aa41a6f9d4f45a896.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_003_offen1.png" +dest_files=["res://.godot/imported/container_003_offen1.png-bc54f3aa06c7683aa41a6f9d4f45a896.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_003_offen2.png b/thumbs/container_003_offen2.png new file mode 100644 index 0000000..6a3b777 Binary files /dev/null and b/thumbs/container_003_offen2.png differ diff --git a/thumbs/container_003_offen2.png.import b/thumbs/container_003_offen2.png.import new file mode 100644 index 0000000..2f77253 --- /dev/null +++ b/thumbs/container_003_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2kpdcvfpfapd" +path="res://.godot/imported/container_003_offen2.png-4089b96be64c84fcdd6fdf336cd0694d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_003_offen2.png" +dest_files=["res://.godot/imported/container_003_offen2.png-4089b96be64c84fcdd6fdf336cd0694d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_003_ohne.png b/thumbs/container_003_ohne.png new file mode 100644 index 0000000..c4fb612 Binary files /dev/null and b/thumbs/container_003_ohne.png differ diff --git a/thumbs/container_003_ohne.png.import b/thumbs/container_003_ohne.png.import new file mode 100644 index 0000000..867130c --- /dev/null +++ b/thumbs/container_003_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://df4oqdfkq2odt" +path="res://.godot/imported/container_003_ohne.png-a91b30e5b3300af76a81aa283e8fd3f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_003_ohne.png" +dest_files=["res://.godot/imported/container_003_ohne.png-a91b30e5b3300af76a81aa283e8fd3f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_004.png b/thumbs/container_004.png new file mode 100644 index 0000000..b786200 Binary files /dev/null and b/thumbs/container_004.png differ diff --git a/thumbs/container_004.png.import b/thumbs/container_004.png.import new file mode 100644 index 0000000..e04afbf --- /dev/null +++ b/thumbs/container_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dx6067mlj6jdc" +path="res://.godot/imported/container_004.png-fa22b914a672c1f97f7809fc3a8a7845.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_004.png" +dest_files=["res://.godot/imported/container_004.png-fa22b914a672c1f97f7809fc3a8a7845.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_004_offen1.png b/thumbs/container_004_offen1.png new file mode 100644 index 0000000..eb88c8c Binary files /dev/null and b/thumbs/container_004_offen1.png differ diff --git a/thumbs/container_004_offen1.png.import b/thumbs/container_004_offen1.png.import new file mode 100644 index 0000000..65092fe --- /dev/null +++ b/thumbs/container_004_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ij0up66wkdei" +path="res://.godot/imported/container_004_offen1.png-639d1fdc12e40ae2fd8e7c6d7665e7fd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_004_offen1.png" +dest_files=["res://.godot/imported/container_004_offen1.png-639d1fdc12e40ae2fd8e7c6d7665e7fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_004_offen2.png b/thumbs/container_004_offen2.png new file mode 100644 index 0000000..2ba16d1 Binary files /dev/null and b/thumbs/container_004_offen2.png differ diff --git a/thumbs/container_004_offen2.png.import b/thumbs/container_004_offen2.png.import new file mode 100644 index 0000000..088c09d --- /dev/null +++ b/thumbs/container_004_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6so0720te2wu" +path="res://.godot/imported/container_004_offen2.png-65a2ff4c43475c590e8c260411766967.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_004_offen2.png" +dest_files=["res://.godot/imported/container_004_offen2.png-65a2ff4c43475c590e8c260411766967.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_004_ohne.png b/thumbs/container_004_ohne.png new file mode 100644 index 0000000..08f658d Binary files /dev/null and b/thumbs/container_004_ohne.png differ diff --git a/thumbs/container_004_ohne.png.import b/thumbs/container_004_ohne.png.import new file mode 100644 index 0000000..45cc6de --- /dev/null +++ b/thumbs/container_004_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0g2gmbbh2ltg" +path="res://.godot/imported/container_004_ohne.png-ba921267ce01e62ae18dbdb4634a10e0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_004_ohne.png" +dest_files=["res://.godot/imported/container_004_ohne.png-ba921267ce01e62ae18dbdb4634a10e0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_005.png b/thumbs/container_005.png new file mode 100644 index 0000000..ac22293 Binary files /dev/null and b/thumbs/container_005.png differ diff --git a/thumbs/container_005.png.import b/thumbs/container_005.png.import new file mode 100644 index 0000000..b0c67df --- /dev/null +++ b/thumbs/container_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf57o5pnj355e" +path="res://.godot/imported/container_005.png-44dac20b33a2377df4629488ec050e8c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_005.png" +dest_files=["res://.godot/imported/container_005.png-44dac20b33a2377df4629488ec050e8c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_005_offen1.png b/thumbs/container_005_offen1.png new file mode 100644 index 0000000..9ae7891 Binary files /dev/null and b/thumbs/container_005_offen1.png differ diff --git a/thumbs/container_005_offen1.png.import b/thumbs/container_005_offen1.png.import new file mode 100644 index 0000000..08d6522 --- /dev/null +++ b/thumbs/container_005_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b05yavnc71m3q" +path="res://.godot/imported/container_005_offen1.png-abe9a419aaa7261b44ea2c423c1c2fed.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_005_offen1.png" +dest_files=["res://.godot/imported/container_005_offen1.png-abe9a419aaa7261b44ea2c423c1c2fed.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_005_offen2.png b/thumbs/container_005_offen2.png new file mode 100644 index 0000000..857abc2 Binary files /dev/null and b/thumbs/container_005_offen2.png differ diff --git a/thumbs/container_005_offen2.png.import b/thumbs/container_005_offen2.png.import new file mode 100644 index 0000000..a3b5717 --- /dev/null +++ b/thumbs/container_005_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7sjub4cbkxvg" +path="res://.godot/imported/container_005_offen2.png-b9a4568f12f6f8709ddfe53c8915cbac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_005_offen2.png" +dest_files=["res://.godot/imported/container_005_offen2.png-b9a4568f12f6f8709ddfe53c8915cbac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_005_ohne.png b/thumbs/container_005_ohne.png new file mode 100644 index 0000000..9058d09 Binary files /dev/null and b/thumbs/container_005_ohne.png differ diff --git a/thumbs/container_005_ohne.png.import b/thumbs/container_005_ohne.png.import new file mode 100644 index 0000000..77a9c13 --- /dev/null +++ b/thumbs/container_005_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnsu2xyjabygv" +path="res://.godot/imported/container_005_ohne.png-21d69026ac64e40efa15ffe31cd6e52e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_005_ohne.png" +dest_files=["res://.godot/imported/container_005_ohne.png-21d69026ac64e40efa15ffe31cd6e52e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_006.png b/thumbs/container_006.png new file mode 100644 index 0000000..2d3fdba Binary files /dev/null and b/thumbs/container_006.png differ diff --git a/thumbs/container_006.png.import b/thumbs/container_006.png.import new file mode 100644 index 0000000..0dcaf4f --- /dev/null +++ b/thumbs/container_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gub8lvdvu8ig" +path="res://.godot/imported/container_006.png-6bbad0f31b1f9733dd0fbfe92452c03b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_006.png" +dest_files=["res://.godot/imported/container_006.png-6bbad0f31b1f9733dd0fbfe92452c03b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_006_offen1.png b/thumbs/container_006_offen1.png new file mode 100644 index 0000000..dd29452 Binary files /dev/null and b/thumbs/container_006_offen1.png differ diff --git a/thumbs/container_006_offen1.png.import b/thumbs/container_006_offen1.png.import new file mode 100644 index 0000000..a0c1f38 --- /dev/null +++ b/thumbs/container_006_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxxe0may3yeqc" +path="res://.godot/imported/container_006_offen1.png-be868c6ee5519f55924324726ee6e809.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_006_offen1.png" +dest_files=["res://.godot/imported/container_006_offen1.png-be868c6ee5519f55924324726ee6e809.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_006_offen2.png b/thumbs/container_006_offen2.png new file mode 100644 index 0000000..bcff00a Binary files /dev/null and b/thumbs/container_006_offen2.png differ diff --git a/thumbs/container_006_offen2.png.import b/thumbs/container_006_offen2.png.import new file mode 100644 index 0000000..c3f49c6 --- /dev/null +++ b/thumbs/container_006_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4e8fd4t7r7m6" +path="res://.godot/imported/container_006_offen2.png-1e7ceda4157caf471ff2302bf78778ea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_006_offen2.png" +dest_files=["res://.godot/imported/container_006_offen2.png-1e7ceda4157caf471ff2302bf78778ea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_006_ohne.png b/thumbs/container_006_ohne.png new file mode 100644 index 0000000..332cd4c Binary files /dev/null and b/thumbs/container_006_ohne.png differ diff --git a/thumbs/container_006_ohne.png.import b/thumbs/container_006_ohne.png.import new file mode 100644 index 0000000..c0d028f --- /dev/null +++ b/thumbs/container_006_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhrrb85snu41i" +path="res://.godot/imported/container_006_ohne.png-532692f0dfce5ee060e28dbac5d17469.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_006_ohne.png" +dest_files=["res://.godot/imported/container_006_ohne.png-532692f0dfce5ee060e28dbac5d17469.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_007.png b/thumbs/container_007.png new file mode 100644 index 0000000..97ab265 Binary files /dev/null and b/thumbs/container_007.png differ diff --git a/thumbs/container_007.png.import b/thumbs/container_007.png.import new file mode 100644 index 0000000..29067cf --- /dev/null +++ b/thumbs/container_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csnvgkhlhmvwt" +path="res://.godot/imported/container_007.png-b7259edecf3ea939113cbf81a5ad003e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_007.png" +dest_files=["res://.godot/imported/container_007.png-b7259edecf3ea939113cbf81a5ad003e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_007_offen1.png b/thumbs/container_007_offen1.png new file mode 100644 index 0000000..f3f4ece Binary files /dev/null and b/thumbs/container_007_offen1.png differ diff --git a/thumbs/container_007_offen1.png.import b/thumbs/container_007_offen1.png.import new file mode 100644 index 0000000..ea1b205 --- /dev/null +++ b/thumbs/container_007_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://denqy1bllro0l" +path="res://.godot/imported/container_007_offen1.png-3331b42973d503316069c3bfd56728ea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_007_offen1.png" +dest_files=["res://.godot/imported/container_007_offen1.png-3331b42973d503316069c3bfd56728ea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_007_offen2.png b/thumbs/container_007_offen2.png new file mode 100644 index 0000000..a3c4733 Binary files /dev/null and b/thumbs/container_007_offen2.png differ diff --git a/thumbs/container_007_offen2.png.import b/thumbs/container_007_offen2.png.import new file mode 100644 index 0000000..7ecab66 --- /dev/null +++ b/thumbs/container_007_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bqkr34vaxvy5c" +path="res://.godot/imported/container_007_offen2.png-1f3964f3eeed540daff6df248b5c78bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_007_offen2.png" +dest_files=["res://.godot/imported/container_007_offen2.png-1f3964f3eeed540daff6df248b5c78bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_007_ohne.png b/thumbs/container_007_ohne.png new file mode 100644 index 0000000..1d87d85 Binary files /dev/null and b/thumbs/container_007_ohne.png differ diff --git a/thumbs/container_007_ohne.png.import b/thumbs/container_007_ohne.png.import new file mode 100644 index 0000000..3531720 --- /dev/null +++ b/thumbs/container_007_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bqm02u41bb737" +path="res://.godot/imported/container_007_ohne.png-eccd26afa9b16068d6d18f951b4b4e6e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_007_ohne.png" +dest_files=["res://.godot/imported/container_007_ohne.png-eccd26afa9b16068d6d18f951b4b4e6e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_008.png b/thumbs/container_008.png new file mode 100644 index 0000000..489e5d0 Binary files /dev/null and b/thumbs/container_008.png differ diff --git a/thumbs/container_008.png.import b/thumbs/container_008.png.import new file mode 100644 index 0000000..e5074c8 --- /dev/null +++ b/thumbs/container_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsncqq2cfvjo4" +path="res://.godot/imported/container_008.png-7a00ce93e7ed35645a1ab53ce2d210ee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_008.png" +dest_files=["res://.godot/imported/container_008.png-7a00ce93e7ed35645a1ab53ce2d210ee.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_008_offen1.png b/thumbs/container_008_offen1.png new file mode 100644 index 0000000..31d97f5 Binary files /dev/null and b/thumbs/container_008_offen1.png differ diff --git a/thumbs/container_008_offen1.png.import b/thumbs/container_008_offen1.png.import new file mode 100644 index 0000000..3ecc20c --- /dev/null +++ b/thumbs/container_008_offen1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbvq06me7e21h" +path="res://.godot/imported/container_008_offen1.png-629fd576afde45845550af64af9462bd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_008_offen1.png" +dest_files=["res://.godot/imported/container_008_offen1.png-629fd576afde45845550af64af9462bd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_008_offen2.png b/thumbs/container_008_offen2.png new file mode 100644 index 0000000..5d55a85 Binary files /dev/null and b/thumbs/container_008_offen2.png differ diff --git a/thumbs/container_008_offen2.png.import b/thumbs/container_008_offen2.png.import new file mode 100644 index 0000000..48a5eb6 --- /dev/null +++ b/thumbs/container_008_offen2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i6qdinbukrrw" +path="res://.godot/imported/container_008_offen2.png-ca7da3785762713cdd1c73212d399e12.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_008_offen2.png" +dest_files=["res://.godot/imported/container_008_offen2.png-ca7da3785762713cdd1c73212d399e12.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/container_008_ohne.png b/thumbs/container_008_ohne.png new file mode 100644 index 0000000..d9d4ce8 Binary files /dev/null and b/thumbs/container_008_ohne.png differ diff --git a/thumbs/container_008_ohne.png.import b/thumbs/container_008_ohne.png.import new file mode 100644 index 0000000..711f9de --- /dev/null +++ b/thumbs/container_008_ohne.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://yrbfa8pcdvlh" +path="res://.godot/imported/container_008_ohne.png-fc59b39356dd3dcd19f1894441dfc381.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/container_008_ohne.png" +dest_files=["res://.godot/imported/container_008_ohne.png-fc59b39356dd3dcd19f1894441dfc381.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_001.png b/thumbs/deadwood_001.png new file mode 100644 index 0000000..dc27646 Binary files /dev/null and b/thumbs/deadwood_001.png differ diff --git a/thumbs/deadwood_001.png.import b/thumbs/deadwood_001.png.import new file mode 100644 index 0000000..6f2967b --- /dev/null +++ b/thumbs/deadwood_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brb6m7wgu8dts" +path="res://.godot/imported/deadwood_001.png-5f57701ccdcad85386aff399fa67c0ff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_001.png" +dest_files=["res://.godot/imported/deadwood_001.png-5f57701ccdcad85386aff399fa67c0ff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_002.png b/thumbs/deadwood_002.png new file mode 100644 index 0000000..fb3a0e7 Binary files /dev/null and b/thumbs/deadwood_002.png differ diff --git a/thumbs/deadwood_002.png.import b/thumbs/deadwood_002.png.import new file mode 100644 index 0000000..b8aec05 --- /dev/null +++ b/thumbs/deadwood_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3sqh234ydke8" +path="res://.godot/imported/deadwood_002.png-3979048ae050bb6eac02d14d423930dd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_002.png" +dest_files=["res://.godot/imported/deadwood_002.png-3979048ae050bb6eac02d14d423930dd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_003.png b/thumbs/deadwood_003.png new file mode 100644 index 0000000..1acd41a Binary files /dev/null and b/thumbs/deadwood_003.png differ diff --git a/thumbs/deadwood_003.png.import b/thumbs/deadwood_003.png.import new file mode 100644 index 0000000..09b96d2 --- /dev/null +++ b/thumbs/deadwood_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x57sc4my1wy2" +path="res://.godot/imported/deadwood_003.png-94304a86c685da05021e13a1ca9f777d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_003.png" +dest_files=["res://.godot/imported/deadwood_003.png-94304a86c685da05021e13a1ca9f777d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_004.png b/thumbs/deadwood_004.png new file mode 100644 index 0000000..ae2bb48 Binary files /dev/null and b/thumbs/deadwood_004.png differ diff --git a/thumbs/deadwood_004.png.import b/thumbs/deadwood_004.png.import new file mode 100644 index 0000000..d251018 --- /dev/null +++ b/thumbs/deadwood_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duw8nsvr05otv" +path="res://.godot/imported/deadwood_004.png-051cd2b125708f7277cd5d8ffaeb2234.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_004.png" +dest_files=["res://.godot/imported/deadwood_004.png-051cd2b125708f7277cd5d8ffaeb2234.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_005.png b/thumbs/deadwood_005.png new file mode 100644 index 0000000..814e2a0 Binary files /dev/null and b/thumbs/deadwood_005.png differ diff --git a/thumbs/deadwood_005.png.import b/thumbs/deadwood_005.png.import new file mode 100644 index 0000000..a0b4146 --- /dev/null +++ b/thumbs/deadwood_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://coiwt4gpm0y3m" +path="res://.godot/imported/deadwood_005.png-cae213d4f55340af24486a42cbe391df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_005.png" +dest_files=["res://.godot/imported/deadwood_005.png-cae213d4f55340af24486a42cbe391df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_006.png b/thumbs/deadwood_006.png new file mode 100644 index 0000000..031293b Binary files /dev/null and b/thumbs/deadwood_006.png differ diff --git a/thumbs/deadwood_006.png.import b/thumbs/deadwood_006.png.import new file mode 100644 index 0000000..d82cca0 --- /dev/null +++ b/thumbs/deadwood_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbl117rcr4wtl" +path="res://.godot/imported/deadwood_006.png-f7332dc02cae7eb7eec49bb04cf5ea58.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_006.png" +dest_files=["res://.godot/imported/deadwood_006.png-f7332dc02cae7eb7eec49bb04cf5ea58.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_007.png b/thumbs/deadwood_007.png new file mode 100644 index 0000000..b3cfb4b Binary files /dev/null and b/thumbs/deadwood_007.png differ diff --git a/thumbs/deadwood_007.png.import b/thumbs/deadwood_007.png.import new file mode 100644 index 0000000..aae557d --- /dev/null +++ b/thumbs/deadwood_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8epd0swcraef" +path="res://.godot/imported/deadwood_007.png-315f04368e8da80b3f3f4a0f45f4c0e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_007.png" +dest_files=["res://.godot/imported/deadwood_007.png-315f04368e8da80b3f3f4a0f45f4c0e2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_008.png b/thumbs/deadwood_008.png new file mode 100644 index 0000000..8b319a0 Binary files /dev/null and b/thumbs/deadwood_008.png differ diff --git a/thumbs/deadwood_008.png.import b/thumbs/deadwood_008.png.import new file mode 100644 index 0000000..7d0e6f9 --- /dev/null +++ b/thumbs/deadwood_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://h16yvecq54xv" +path="res://.godot/imported/deadwood_008.png-42710d1c92f9f0ef74976e4fbdd3bd4c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_008.png" +dest_files=["res://.godot/imported/deadwood_008.png-42710d1c92f9f0ef74976e4fbdd3bd4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_009.png b/thumbs/deadwood_009.png new file mode 100644 index 0000000..a7160c1 Binary files /dev/null and b/thumbs/deadwood_009.png differ diff --git a/thumbs/deadwood_009.png.import b/thumbs/deadwood_009.png.import new file mode 100644 index 0000000..a09e7fc --- /dev/null +++ b/thumbs/deadwood_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvh8c71joh4kt" +path="res://.godot/imported/deadwood_009.png-fa781055081084858eca43f617417669.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_009.png" +dest_files=["res://.godot/imported/deadwood_009.png-fa781055081084858eca43f617417669.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_010.png b/thumbs/deadwood_010.png new file mode 100644 index 0000000..fd49f52 Binary files /dev/null and b/thumbs/deadwood_010.png differ diff --git a/thumbs/deadwood_010.png.import b/thumbs/deadwood_010.png.import new file mode 100644 index 0000000..766e31b --- /dev/null +++ b/thumbs/deadwood_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3dd8hmm48f2u" +path="res://.godot/imported/deadwood_010.png-72c23938bc1a2081a750e7063b1757e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_010.png" +dest_files=["res://.godot/imported/deadwood_010.png-72c23938bc1a2081a750e7063b1757e2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_011.png b/thumbs/deadwood_011.png new file mode 100644 index 0000000..cf79597 Binary files /dev/null and b/thumbs/deadwood_011.png differ diff --git a/thumbs/deadwood_011.png.import b/thumbs/deadwood_011.png.import new file mode 100644 index 0000000..2254a93 --- /dev/null +++ b/thumbs/deadwood_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cpgudb46fonl6" +path="res://.godot/imported/deadwood_011.png-b18166c43739b7f1f61945edabb7a1c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_011.png" +dest_files=["res://.godot/imported/deadwood_011.png-b18166c43739b7f1f61945edabb7a1c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_012.png b/thumbs/deadwood_012.png new file mode 100644 index 0000000..11352a4 Binary files /dev/null and b/thumbs/deadwood_012.png differ diff --git a/thumbs/deadwood_012.png.import b/thumbs/deadwood_012.png.import new file mode 100644 index 0000000..ae4d501 --- /dev/null +++ b/thumbs/deadwood_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfj3l6ryvbifx" +path="res://.godot/imported/deadwood_012.png-1cbdbfc502eba2800bdc64dbf7b63ea8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_012.png" +dest_files=["res://.godot/imported/deadwood_012.png-1cbdbfc502eba2800bdc64dbf7b63ea8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_013.png b/thumbs/deadwood_013.png new file mode 100644 index 0000000..501cfbe Binary files /dev/null and b/thumbs/deadwood_013.png differ diff --git a/thumbs/deadwood_013.png.import b/thumbs/deadwood_013.png.import new file mode 100644 index 0000000..e03e80b --- /dev/null +++ b/thumbs/deadwood_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7efle2j6s4ap" +path="res://.godot/imported/deadwood_013.png-07bf7879adaad44641ee6cfc3dc156ab.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_013.png" +dest_files=["res://.godot/imported/deadwood_013.png-07bf7879adaad44641ee6cfc3dc156ab.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_016.png b/thumbs/deadwood_016.png new file mode 100644 index 0000000..bcf3558 Binary files /dev/null and b/thumbs/deadwood_016.png differ diff --git a/thumbs/deadwood_016.png.import b/thumbs/deadwood_016.png.import new file mode 100644 index 0000000..557cec2 --- /dev/null +++ b/thumbs/deadwood_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://db4vsy75vwufd" +path="res://.godot/imported/deadwood_016.png-33fdc1dc42e562482ef59569b7b2bafb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_016.png" +dest_files=["res://.godot/imported/deadwood_016.png-33fdc1dc42e562482ef59569b7b2bafb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/deadwood_023.png b/thumbs/deadwood_023.png new file mode 100644 index 0000000..6a462f9 Binary files /dev/null and b/thumbs/deadwood_023.png differ diff --git a/thumbs/deadwood_023.png.import b/thumbs/deadwood_023.png.import new file mode 100644 index 0000000..0db8945 --- /dev/null +++ b/thumbs/deadwood_023.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ee1xpayr4wed" +path="res://.godot/imported/deadwood_023.png-6e382e9b2f5daa1d46d7d80382677ccb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/deadwood_023.png" +dest_files=["res://.godot/imported/deadwood_023.png-6e382e9b2f5daa1d46d7d80382677ccb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_001.png b/thumbs/decal_001.png new file mode 100644 index 0000000..4d75b55 Binary files /dev/null and b/thumbs/decal_001.png differ diff --git a/thumbs/decal_001.png.import b/thumbs/decal_001.png.import new file mode 100644 index 0000000..18f0e3e --- /dev/null +++ b/thumbs/decal_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7lew1osrqr5r" +path="res://.godot/imported/decal_001.png-6162f15b97ebbaca81cb7f1dd267744d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_001.png" +dest_files=["res://.godot/imported/decal_001.png-6162f15b97ebbaca81cb7f1dd267744d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_002.png b/thumbs/decal_002.png new file mode 100644 index 0000000..68a4b05 Binary files /dev/null and b/thumbs/decal_002.png differ diff --git a/thumbs/decal_002.png.import b/thumbs/decal_002.png.import new file mode 100644 index 0000000..840f2c9 --- /dev/null +++ b/thumbs/decal_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xqi68v0ratl5" +path="res://.godot/imported/decal_002.png-b96fc226722ef0bdd1963fa42a78e2c5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_002.png" +dest_files=["res://.godot/imported/decal_002.png-b96fc226722ef0bdd1963fa42a78e2c5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_003.png b/thumbs/decal_003.png new file mode 100644 index 0000000..54f9682 Binary files /dev/null and b/thumbs/decal_003.png differ diff --git a/thumbs/decal_003.png.import b/thumbs/decal_003.png.import new file mode 100644 index 0000000..c0d2a8b --- /dev/null +++ b/thumbs/decal_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://pecgxgai7fs1" +path="res://.godot/imported/decal_003.png-20e90f039ee23ad30ea27b780e1a5b84.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_003.png" +dest_files=["res://.godot/imported/decal_003.png-20e90f039ee23ad30ea27b780e1a5b84.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_004.png b/thumbs/decal_004.png new file mode 100644 index 0000000..5ac3a01 Binary files /dev/null and b/thumbs/decal_004.png differ diff --git a/thumbs/decal_004.png.import b/thumbs/decal_004.png.import new file mode 100644 index 0000000..64f745d --- /dev/null +++ b/thumbs/decal_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hjsxl3jr1hl5" +path="res://.godot/imported/decal_004.png-719883b00d864e9963e1e4582f599604.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_004.png" +dest_files=["res://.godot/imported/decal_004.png-719883b00d864e9963e1e4582f599604.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_005.png b/thumbs/decal_005.png new file mode 100644 index 0000000..c288d78 Binary files /dev/null and b/thumbs/decal_005.png differ diff --git a/thumbs/decal_005.png.import b/thumbs/decal_005.png.import new file mode 100644 index 0000000..f6dd2f0 --- /dev/null +++ b/thumbs/decal_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpycwrna8l1u" +path="res://.godot/imported/decal_005.png-3e61ef0680c830259c2cd5116c4e4a8b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_005.png" +dest_files=["res://.godot/imported/decal_005.png-3e61ef0680c830259c2cd5116c4e4a8b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_006.png b/thumbs/decal_006.png new file mode 100644 index 0000000..2957b45 Binary files /dev/null and b/thumbs/decal_006.png differ diff --git a/thumbs/decal_006.png.import b/thumbs/decal_006.png.import new file mode 100644 index 0000000..2e27048 --- /dev/null +++ b/thumbs/decal_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5vb3tdhktjcs" +path="res://.godot/imported/decal_006.png-14c41b129eb81c7a8b621c0fe9925278.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_006.png" +dest_files=["res://.godot/imported/decal_006.png-14c41b129eb81c7a8b621c0fe9925278.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_007.png b/thumbs/decal_007.png new file mode 100644 index 0000000..1d2c6bd Binary files /dev/null and b/thumbs/decal_007.png differ diff --git a/thumbs/decal_007.png.import b/thumbs/decal_007.png.import new file mode 100644 index 0000000..ab9fa1f --- /dev/null +++ b/thumbs/decal_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcjttf21fewn1" +path="res://.godot/imported/decal_007.png-af55afefddea6b5521a4c369f42af69a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_007.png" +dest_files=["res://.godot/imported/decal_007.png-af55afefddea6b5521a4c369f42af69a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_008.png b/thumbs/decal_008.png new file mode 100644 index 0000000..534e068 Binary files /dev/null and b/thumbs/decal_008.png differ diff --git a/thumbs/decal_008.png.import b/thumbs/decal_008.png.import new file mode 100644 index 0000000..9e43199 --- /dev/null +++ b/thumbs/decal_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kl1dc8qqllob" +path="res://.godot/imported/decal_008.png-fe6b54c54a37c254357269c55e6b00f1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_008.png" +dest_files=["res://.godot/imported/decal_008.png-fe6b54c54a37c254357269c55e6b00f1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_009.png b/thumbs/decal_009.png new file mode 100644 index 0000000..6394f5c Binary files /dev/null and b/thumbs/decal_009.png differ diff --git a/thumbs/decal_009.png.import b/thumbs/decal_009.png.import new file mode 100644 index 0000000..f39676c --- /dev/null +++ b/thumbs/decal_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1tg6k788p7u4" +path="res://.godot/imported/decal_009.png-dcc2cc26de568749e64d933fb4302763.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_009.png" +dest_files=["res://.godot/imported/decal_009.png-dcc2cc26de568749e64d933fb4302763.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_010.png b/thumbs/decal_010.png new file mode 100644 index 0000000..ea8a275 Binary files /dev/null and b/thumbs/decal_010.png differ diff --git a/thumbs/decal_010.png.import b/thumbs/decal_010.png.import new file mode 100644 index 0000000..a1f3890 --- /dev/null +++ b/thumbs/decal_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ce82gtlto5kyu" +path="res://.godot/imported/decal_010.png-e13daa97000b2f032366c5e357a66eef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_010.png" +dest_files=["res://.godot/imported/decal_010.png-e13daa97000b2f032366c5e357a66eef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_011.png b/thumbs/decal_011.png new file mode 100644 index 0000000..1622aed Binary files /dev/null and b/thumbs/decal_011.png differ diff --git a/thumbs/decal_011.png.import b/thumbs/decal_011.png.import new file mode 100644 index 0000000..59d0df4 --- /dev/null +++ b/thumbs/decal_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vx21uyfes5b5" +path="res://.godot/imported/decal_011.png-70022216753cd8167957d8ff9a091d58.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_011.png" +dest_files=["res://.godot/imported/decal_011.png-70022216753cd8167957d8ff9a091d58.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_012.png b/thumbs/decal_012.png new file mode 100644 index 0000000..11012bf Binary files /dev/null and b/thumbs/decal_012.png differ diff --git a/thumbs/decal_012.png.import b/thumbs/decal_012.png.import new file mode 100644 index 0000000..73c18ee --- /dev/null +++ b/thumbs/decal_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dut423cqtpll6" +path="res://.godot/imported/decal_012.png-69b42c3efe2a8b1f7c51f880f0fbbaaa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_012.png" +dest_files=["res://.godot/imported/decal_012.png-69b42c3efe2a8b1f7c51f880f0fbbaaa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_013.png b/thumbs/decal_013.png new file mode 100644 index 0000000..50f7f10 Binary files /dev/null and b/thumbs/decal_013.png differ diff --git a/thumbs/decal_013.png.import b/thumbs/decal_013.png.import new file mode 100644 index 0000000..5b81eff --- /dev/null +++ b/thumbs/decal_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://l8a5euv86nnp" +path="res://.godot/imported/decal_013.png-6aafbb5ecdd96f653be3cab886f866e9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_013.png" +dest_files=["res://.godot/imported/decal_013.png-6aafbb5ecdd96f653be3cab886f866e9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/decal_014.png b/thumbs/decal_014.png new file mode 100644 index 0000000..2326e96 Binary files /dev/null and b/thumbs/decal_014.png differ diff --git a/thumbs/decal_014.png.import b/thumbs/decal_014.png.import new file mode 100644 index 0000000..f5b374e --- /dev/null +++ b/thumbs/decal_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c28q30s5tbmpy" +path="res://.godot/imported/decal_014.png-333e47df5bfe4bf93289f00a529f89fc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/decal_014.png" +dest_files=["res://.godot/imported/decal_014.png-333e47df5bfe4bf93289f00a529f89fc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/door_001.png b/thumbs/door_001.png new file mode 100644 index 0000000..0e5bf19 Binary files /dev/null and b/thumbs/door_001.png differ diff --git a/thumbs/door_001.png.import b/thumbs/door_001.png.import new file mode 100644 index 0000000..0d87297 --- /dev/null +++ b/thumbs/door_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tr06b7hh5j2y" +path="res://.godot/imported/door_001.png-b40a1a9e719ddeeeea831462319e52bd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/door_001.png" +dest_files=["res://.godot/imported/door_001.png-b40a1a9e719ddeeeea831462319e52bd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/door_002.png b/thumbs/door_002.png new file mode 100644 index 0000000..73df013 Binary files /dev/null and b/thumbs/door_002.png differ diff --git a/thumbs/door_002.png.import b/thumbs/door_002.png.import new file mode 100644 index 0000000..4177627 --- /dev/null +++ b/thumbs/door_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwud8kkshrkff" +path="res://.godot/imported/door_002.png-902a396b8b34fb733dfd1ed0ecbce24f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/door_002.png" +dest_files=["res://.godot/imported/door_002.png-902a396b8b34fb733dfd1ed0ecbce24f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/door_003.png b/thumbs/door_003.png new file mode 100644 index 0000000..6b0ca0e Binary files /dev/null and b/thumbs/door_003.png differ diff --git a/thumbs/door_003.png.import b/thumbs/door_003.png.import new file mode 100644 index 0000000..1632acd --- /dev/null +++ b/thumbs/door_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqk4exg0hksc1" +path="res://.godot/imported/door_003.png-ae12d8e770739b27b6e7a23b1174af61.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/door_003.png" +dest_files=["res://.godot/imported/door_003.png-ae12d8e770739b27b6e7a23b1174af61.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/dune_010.png b/thumbs/dune_010.png new file mode 100644 index 0000000..7e09184 Binary files /dev/null and b/thumbs/dune_010.png differ diff --git a/thumbs/dune_010.png.import b/thumbs/dune_010.png.import new file mode 100644 index 0000000..95ee483 --- /dev/null +++ b/thumbs/dune_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://oavouedycf5f" +path="res://.godot/imported/dune_010.png-0471705a9009c1f7f1537980a543c25e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/dune_010.png" +dest_files=["res://.godot/imported/dune_010.png-0471705a9009c1f7f1537980a543c25e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fence_001.png b/thumbs/fence_001.png new file mode 100644 index 0000000..50a32dd Binary files /dev/null and b/thumbs/fence_001.png differ diff --git a/thumbs/fence_001.png.import b/thumbs/fence_001.png.import new file mode 100644 index 0000000..267f18b --- /dev/null +++ b/thumbs/fence_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpa7plvahnvhp" +path="res://.godot/imported/fence_001.png-931c7db4a24fccaf6da66e5283ce5edf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fence_001.png" +dest_files=["res://.godot/imported/fence_001.png-931c7db4a24fccaf6da66e5283ce5edf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fence_002.png b/thumbs/fence_002.png new file mode 100644 index 0000000..4ba98f0 Binary files /dev/null and b/thumbs/fence_002.png differ diff --git a/thumbs/fence_002.png.import b/thumbs/fence_002.png.import new file mode 100644 index 0000000..56edb13 --- /dev/null +++ b/thumbs/fence_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://42jlkxottfgg" +path="res://.godot/imported/fence_002.png-a432336c73af686d500561f6cb63f8bb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fence_002.png" +dest_files=["res://.godot/imported/fence_002.png-a432336c73af686d500561f6cb63f8bb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fighter_jet_001.png b/thumbs/fighter_jet_001.png new file mode 100644 index 0000000..af43bd5 Binary files /dev/null and b/thumbs/fighter_jet_001.png differ diff --git a/thumbs/fighter_jet_001.png.import b/thumbs/fighter_jet_001.png.import new file mode 100644 index 0000000..a386533 --- /dev/null +++ b/thumbs/fighter_jet_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1chndfgaoj8o" +path="res://.godot/imported/fighter_jet_001.png-9e81f8cdafc349e9361101c7f8b80f44.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fighter_jet_001.png" +dest_files=["res://.godot/imported/fighter_jet_001.png-9e81f8cdafc349e9361101c7f8b80f44.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fighter_jet_002.png b/thumbs/fighter_jet_002.png new file mode 100644 index 0000000..9a520df Binary files /dev/null and b/thumbs/fighter_jet_002.png differ diff --git a/thumbs/fighter_jet_002.png.import b/thumbs/fighter_jet_002.png.import new file mode 100644 index 0000000..be84268 --- /dev/null +++ b/thumbs/fighter_jet_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwliah1ow4gv3" +path="res://.godot/imported/fighter_jet_002.png-c5ed1d9ce1915e7b11b806778e10edf0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fighter_jet_002.png" +dest_files=["res://.godot/imported/fighter_jet_002.png-c5ed1d9ce1915e7b11b806778e10edf0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fighter_jet_003.png b/thumbs/fighter_jet_003.png new file mode 100644 index 0000000..33d24b2 Binary files /dev/null and b/thumbs/fighter_jet_003.png differ diff --git a/thumbs/fighter_jet_003.png.import b/thumbs/fighter_jet_003.png.import new file mode 100644 index 0000000..5dbd2f4 --- /dev/null +++ b/thumbs/fighter_jet_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqwfnx4vwnpvx" +path="res://.godot/imported/fighter_jet_003.png-3c62c09d40c113cc57fe4f393da8d3ea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fighter_jet_003.png" +dest_files=["res://.godot/imported/fighter_jet_003.png-3c62c09d40c113cc57fe4f393da8d3ea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/fighter_jet_004.png b/thumbs/fighter_jet_004.png new file mode 100644 index 0000000..d773f4b Binary files /dev/null and b/thumbs/fighter_jet_004.png differ diff --git a/thumbs/fighter_jet_004.png.import b/thumbs/fighter_jet_004.png.import new file mode 100644 index 0000000..14d50d9 --- /dev/null +++ b/thumbs/fighter_jet_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dj2kl8dhnogl" +path="res://.godot/imported/fighter_jet_004.png-aa28fed20712e015ec9f061d039a82e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/fighter_jet_004.png" +dest_files=["res://.godot/imported/fighter_jet_004.png-aa28fed20712e015ec9f061d039a82e2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/flashlight_001.png b/thumbs/flashlight_001.png new file mode 100644 index 0000000..49ec57f Binary files /dev/null and b/thumbs/flashlight_001.png differ diff --git a/thumbs/flashlight_001.png.import b/thumbs/flashlight_001.png.import new file mode 100644 index 0000000..3f0542e --- /dev/null +++ b/thumbs/flashlight_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfw2apb7qh6df" +path="res://.godot/imported/flashlight_001.png-9b3c3f286242326d5eddce8c7777380d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/flashlight_001.png" +dest_files=["res://.godot/imported/flashlight_001.png-9b3c3f286242326d5eddce8c7777380d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/flashlight_002.png b/thumbs/flashlight_002.png new file mode 100644 index 0000000..94f8f26 Binary files /dev/null and b/thumbs/flashlight_002.png differ diff --git a/thumbs/flashlight_002.png.import b/thumbs/flashlight_002.png.import new file mode 100644 index 0000000..97e944c --- /dev/null +++ b/thumbs/flashlight_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csf16sblar7h2" +path="res://.godot/imported/flashlight_002.png-ca4be632b113a37f341a7c269f1e5694.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/flashlight_002.png" +dest_files=["res://.godot/imported/flashlight_002.png-ca4be632b113a37f341a7c269f1e5694.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/generator_001.png b/thumbs/generator_001.png new file mode 100644 index 0000000..454c4c3 Binary files /dev/null and b/thumbs/generator_001.png differ diff --git a/thumbs/generator_001.png.import b/thumbs/generator_001.png.import new file mode 100644 index 0000000..d2d558a --- /dev/null +++ b/thumbs/generator_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhguehdkjm71i" +path="res://.godot/imported/generator_001.png-83c7d2c2e14c3d733653086ee836f133.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/generator_001.png" +dest_files=["res://.godot/imported/generator_001.png-83c7d2c2e14c3d733653086ee836f133.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/generator_002.png b/thumbs/generator_002.png new file mode 100644 index 0000000..a2e4499 Binary files /dev/null and b/thumbs/generator_002.png differ diff --git a/thumbs/generator_002.png.import b/thumbs/generator_002.png.import new file mode 100644 index 0000000..a5ec8bb --- /dev/null +++ b/thumbs/generator_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cc0fl2o0fwa1i" +path="res://.godot/imported/generator_002.png-284d38dfce3e6000fe10b79796703113.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/generator_002.png" +dest_files=["res://.godot/imported/generator_002.png-284d38dfce3e6000fe10b79796703113.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/generator_003.png b/thumbs/generator_003.png new file mode 100644 index 0000000..c5788a4 Binary files /dev/null and b/thumbs/generator_003.png differ diff --git a/thumbs/generator_003.png.import b/thumbs/generator_003.png.import new file mode 100644 index 0000000..e93ff4e --- /dev/null +++ b/thumbs/generator_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw0u2vv7sg4e8" +path="res://.godot/imported/generator_003.png-75a98f66e2528fcede4e417758a3cefe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/generator_003.png" +dest_files=["res://.godot/imported/generator_003.png-75a98f66e2528fcede4e417758a3cefe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/generator_004.png b/thumbs/generator_004.png new file mode 100644 index 0000000..283ee98 Binary files /dev/null and b/thumbs/generator_004.png differ diff --git a/thumbs/generator_004.png.import b/thumbs/generator_004.png.import new file mode 100644 index 0000000..c76f2da --- /dev/null +++ b/thumbs/generator_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0hd3u3xkdciu" +path="res://.godot/imported/generator_004.png-47c045ce3698c86d8e3fb05cbe0fbec7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/generator_004.png" +dest_files=["res://.godot/imported/generator_004.png-47c045ce3698c86d8e3fb05cbe0fbec7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_003.png b/thumbs/grass_003.png new file mode 100644 index 0000000..6fe4520 Binary files /dev/null and b/thumbs/grass_003.png differ diff --git a/thumbs/grass_003.png.import b/thumbs/grass_003.png.import new file mode 100644 index 0000000..1d8b82e --- /dev/null +++ b/thumbs/grass_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kxti6habu4h1" +path="res://.godot/imported/grass_003.png-dd9e3058853178b3320f9626b627a7bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_003.png" +dest_files=["res://.godot/imported/grass_003.png-dd9e3058853178b3320f9626b627a7bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_004.png b/thumbs/grass_004.png new file mode 100644 index 0000000..0975367 Binary files /dev/null and b/thumbs/grass_004.png differ diff --git a/thumbs/grass_004.png.import b/thumbs/grass_004.png.import new file mode 100644 index 0000000..268c80a --- /dev/null +++ b/thumbs/grass_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhxqqpaqt8wkl" +path="res://.godot/imported/grass_004.png-1838e0b0f4ccc06ef1191e3d49e84327.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_004.png" +dest_files=["res://.godot/imported/grass_004.png-1838e0b0f4ccc06ef1191e3d49e84327.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_005.png b/thumbs/grass_005.png new file mode 100644 index 0000000..8231384 Binary files /dev/null and b/thumbs/grass_005.png differ diff --git a/thumbs/grass_005.png.import b/thumbs/grass_005.png.import new file mode 100644 index 0000000..3d040b0 --- /dev/null +++ b/thumbs/grass_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djbf1xwlqph60" +path="res://.godot/imported/grass_005.png-7734fac4b9065208e085196453fdd5e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_005.png" +dest_files=["res://.godot/imported/grass_005.png-7734fac4b9065208e085196453fdd5e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_006.png b/thumbs/grass_006.png new file mode 100644 index 0000000..4c9824b Binary files /dev/null and b/thumbs/grass_006.png differ diff --git a/thumbs/grass_006.png.import b/thumbs/grass_006.png.import new file mode 100644 index 0000000..dfa2f8d --- /dev/null +++ b/thumbs/grass_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dummnwoaakbi8" +path="res://.godot/imported/grass_006.png-af575f5f82692bccbff02b47aefd4f30.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_006.png" +dest_files=["res://.godot/imported/grass_006.png-af575f5f82692bccbff02b47aefd4f30.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_007.png b/thumbs/grass_007.png new file mode 100644 index 0000000..5b7c42f Binary files /dev/null and b/thumbs/grass_007.png differ diff --git a/thumbs/grass_007.png.import b/thumbs/grass_007.png.import new file mode 100644 index 0000000..5ae7eba --- /dev/null +++ b/thumbs/grass_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqsb1237co8jt" +path="res://.godot/imported/grass_007.png-ee8303a8e0ab4f7ece24904942fc4757.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_007.png" +dest_files=["res://.godot/imported/grass_007.png-ee8303a8e0ab4f7ece24904942fc4757.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/grass_008.png b/thumbs/grass_008.png new file mode 100644 index 0000000..607a0b3 Binary files /dev/null and b/thumbs/grass_008.png differ diff --git a/thumbs/grass_008.png.import b/thumbs/grass_008.png.import new file mode 100644 index 0000000..5f87aa8 --- /dev/null +++ b/thumbs/grass_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7x1l2afmoqop" +path="res://.godot/imported/grass_008.png-8dec7745258fff96f271aa748e125227.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/grass_008.png" +dest_files=["res://.godot/imported/grass_008.png-8dec7745258fff96f271aa748e125227.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_001.png b/thumbs/ground_001.png new file mode 100644 index 0000000..c139b2a Binary files /dev/null and b/thumbs/ground_001.png differ diff --git a/thumbs/ground_001.png.import b/thumbs/ground_001.png.import new file mode 100644 index 0000000..ae9ce31 --- /dev/null +++ b/thumbs/ground_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cpouetex3y7tw" +path="res://.godot/imported/ground_001.png-36bdb0769403daa45f342b9341063750.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_001.png" +dest_files=["res://.godot/imported/ground_001.png-36bdb0769403daa45f342b9341063750.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_002.png b/thumbs/ground_002.png new file mode 100644 index 0000000..c60d283 Binary files /dev/null and b/thumbs/ground_002.png differ diff --git a/thumbs/ground_002.png.import b/thumbs/ground_002.png.import new file mode 100644 index 0000000..8ef2675 --- /dev/null +++ b/thumbs/ground_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqrbrema1q5ip" +path="res://.godot/imported/ground_002.png-fed7b7cfb80eab63aa06512485d36475.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_002.png" +dest_files=["res://.godot/imported/ground_002.png-fed7b7cfb80eab63aa06512485d36475.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_003.png b/thumbs/ground_003.png new file mode 100644 index 0000000..a20115d Binary files /dev/null and b/thumbs/ground_003.png differ diff --git a/thumbs/ground_003.png.import b/thumbs/ground_003.png.import new file mode 100644 index 0000000..a197223 --- /dev/null +++ b/thumbs/ground_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cafdetlj1g1q2" +path="res://.godot/imported/ground_003.png-2d516bf69e020009859cb4fede05df1e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_003.png" +dest_files=["res://.godot/imported/ground_003.png-2d516bf69e020009859cb4fede05df1e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_004.png b/thumbs/ground_004.png new file mode 100644 index 0000000..80a8ab4 Binary files /dev/null and b/thumbs/ground_004.png differ diff --git a/thumbs/ground_004.png.import b/thumbs/ground_004.png.import new file mode 100644 index 0000000..0af8d0b --- /dev/null +++ b/thumbs/ground_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b01gbyck4hmt" +path="res://.godot/imported/ground_004.png-14ea2eab0481f1bc094c09c11bb65eda.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_004.png" +dest_files=["res://.godot/imported/ground_004.png-14ea2eab0481f1bc094c09c11bb65eda.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_005.png b/thumbs/ground_005.png new file mode 100644 index 0000000..c1edc99 Binary files /dev/null and b/thumbs/ground_005.png differ diff --git a/thumbs/ground_005.png.import b/thumbs/ground_005.png.import new file mode 100644 index 0000000..b15bf3e --- /dev/null +++ b/thumbs/ground_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlctx83x50wq3" +path="res://.godot/imported/ground_005.png-c367b726e031a0344deeb5ee10e5748e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_005.png" +dest_files=["res://.godot/imported/ground_005.png-c367b726e031a0344deeb5ee10e5748e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_006.png b/thumbs/ground_006.png new file mode 100644 index 0000000..6e4cdb0 Binary files /dev/null and b/thumbs/ground_006.png differ diff --git a/thumbs/ground_006.png.import b/thumbs/ground_006.png.import new file mode 100644 index 0000000..e525edc --- /dev/null +++ b/thumbs/ground_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbxb1j8iwbfp3" +path="res://.godot/imported/ground_006.png-296a5752b59f8b2798876b0c1c84008d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_006.png" +dest_files=["res://.godot/imported/ground_006.png-296a5752b59f8b2798876b0c1c84008d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_007.png b/thumbs/ground_007.png new file mode 100644 index 0000000..e0f77af Binary files /dev/null and b/thumbs/ground_007.png differ diff --git a/thumbs/ground_007.png.import b/thumbs/ground_007.png.import new file mode 100644 index 0000000..1a04891 --- /dev/null +++ b/thumbs/ground_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://mq84f6isca1m" +path="res://.godot/imported/ground_007.png-b4aa2a9b99302d2660cc451ea22c7083.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_007.png" +dest_files=["res://.godot/imported/ground_007.png-b4aa2a9b99302d2660cc451ea22c7083.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_008.png b/thumbs/ground_008.png new file mode 100644 index 0000000..5338900 Binary files /dev/null and b/thumbs/ground_008.png differ diff --git a/thumbs/ground_008.png.import b/thumbs/ground_008.png.import new file mode 100644 index 0000000..7ebb9d4 --- /dev/null +++ b/thumbs/ground_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8g4tmxhckwix" +path="res://.godot/imported/ground_008.png-e24c35cf88870852ee664b4a1035f136.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_008.png" +dest_files=["res://.godot/imported/ground_008.png-e24c35cf88870852ee664b4a1035f136.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ground_009.png b/thumbs/ground_009.png new file mode 100644 index 0000000..fc96d27 Binary files /dev/null and b/thumbs/ground_009.png differ diff --git a/thumbs/ground_009.png.import b/thumbs/ground_009.png.import new file mode 100644 index 0000000..8308ffe --- /dev/null +++ b/thumbs/ground_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqxjluesevpgd" +path="res://.godot/imported/ground_009.png-1676c589e2c36b544a562c97fb536e07.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ground_009.png" +dest_files=["res://.godot/imported/ground_009.png-1676c589e2c36b544a562c97fb536e07.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/gsm_001.png b/thumbs/gsm_001.png new file mode 100644 index 0000000..467f9d3 Binary files /dev/null and b/thumbs/gsm_001.png differ diff --git a/thumbs/gsm_001.png.import b/thumbs/gsm_001.png.import new file mode 100644 index 0000000..a95182b --- /dev/null +++ b/thumbs/gsm_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://js75fxtglx73" +path="res://.godot/imported/gsm_001.png-992352df17ac337ad1b53ec3a6b03556.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/gsm_001.png" +dest_files=["res://.godot/imported/gsm_001.png-992352df17ac337ad1b53ec3a6b03556.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_001.png b/thumbs/hangar_001.png new file mode 100644 index 0000000..3aa1b78 Binary files /dev/null and b/thumbs/hangar_001.png differ diff --git a/thumbs/hangar_001.png.import b/thumbs/hangar_001.png.import new file mode 100644 index 0000000..b852829 --- /dev/null +++ b/thumbs/hangar_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://oxdxsm1ga37" +path="res://.godot/imported/hangar_001.png-ab985da002094159466b17196f4b5371.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_001.png" +dest_files=["res://.godot/imported/hangar_001.png-ab985da002094159466b17196f4b5371.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_002.png b/thumbs/hangar_002.png new file mode 100644 index 0000000..3d2dfe1 Binary files /dev/null and b/thumbs/hangar_002.png differ diff --git a/thumbs/hangar_002.png.import b/thumbs/hangar_002.png.import new file mode 100644 index 0000000..deee225 --- /dev/null +++ b/thumbs/hangar_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhmgksiexoq4n" +path="res://.godot/imported/hangar_002.png-37f8a9365073a4a37192eafdcda29ceb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_002.png" +dest_files=["res://.godot/imported/hangar_002.png-37f8a9365073a4a37192eafdcda29ceb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_003.png b/thumbs/hangar_003.png new file mode 100644 index 0000000..6d330e8 Binary files /dev/null and b/thumbs/hangar_003.png differ diff --git a/thumbs/hangar_003.png.import b/thumbs/hangar_003.png.import new file mode 100644 index 0000000..cd037a8 --- /dev/null +++ b/thumbs/hangar_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bytr0yfoa1ff2" +path="res://.godot/imported/hangar_003.png-bb79f7b35e8ce29cadce98717c9e7293.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_003.png" +dest_files=["res://.godot/imported/hangar_003.png-bb79f7b35e8ce29cadce98717c9e7293.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_004.png b/thumbs/hangar_004.png new file mode 100644 index 0000000..bd1ad6e Binary files /dev/null and b/thumbs/hangar_004.png differ diff --git a/thumbs/hangar_004.png.import b/thumbs/hangar_004.png.import new file mode 100644 index 0000000..d788de7 --- /dev/null +++ b/thumbs/hangar_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crfn6k8kq7skr" +path="res://.godot/imported/hangar_004.png-80f63e15d6ab45e7f48be06e5f8f3810.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_004.png" +dest_files=["res://.godot/imported/hangar_004.png-80f63e15d6ab45e7f48be06e5f8f3810.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_005.png b/thumbs/hangar_005.png new file mode 100644 index 0000000..e205479 Binary files /dev/null and b/thumbs/hangar_005.png differ diff --git a/thumbs/hangar_005.png.import b/thumbs/hangar_005.png.import new file mode 100644 index 0000000..fa34950 --- /dev/null +++ b/thumbs/hangar_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn2gr2b7332vh" +path="res://.godot/imported/hangar_005.png-71cd601620672c7569500ba92449d507.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_005.png" +dest_files=["res://.godot/imported/hangar_005.png-71cd601620672c7569500ba92449d507.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_006.png b/thumbs/hangar_006.png new file mode 100644 index 0000000..9827602 Binary files /dev/null and b/thumbs/hangar_006.png differ diff --git a/thumbs/hangar_006.png.import b/thumbs/hangar_006.png.import new file mode 100644 index 0000000..ab5344f --- /dev/null +++ b/thumbs/hangar_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://de7pl4g7cdwan" +path="res://.godot/imported/hangar_006.png-8c6572fc592ceee43a4df696e634fabc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_006.png" +dest_files=["res://.godot/imported/hangar_006.png-8c6572fc592ceee43a4df696e634fabc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_007.png b/thumbs/hangar_007.png new file mode 100644 index 0000000..8b1c2c8 Binary files /dev/null and b/thumbs/hangar_007.png differ diff --git a/thumbs/hangar_007.png.import b/thumbs/hangar_007.png.import new file mode 100644 index 0000000..33d441d --- /dev/null +++ b/thumbs/hangar_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://30hgrq6alxw4" +path="res://.godot/imported/hangar_007.png-e15e2c777eb2524586f0558b331bf5f8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_007.png" +dest_files=["res://.godot/imported/hangar_007.png-e15e2c777eb2524586f0558b331bf5f8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_008.png b/thumbs/hangar_008.png new file mode 100644 index 0000000..3cc281e Binary files /dev/null and b/thumbs/hangar_008.png differ diff --git a/thumbs/hangar_008.png.import b/thumbs/hangar_008.png.import new file mode 100644 index 0000000..879ad8f --- /dev/null +++ b/thumbs/hangar_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccrcd7yexl8h5" +path="res://.godot/imported/hangar_008.png-a557315a61a04a2c79315d091959d139.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_008.png" +dest_files=["res://.godot/imported/hangar_008.png-a557315a61a04a2c79315d091959d139.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_door_001.png b/thumbs/hangar_door_001.png new file mode 100644 index 0000000..a01a4f4 Binary files /dev/null and b/thumbs/hangar_door_001.png differ diff --git a/thumbs/hangar_door_001.png.import b/thumbs/hangar_door_001.png.import new file mode 100644 index 0000000..c0a6a11 --- /dev/null +++ b/thumbs/hangar_door_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xxw5jfn6q3nq" +path="res://.godot/imported/hangar_door_001.png-e71769a2a0b154146d320b5dfd70e857.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_door_001.png" +dest_files=["res://.godot/imported/hangar_door_001.png-e71769a2a0b154146d320b5dfd70e857.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_door_002.png b/thumbs/hangar_door_002.png new file mode 100644 index 0000000..95a4c10 Binary files /dev/null and b/thumbs/hangar_door_002.png differ diff --git a/thumbs/hangar_door_002.png.import b/thumbs/hangar_door_002.png.import new file mode 100644 index 0000000..96da55b --- /dev/null +++ b/thumbs/hangar_door_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0f782ov24am3" +path="res://.godot/imported/hangar_door_002.png-24fd067cedcacb5419145f7fb65d81e4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_door_002.png" +dest_files=["res://.godot/imported/hangar_door_002.png-24fd067cedcacb5419145f7fb65d81e4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hangar_door_003.png b/thumbs/hangar_door_003.png new file mode 100644 index 0000000..4d2a911 Binary files /dev/null and b/thumbs/hangar_door_003.png differ diff --git a/thumbs/hangar_door_003.png.import b/thumbs/hangar_door_003.png.import new file mode 100644 index 0000000..b04d8c6 --- /dev/null +++ b/thumbs/hangar_door_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bh6py8abiecad" +path="res://.godot/imported/hangar_door_003.png-42145e18771f39cf8aabe98c96c39ad6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hangar_door_003.png" +dest_files=["res://.godot/imported/hangar_door_003.png-42145e18771f39cf8aabe98c96c39ad6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hedgehog_001.png b/thumbs/hedgehog_001.png new file mode 100644 index 0000000..3c7048c Binary files /dev/null and b/thumbs/hedgehog_001.png differ diff --git a/thumbs/hedgehog_001.png.import b/thumbs/hedgehog_001.png.import new file mode 100644 index 0000000..947570a --- /dev/null +++ b/thumbs/hedgehog_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqn1wnj0csnx" +path="res://.godot/imported/hedgehog_001.png-3ca856ffb8102c1e40ef57c2ab1f9a5a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hedgehog_001.png" +dest_files=["res://.godot/imported/hedgehog_001.png-3ca856ffb8102c1e40ef57c2ab1f9a5a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/helicopter_001.png b/thumbs/helicopter_001.png new file mode 100644 index 0000000..782028d Binary files /dev/null and b/thumbs/helicopter_001.png differ diff --git a/thumbs/helicopter_001.png.import b/thumbs/helicopter_001.png.import new file mode 100644 index 0000000..55b8c8e --- /dev/null +++ b/thumbs/helicopter_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3k1ayhalsfnj" +path="res://.godot/imported/helicopter_001.png-16e63a120eac2446266d4bf8bad5cd64.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/helicopter_001.png" +dest_files=["res://.godot/imported/helicopter_001.png-16e63a120eac2446266d4bf8bad5cd64.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/helicopter_platform_001.png b/thumbs/helicopter_platform_001.png new file mode 100644 index 0000000..55d1658 Binary files /dev/null and b/thumbs/helicopter_platform_001.png differ diff --git a/thumbs/helicopter_platform_001.png.import b/thumbs/helicopter_platform_001.png.import new file mode 100644 index 0000000..06e9c01 --- /dev/null +++ b/thumbs/helicopter_platform_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7ujf1j8tds8i" +path="res://.godot/imported/helicopter_platform_001.png-401551cbfe51fa1ce5357b21d39af14c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/helicopter_platform_001.png" +dest_files=["res://.godot/imported/helicopter_platform_001.png-401551cbfe51fa1ce5357b21d39af14c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hummer_001.png b/thumbs/hummer_001.png new file mode 100644 index 0000000..b6bc800 Binary files /dev/null and b/thumbs/hummer_001.png differ diff --git a/thumbs/hummer_001.png.import b/thumbs/hummer_001.png.import new file mode 100644 index 0000000..fcb331a --- /dev/null +++ b/thumbs/hummer_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2uartd0dn3iu" +path="res://.godot/imported/hummer_001.png-be9ca8e497ec57595d0c647cabaadf68.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hummer_001.png" +dest_files=["res://.godot/imported/hummer_001.png-be9ca8e497ec57595d0c647cabaadf68.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hummer_002.png b/thumbs/hummer_002.png new file mode 100644 index 0000000..6789ed2 Binary files /dev/null and b/thumbs/hummer_002.png differ diff --git a/thumbs/hummer_002.png.import b/thumbs/hummer_002.png.import new file mode 100644 index 0000000..6015533 --- /dev/null +++ b/thumbs/hummer_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bur6cisexpp2k" +path="res://.godot/imported/hummer_002.png-a4841a7fee62dc784b4b9fa231c9201f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hummer_002.png" +dest_files=["res://.godot/imported/hummer_002.png-a4841a7fee62dc784b4b9fa231c9201f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hummer_003.png b/thumbs/hummer_003.png new file mode 100644 index 0000000..ded3669 Binary files /dev/null and b/thumbs/hummer_003.png differ diff --git a/thumbs/hummer_003.png.import b/thumbs/hummer_003.png.import new file mode 100644 index 0000000..a06785b --- /dev/null +++ b/thumbs/hummer_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dx5g2bynue8ks" +path="res://.godot/imported/hummer_003.png-d545e6d04af3c132a995e7ca1bfee24c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hummer_003.png" +dest_files=["res://.godot/imported/hummer_003.png-d545e6d04af3c132a995e7ca1bfee24c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/hummer_004.png b/thumbs/hummer_004.png new file mode 100644 index 0000000..37979fc Binary files /dev/null and b/thumbs/hummer_004.png differ diff --git a/thumbs/hummer_004.png.import b/thumbs/hummer_004.png.import new file mode 100644 index 0000000..5110f4d --- /dev/null +++ b/thumbs/hummer_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3w8lpx2muutn" +path="res://.godot/imported/hummer_004.png-11471605f359a4f101e78ff34b5e7f10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/hummer_004.png" +dest_files=["res://.godot/imported/hummer_004.png-11471605f359a4f101e78ff34b5e7f10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ladder_001.png b/thumbs/ladder_001.png new file mode 100644 index 0000000..75a22d4 Binary files /dev/null and b/thumbs/ladder_001.png differ diff --git a/thumbs/ladder_001.png.import b/thumbs/ladder_001.png.import new file mode 100644 index 0000000..6ed5265 --- /dev/null +++ b/thumbs/ladder_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnyw2hcwnplu1" +path="res://.godot/imported/ladder_001.png-3cb6cf8bf32c42476432c3538826b9a5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ladder_001.png" +dest_files=["res://.godot/imported/ladder_001.png-3cb6cf8bf32c42476432c3538826b9a5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ladder_002.png b/thumbs/ladder_002.png new file mode 100644 index 0000000..293bbba Binary files /dev/null and b/thumbs/ladder_002.png differ diff --git a/thumbs/ladder_002.png.import b/thumbs/ladder_002.png.import new file mode 100644 index 0000000..8ad723d --- /dev/null +++ b/thumbs/ladder_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnntdaecyuisd" +path="res://.godot/imported/ladder_002.png-824513e0f517c632d1b3283c1469eb90.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ladder_002.png" +dest_files=["res://.godot/imported/ladder_002.png-824513e0f517c632d1b3283c1469eb90.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/ladder_003.png b/thumbs/ladder_003.png new file mode 100644 index 0000000..4162fd9 Binary files /dev/null and b/thumbs/ladder_003.png differ diff --git a/thumbs/ladder_003.png.import b/thumbs/ladder_003.png.import new file mode 100644 index 0000000..0014ea1 --- /dev/null +++ b/thumbs/ladder_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpmrqkuydjhd6" +path="res://.godot/imported/ladder_003.png-004147375948a2ea52f05ea572f1e28c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/ladder_003.png" +dest_files=["res://.godot/imported/ladder_003.png-004147375948a2ea52f05ea572f1e28c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/lamp_001.png b/thumbs/lamp_001.png new file mode 100644 index 0000000..1356f8e Binary files /dev/null and b/thumbs/lamp_001.png differ diff --git a/thumbs/lamp_001.png.import b/thumbs/lamp_001.png.import new file mode 100644 index 0000000..d373744 --- /dev/null +++ b/thumbs/lamp_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpkh0ald131kr" +path="res://.godot/imported/lamp_001.png-7f97d6e134f3cb6e9a7021e0cbe3f125.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/lamp_001.png" +dest_files=["res://.godot/imported/lamp_001.png-7f97d6e134f3cb6e9a7021e0cbe3f125.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/log_001.png b/thumbs/log_001.png new file mode 100644 index 0000000..7ead406 Binary files /dev/null and b/thumbs/log_001.png differ diff --git a/thumbs/log_001.png.import b/thumbs/log_001.png.import new file mode 100644 index 0000000..886eb26 --- /dev/null +++ b/thumbs/log_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cae7hw0m4wqg1" +path="res://.godot/imported/log_001.png-8537d34cdf1193f0ad1131974d9f24ab.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/log_001.png" +dest_files=["res://.godot/imported/log_001.png-8537d34cdf1193f0ad1131974d9f24ab.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_001.png b/thumbs/machine_gun_001.png new file mode 100644 index 0000000..38673ab Binary files /dev/null and b/thumbs/machine_gun_001.png differ diff --git a/thumbs/machine_gun_001.png.import b/thumbs/machine_gun_001.png.import new file mode 100644 index 0000000..4f6eaa8 --- /dev/null +++ b/thumbs/machine_gun_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xh1oboycufg3" +path="res://.godot/imported/machine_gun_001.png-3ce29604df13beff873ead3da78037d8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_001.png" +dest_files=["res://.godot/imported/machine_gun_001.png-3ce29604df13beff873ead3da78037d8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_002.png b/thumbs/machine_gun_002.png new file mode 100644 index 0000000..8631c3b Binary files /dev/null and b/thumbs/machine_gun_002.png differ diff --git a/thumbs/machine_gun_002.png.import b/thumbs/machine_gun_002.png.import new file mode 100644 index 0000000..e373b63 --- /dev/null +++ b/thumbs/machine_gun_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1x5nyxrgi37a" +path="res://.godot/imported/machine_gun_002.png-52fc02dc38fdc59c768596d5a5d7c634.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_002.png" +dest_files=["res://.godot/imported/machine_gun_002.png-52fc02dc38fdc59c768596d5a5d7c634.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_003.png b/thumbs/machine_gun_003.png new file mode 100644 index 0000000..52d110d Binary files /dev/null and b/thumbs/machine_gun_003.png differ diff --git a/thumbs/machine_gun_003.png.import b/thumbs/machine_gun_003.png.import new file mode 100644 index 0000000..bb2cc06 --- /dev/null +++ b/thumbs/machine_gun_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cchq1yofpovw3" +path="res://.godot/imported/machine_gun_003.png-09b783a4b8995d7d7ee7c111125c18ca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_003.png" +dest_files=["res://.godot/imported/machine_gun_003.png-09b783a4b8995d7d7ee7c111125c18ca.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_003_schild.png b/thumbs/machine_gun_003_schild.png new file mode 100644 index 0000000..52d110d Binary files /dev/null and b/thumbs/machine_gun_003_schild.png differ diff --git a/thumbs/machine_gun_003_schild.png.import b/thumbs/machine_gun_003_schild.png.import new file mode 100644 index 0000000..16da2ba --- /dev/null +++ b/thumbs/machine_gun_003_schild.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jq8jyfhi8ebn" +path="res://.godot/imported/machine_gun_003_schild.png-62a0b422057611389b1f0becbb881926.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_003_schild.png" +dest_files=["res://.godot/imported/machine_gun_003_schild.png-62a0b422057611389b1f0becbb881926.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_004.png b/thumbs/machine_gun_004.png new file mode 100644 index 0000000..97ad936 Binary files /dev/null and b/thumbs/machine_gun_004.png differ diff --git a/thumbs/machine_gun_004.png.import b/thumbs/machine_gun_004.png.import new file mode 100644 index 0000000..9c5ff19 --- /dev/null +++ b/thumbs/machine_gun_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cc7gunrt8jror" +path="res://.godot/imported/machine_gun_004.png-3f688f149e9c692a0c743a6d07ee1b53.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_004.png" +dest_files=["res://.godot/imported/machine_gun_004.png-3f688f149e9c692a0c743a6d07ee1b53.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/machine_gun_005.png b/thumbs/machine_gun_005.png new file mode 100644 index 0000000..3de1977 Binary files /dev/null and b/thumbs/machine_gun_005.png differ diff --git a/thumbs/machine_gun_005.png.import b/thumbs/machine_gun_005.png.import new file mode 100644 index 0000000..7f3b94b --- /dev/null +++ b/thumbs/machine_gun_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d02ib6fweovil" +path="res://.godot/imported/machine_gun_005.png-64f096aba67ff13bcb64b3f8a324a070.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/machine_gun_005.png" +dest_files=["res://.godot/imported/machine_gun_005.png-64f096aba67ff13bcb64b3f8a324a070.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/military_transport_aircraft_001.png b/thumbs/military_transport_aircraft_001.png new file mode 100644 index 0000000..5eab53f Binary files /dev/null and b/thumbs/military_transport_aircraft_001.png differ diff --git a/thumbs/military_transport_aircraft_001.png.import b/thumbs/military_transport_aircraft_001.png.import new file mode 100644 index 0000000..a00b873 --- /dev/null +++ b/thumbs/military_transport_aircraft_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdsdyfdep87dm" +path="res://.godot/imported/military_transport_aircraft_001.png-87744eba4d595c5b08a6fabcbf7f2c29.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/military_transport_aircraft_001.png" +dest_files=["res://.godot/imported/military_transport_aircraft_001.png-87744eba4d595c5b08a6fabcbf7f2c29.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/mortar_001.png b/thumbs/mortar_001.png new file mode 100644 index 0000000..6f84f92 Binary files /dev/null and b/thumbs/mortar_001.png differ diff --git a/thumbs/mortar_001.png.import b/thumbs/mortar_001.png.import new file mode 100644 index 0000000..f3bdc44 --- /dev/null +++ b/thumbs/mortar_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://v84fuy7v5cs" +path="res://.godot/imported/mortar_001.png-75f4699e57c777c8fbc44a1ccff17099.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/mortar_001.png" +dest_files=["res://.godot/imported/mortar_001.png-75f4699e57c777c8fbc44a1ccff17099.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/observation_tower_001.png b/thumbs/observation_tower_001.png new file mode 100644 index 0000000..05a7066 Binary files /dev/null and b/thumbs/observation_tower_001.png differ diff --git a/thumbs/observation_tower_001.png.import b/thumbs/observation_tower_001.png.import new file mode 100644 index 0000000..b737675 --- /dev/null +++ b/thumbs/observation_tower_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbic4ojgqldwf" +path="res://.godot/imported/observation_tower_001.png-529d8b35c5019632efb69f29021beae5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/observation_tower_001.png" +dest_files=["res://.godot/imported/observation_tower_001.png-529d8b35c5019632efb69f29021beae5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/observation_tower_002.png b/thumbs/observation_tower_002.png new file mode 100644 index 0000000..ddd1fdc Binary files /dev/null and b/thumbs/observation_tower_002.png differ diff --git a/thumbs/observation_tower_002.png.import b/thumbs/observation_tower_002.png.import new file mode 100644 index 0000000..825395f --- /dev/null +++ b/thumbs/observation_tower_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chiebx5wma6r3" +path="res://.godot/imported/observation_tower_002.png-416887029d2c91090708fbc47968fe92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/observation_tower_002.png" +dest_files=["res://.godot/imported/observation_tower_002.png-416887029d2c91090708fbc47968fe92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_001.png b/thumbs/obstacle_001.png new file mode 100644 index 0000000..87c0b9b Binary files /dev/null and b/thumbs/obstacle_001.png differ diff --git a/thumbs/obstacle_001.png.import b/thumbs/obstacle_001.png.import new file mode 100644 index 0000000..aa2d4e8 --- /dev/null +++ b/thumbs/obstacle_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6d6aiegl1n0g" +path="res://.godot/imported/obstacle_001.png-003f01cd7547dd99ce5ff0586a20df6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_001.png" +dest_files=["res://.godot/imported/obstacle_001.png-003f01cd7547dd99ce5ff0586a20df6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_002.png b/thumbs/obstacle_002.png new file mode 100644 index 0000000..7cd7be6 Binary files /dev/null and b/thumbs/obstacle_002.png differ diff --git a/thumbs/obstacle_002.png.import b/thumbs/obstacle_002.png.import new file mode 100644 index 0000000..d4cf172 --- /dev/null +++ b/thumbs/obstacle_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bkg1ju0lxdf5g" +path="res://.godot/imported/obstacle_002.png-9829e66fb755cc762438d224507438e3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_002.png" +dest_files=["res://.godot/imported/obstacle_002.png-9829e66fb755cc762438d224507438e3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_003.png b/thumbs/obstacle_003.png new file mode 100644 index 0000000..6438a66 Binary files /dev/null and b/thumbs/obstacle_003.png differ diff --git a/thumbs/obstacle_003.png.import b/thumbs/obstacle_003.png.import new file mode 100644 index 0000000..053a12f --- /dev/null +++ b/thumbs/obstacle_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7tdyurwsi0uq" +path="res://.godot/imported/obstacle_003.png-207110653c8cd83481863f857de8ef63.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_003.png" +dest_files=["res://.godot/imported/obstacle_003.png-207110653c8cd83481863f857de8ef63.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_004.png b/thumbs/obstacle_004.png new file mode 100644 index 0000000..7b58aa8 Binary files /dev/null and b/thumbs/obstacle_004.png differ diff --git a/thumbs/obstacle_004.png.import b/thumbs/obstacle_004.png.import new file mode 100644 index 0000000..59506a9 --- /dev/null +++ b/thumbs/obstacle_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7vmv3keroksj" +path="res://.godot/imported/obstacle_004.png-4f979e09a2d428d6379508c24c5aa18f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_004.png" +dest_files=["res://.godot/imported/obstacle_004.png-4f979e09a2d428d6379508c24c5aa18f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_005.png b/thumbs/obstacle_005.png new file mode 100644 index 0000000..3c5f4aa Binary files /dev/null and b/thumbs/obstacle_005.png differ diff --git a/thumbs/obstacle_005.png.import b/thumbs/obstacle_005.png.import new file mode 100644 index 0000000..36ee0d2 --- /dev/null +++ b/thumbs/obstacle_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2nq5302is5nq" +path="res://.godot/imported/obstacle_005.png-efbbf9e7ecf79b200ff9c284af663b44.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_005.png" +dest_files=["res://.godot/imported/obstacle_005.png-efbbf9e7ecf79b200ff9c284af663b44.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_006.png b/thumbs/obstacle_006.png new file mode 100644 index 0000000..e008157 Binary files /dev/null and b/thumbs/obstacle_006.png differ diff --git a/thumbs/obstacle_006.png.import b/thumbs/obstacle_006.png.import new file mode 100644 index 0000000..764e82d --- /dev/null +++ b/thumbs/obstacle_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://daxxt4wxe0gdd" +path="res://.godot/imported/obstacle_006.png-1027d2a99d4c96df0caf0b63408456c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_006.png" +dest_files=["res://.godot/imported/obstacle_006.png-1027d2a99d4c96df0caf0b63408456c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_007.png b/thumbs/obstacle_007.png new file mode 100644 index 0000000..5231bdc Binary files /dev/null and b/thumbs/obstacle_007.png differ diff --git a/thumbs/obstacle_007.png.import b/thumbs/obstacle_007.png.import new file mode 100644 index 0000000..2983009 --- /dev/null +++ b/thumbs/obstacle_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fbb2wc4ueccy" +path="res://.godot/imported/obstacle_007.png-ddbceea5697f2664db0ba0d1e27ed39e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_007.png" +dest_files=["res://.godot/imported/obstacle_007.png-ddbceea5697f2664db0ba0d1e27ed39e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_008.png b/thumbs/obstacle_008.png new file mode 100644 index 0000000..facc3db Binary files /dev/null and b/thumbs/obstacle_008.png differ diff --git a/thumbs/obstacle_008.png.import b/thumbs/obstacle_008.png.import new file mode 100644 index 0000000..559966f --- /dev/null +++ b/thumbs/obstacle_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btgirrxjoluyw" +path="res://.godot/imported/obstacle_008.png-6dfc37fd72626beea44e2e5fcd06480c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_008.png" +dest_files=["res://.godot/imported/obstacle_008.png-6dfc37fd72626beea44e2e5fcd06480c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_009.png b/thumbs/obstacle_009.png new file mode 100644 index 0000000..ac7f59f Binary files /dev/null and b/thumbs/obstacle_009.png differ diff --git a/thumbs/obstacle_009.png.import b/thumbs/obstacle_009.png.import new file mode 100644 index 0000000..8b8195a --- /dev/null +++ b/thumbs/obstacle_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxhmucxkqh10m" +path="res://.godot/imported/obstacle_009.png-a837064651bc36e09af57a9798caafe7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_009.png" +dest_files=["res://.godot/imported/obstacle_009.png-a837064651bc36e09af57a9798caafe7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/obstacle_010.png b/thumbs/obstacle_010.png new file mode 100644 index 0000000..114b707 Binary files /dev/null and b/thumbs/obstacle_010.png differ diff --git a/thumbs/obstacle_010.png.import b/thumbs/obstacle_010.png.import new file mode 100644 index 0000000..4d12db9 --- /dev/null +++ b/thumbs/obstacle_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gv4u5ep8yxu4" +path="res://.godot/imported/obstacle_010.png-c436677281ec794b506a4719e679614a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/obstacle_010.png" +dest_files=["res://.godot/imported/obstacle_010.png-c436677281ec794b506a4719e679614a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pallet_001.png b/thumbs/pallet_001.png new file mode 100644 index 0000000..28ccb5e Binary files /dev/null and b/thumbs/pallet_001.png differ diff --git a/thumbs/pallet_001.png.import b/thumbs/pallet_001.png.import new file mode 100644 index 0000000..c8910e6 --- /dev/null +++ b/thumbs/pallet_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bb3f7drsrv3cn" +path="res://.godot/imported/pallet_001.png-7f254b4f097c02f78522930a0908f763.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pallet_001.png" +dest_files=["res://.godot/imported/pallet_001.png-7f254b4f097c02f78522930a0908f763.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pallet_002.png b/thumbs/pallet_002.png new file mode 100644 index 0000000..b64ed03 Binary files /dev/null and b/thumbs/pallet_002.png differ diff --git a/thumbs/pallet_002.png.import b/thumbs/pallet_002.png.import new file mode 100644 index 0000000..3013a5c --- /dev/null +++ b/thumbs/pallet_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdpjb7f7l0bj2" +path="res://.godot/imported/pallet_002.png-5441dc095cfac24679d9c7c4d6781000.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pallet_002.png" +dest_files=["res://.godot/imported/pallet_002.png-5441dc095cfac24679d9c7c4d6781000.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pillar_001.png b/thumbs/pillar_001.png new file mode 100644 index 0000000..db1fe9f Binary files /dev/null and b/thumbs/pillar_001.png differ diff --git a/thumbs/pillar_001.png.import b/thumbs/pillar_001.png.import new file mode 100644 index 0000000..fa64ccc --- /dev/null +++ b/thumbs/pillar_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gs5fj3y8oofo" +path="res://.godot/imported/pillar_001.png-eee810fc92313f7758f5db4dd5ba470c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pillar_001.png" +dest_files=["res://.godot/imported/pillar_001.png-eee810fc92313f7758f5db4dd5ba470c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pillar_002.png b/thumbs/pillar_002.png new file mode 100644 index 0000000..600c869 Binary files /dev/null and b/thumbs/pillar_002.png differ diff --git a/thumbs/pillar_002.png.import b/thumbs/pillar_002.png.import new file mode 100644 index 0000000..70f84fc --- /dev/null +++ b/thumbs/pillar_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cj2jr7ry6ndom" +path="res://.godot/imported/pillar_002.png-f7c9c4a8adc5eeb02d7e3240af93ed3b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pillar_002.png" +dest_files=["res://.godot/imported/pillar_002.png-f7c9c4a8adc5eeb02d7e3240af93ed3b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pillar_003.png b/thumbs/pillar_003.png new file mode 100644 index 0000000..3a6c1f7 Binary files /dev/null and b/thumbs/pillar_003.png differ diff --git a/thumbs/pillar_003.png.import b/thumbs/pillar_003.png.import new file mode 100644 index 0000000..b527dc3 --- /dev/null +++ b/thumbs/pillar_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xukr7eaglupr" +path="res://.godot/imported/pillar_003.png-b8155b45233456e99263ca255574d978.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pillar_003.png" +dest_files=["res://.godot/imported/pillar_003.png-b8155b45233456e99263ca255574d978.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pillar_004.png b/thumbs/pillar_004.png new file mode 100644 index 0000000..df4d143 Binary files /dev/null and b/thumbs/pillar_004.png differ diff --git a/thumbs/pillar_004.png.import b/thumbs/pillar_004.png.import new file mode 100644 index 0000000..cf3bb3a --- /dev/null +++ b/thumbs/pillar_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dccarrcra22u" +path="res://.godot/imported/pillar_004.png-419e90f974bc3099617e733d419c7dbd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pillar_004.png" +dest_files=["res://.godot/imported/pillar_004.png-419e90f974bc3099617e733d419c7dbd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/pipe_001.png b/thumbs/pipe_001.png new file mode 100644 index 0000000..e41b61c Binary files /dev/null and b/thumbs/pipe_001.png differ diff --git a/thumbs/pipe_001.png.import b/thumbs/pipe_001.png.import new file mode 100644 index 0000000..930730d --- /dev/null +++ b/thumbs/pipe_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wi4fn21t17gk" +path="res://.godot/imported/pipe_001.png-d22afa99d51c73b7992db1259c8ca40e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/pipe_001.png" +dest_files=["res://.godot/imported/pipe_001.png-d22afa99d51c73b7992db1259c8ca40e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protection_001.png b/thumbs/protection_001.png new file mode 100644 index 0000000..c68d089 Binary files /dev/null and b/thumbs/protection_001.png differ diff --git a/thumbs/protection_001.png.import b/thumbs/protection_001.png.import new file mode 100644 index 0000000..ee6271c --- /dev/null +++ b/thumbs/protection_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2i2bc1dee5v7" +path="res://.godot/imported/protection_001.png-73684cfb317b5961aea6d5b7bac41fee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protection_001.png" +dest_files=["res://.godot/imported/protection_001.png-73684cfb317b5961aea6d5b7bac41fee.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protection_002.png b/thumbs/protection_002.png new file mode 100644 index 0000000..60c7b1a Binary files /dev/null and b/thumbs/protection_002.png differ diff --git a/thumbs/protection_002.png.import b/thumbs/protection_002.png.import new file mode 100644 index 0000000..6a78e4d --- /dev/null +++ b/thumbs/protection_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cd0h47o72jyil" +path="res://.godot/imported/protection_002.png-1d3e2b0ed049157260f143b243b596a6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protection_002.png" +dest_files=["res://.godot/imported/protection_002.png-1d3e2b0ed049157260f143b243b596a6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protective_wall_001.png b/thumbs/protective_wall_001.png new file mode 100644 index 0000000..79172b0 Binary files /dev/null and b/thumbs/protective_wall_001.png differ diff --git a/thumbs/protective_wall_001.png.import b/thumbs/protective_wall_001.png.import new file mode 100644 index 0000000..e1ddcf2 --- /dev/null +++ b/thumbs/protective_wall_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c51jk5b2es66v" +path="res://.godot/imported/protective_wall_001.png-8384ef0b79cd9948050d1db59cf8c4b0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protective_wall_001.png" +dest_files=["res://.godot/imported/protective_wall_001.png-8384ef0b79cd9948050d1db59cf8c4b0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protective_wall_002.png b/thumbs/protective_wall_002.png new file mode 100644 index 0000000..350458a Binary files /dev/null and b/thumbs/protective_wall_002.png differ diff --git a/thumbs/protective_wall_002.png.import b/thumbs/protective_wall_002.png.import new file mode 100644 index 0000000..9c7110d --- /dev/null +++ b/thumbs/protective_wall_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0bsa7bh3dmv6" +path="res://.godot/imported/protective_wall_002.png-06691a1b6a59477b69096b5b170c5e78.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protective_wall_002.png" +dest_files=["res://.godot/imported/protective_wall_002.png-06691a1b6a59477b69096b5b170c5e78.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protective_wall_003.png b/thumbs/protective_wall_003.png new file mode 100644 index 0000000..f4d9a2a Binary files /dev/null and b/thumbs/protective_wall_003.png differ diff --git a/thumbs/protective_wall_003.png.import b/thumbs/protective_wall_003.png.import new file mode 100644 index 0000000..e4d6565 --- /dev/null +++ b/thumbs/protective_wall_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://34q8x6ynrerm" +path="res://.godot/imported/protective_wall_003.png-024c6e098654a4e2cc8b10709d12d6b7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protective_wall_003.png" +dest_files=["res://.godot/imported/protective_wall_003.png-024c6e098654a4e2cc8b10709d12d6b7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/protective_wall_004.png b/thumbs/protective_wall_004.png new file mode 100644 index 0000000..e4c2b2e Binary files /dev/null and b/thumbs/protective_wall_004.png differ diff --git a/thumbs/protective_wall_004.png.import b/thumbs/protective_wall_004.png.import new file mode 100644 index 0000000..85367ee --- /dev/null +++ b/thumbs/protective_wall_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvy0eb8ooaxss" +path="res://.godot/imported/protective_wall_004.png-99f9cb9362c0cfe09526dd9dbcf6ad1f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/protective_wall_004.png" +dest_files=["res://.godot/imported/protective_wall_004.png-99f9cb9362c0cfe09526dd9dbcf6ad1f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiolocator_001.png b/thumbs/radiolocator_001.png new file mode 100644 index 0000000..160be82 Binary files /dev/null and b/thumbs/radiolocator_001.png differ diff --git a/thumbs/radiolocator_001.png.import b/thumbs/radiolocator_001.png.import new file mode 100644 index 0000000..a96f27e --- /dev/null +++ b/thumbs/radiolocator_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brq1gcri651dv" +path="res://.godot/imported/radiolocator_001.png-9cb36d96b007c08e79df526650fc7d15.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiolocator_001.png" +dest_files=["res://.godot/imported/radiolocator_001.png-9cb36d96b007c08e79df526650fc7d15.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiolocator_002.png b/thumbs/radiolocator_002.png new file mode 100644 index 0000000..3485cc1 Binary files /dev/null and b/thumbs/radiolocator_002.png differ diff --git a/thumbs/radiolocator_002.png.import b/thumbs/radiolocator_002.png.import new file mode 100644 index 0000000..8306030 --- /dev/null +++ b/thumbs/radiolocator_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bngtff4ba66yo" +path="res://.godot/imported/radiolocator_002.png-9a24a2b8d6eddfbf2750e72036725fb9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiolocator_002.png" +dest_files=["res://.godot/imported/radiolocator_002.png-9a24a2b8d6eddfbf2750e72036725fb9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiolocator_platform_001.png b/thumbs/radiolocator_platform_001.png new file mode 100644 index 0000000..78954cc Binary files /dev/null and b/thumbs/radiolocator_platform_001.png differ diff --git a/thumbs/radiolocator_platform_001.png.import b/thumbs/radiolocator_platform_001.png.import new file mode 100644 index 0000000..f1d5c47 --- /dev/null +++ b/thumbs/radiolocator_platform_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jx200km4k0pt" +path="res://.godot/imported/radiolocator_platform_001.png-6eb430fc384cbf0587fd75ff8b6a77e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiolocator_platform_001.png" +dest_files=["res://.godot/imported/radiolocator_platform_001.png-6eb430fc384cbf0587fd75ff8b6a77e2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiolocator_platform_002.png b/thumbs/radiolocator_platform_002.png new file mode 100644 index 0000000..e9bef08 Binary files /dev/null and b/thumbs/radiolocator_platform_002.png differ diff --git a/thumbs/radiolocator_platform_002.png.import b/thumbs/radiolocator_platform_002.png.import new file mode 100644 index 0000000..3092a70 --- /dev/null +++ b/thumbs/radiolocator_platform_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vb0b5j4gwxcl" +path="res://.godot/imported/radiolocator_platform_002.png-39e9ad948f9bc27a719848edaf5424db.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiolocator_platform_002.png" +dest_files=["res://.godot/imported/radiolocator_platform_002.png-39e9ad948f9bc27a719848edaf5424db.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiostation_001.png b/thumbs/radiostation_001.png new file mode 100644 index 0000000..aedb739 Binary files /dev/null and b/thumbs/radiostation_001.png differ diff --git a/thumbs/radiostation_001.png.import b/thumbs/radiostation_001.png.import new file mode 100644 index 0000000..893fed6 --- /dev/null +++ b/thumbs/radiostation_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ncoqtcn6fxrt" +path="res://.godot/imported/radiostation_001.png-6ed6ed5a0d87f0de8551e6774ae0c509.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiostation_001.png" +dest_files=["res://.godot/imported/radiostation_001.png-6ed6ed5a0d87f0de8551e6774ae0c509.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/radiostation_002.png b/thumbs/radiostation_002.png new file mode 100644 index 0000000..3cddc20 Binary files /dev/null and b/thumbs/radiostation_002.png differ diff --git a/thumbs/radiostation_002.png.import b/thumbs/radiostation_002.png.import new file mode 100644 index 0000000..ca0ddfb --- /dev/null +++ b/thumbs/radiostation_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://daq6ej2fbbiwm" +path="res://.godot/imported/radiostation_002.png-029413762256a869b25b432bfeeeefb9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/radiostation_002.png" +dest_files=["res://.godot/imported/radiostation_002.png-029413762256a869b25b432bfeeeefb9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/road_006.png b/thumbs/road_006.png new file mode 100644 index 0000000..eadd278 Binary files /dev/null and b/thumbs/road_006.png differ diff --git a/thumbs/road_006.png.import b/thumbs/road_006.png.import new file mode 100644 index 0000000..69406c7 --- /dev/null +++ b/thumbs/road_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8wgfvuwjebct" +path="res://.godot/imported/road_006.png-05b8dba7388e0e111c2c9c0357c72049.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/road_006.png" +dest_files=["res://.godot/imported/road_006.png-05b8dba7388e0e111c2c9c0357c72049.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/road_007.png b/thumbs/road_007.png new file mode 100644 index 0000000..9da7cdf Binary files /dev/null and b/thumbs/road_007.png differ diff --git a/thumbs/road_007.png.import b/thumbs/road_007.png.import new file mode 100644 index 0000000..dbd602e --- /dev/null +++ b/thumbs/road_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8qu5ifwxba67" +path="res://.godot/imported/road_007.png-b3902b8757b1775b8ac04bd2dba5ab10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/road_007.png" +dest_files=["res://.godot/imported/road_007.png-b3902b8757b1775b8ac04bd2dba5ab10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/road_008.png b/thumbs/road_008.png new file mode 100644 index 0000000..23e483a Binary files /dev/null and b/thumbs/road_008.png differ diff --git a/thumbs/road_008.png.import b/thumbs/road_008.png.import new file mode 100644 index 0000000..af2dbac --- /dev/null +++ b/thumbs/road_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxv7y24wu5fsx" +path="res://.godot/imported/road_008.png-41ee917d390a7849b6fb35d28c398725.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/road_008.png" +dest_files=["res://.godot/imported/road_008.png-41ee917d390a7849b6fb35d28c398725.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/road_009.png b/thumbs/road_009.png new file mode 100644 index 0000000..4260e75 Binary files /dev/null and b/thumbs/road_009.png differ diff --git a/thumbs/road_009.png.import b/thumbs/road_009.png.import new file mode 100644 index 0000000..7c60d64 --- /dev/null +++ b/thumbs/road_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bp8p1eak31c5k" +path="res://.godot/imported/road_009.png-5bce11978d4178dc6501e0797824e37f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/road_009.png" +dest_files=["res://.godot/imported/road_009.png-5bce11978d4178dc6501e0797824e37f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_003.png b/thumbs/rock_003.png new file mode 100644 index 0000000..b736545 Binary files /dev/null and b/thumbs/rock_003.png differ diff --git a/thumbs/rock_003.png.import b/thumbs/rock_003.png.import new file mode 100644 index 0000000..dec7ed2 --- /dev/null +++ b/thumbs/rock_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b254mqm4vo7am" +path="res://.godot/imported/rock_003.png-6406eddfb18e7e7888dc32d42edab90e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_003.png" +dest_files=["res://.godot/imported/rock_003.png-6406eddfb18e7e7888dc32d42edab90e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_004.png b/thumbs/rock_004.png new file mode 100644 index 0000000..e837410 Binary files /dev/null and b/thumbs/rock_004.png differ diff --git a/thumbs/rock_004.png.import b/thumbs/rock_004.png.import new file mode 100644 index 0000000..d8d3f0b --- /dev/null +++ b/thumbs/rock_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdo6uwkifcs3y" +path="res://.godot/imported/rock_004.png-d2fd680906dd10ff4135d472ea916307.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_004.png" +dest_files=["res://.godot/imported/rock_004.png-d2fd680906dd10ff4135d472ea916307.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_005.png b/thumbs/rock_005.png new file mode 100644 index 0000000..564d56a Binary files /dev/null and b/thumbs/rock_005.png differ diff --git a/thumbs/rock_005.png.import b/thumbs/rock_005.png.import new file mode 100644 index 0000000..c7b26eb --- /dev/null +++ b/thumbs/rock_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbh3fcaqd7w8r" +path="res://.godot/imported/rock_005.png-662557edfed6291b142f06b7e5b91387.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_005.png" +dest_files=["res://.godot/imported/rock_005.png-662557edfed6291b142f06b7e5b91387.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_006.png b/thumbs/rock_006.png new file mode 100644 index 0000000..8d69ced Binary files /dev/null and b/thumbs/rock_006.png differ diff --git a/thumbs/rock_006.png.import b/thumbs/rock_006.png.import new file mode 100644 index 0000000..6cf17c0 --- /dev/null +++ b/thumbs/rock_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfs37vno1jid" +path="res://.godot/imported/rock_006.png-07688541cf5d99552b98a8550642d3b9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_006.png" +dest_files=["res://.godot/imported/rock_006.png-07688541cf5d99552b98a8550642d3b9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_007.png b/thumbs/rock_007.png new file mode 100644 index 0000000..c70d073 Binary files /dev/null and b/thumbs/rock_007.png differ diff --git a/thumbs/rock_007.png.import b/thumbs/rock_007.png.import new file mode 100644 index 0000000..d1a48c0 --- /dev/null +++ b/thumbs/rock_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://g58x7q0unqhm" +path="res://.godot/imported/rock_007.png-3cc03e62a568b43af0a8d5cae3ca5ed0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_007.png" +dest_files=["res://.godot/imported/rock_007.png-3cc03e62a568b43af0a8d5cae3ca5ed0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_008.png b/thumbs/rock_008.png new file mode 100644 index 0000000..b2a2feb Binary files /dev/null and b/thumbs/rock_008.png differ diff --git a/thumbs/rock_008.png.import b/thumbs/rock_008.png.import new file mode 100644 index 0000000..434d079 --- /dev/null +++ b/thumbs/rock_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gwjekiuijf60" +path="res://.godot/imported/rock_008.png-bb03792c1e1cf798ba35cfd9f6c6ae94.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_008.png" +dest_files=["res://.godot/imported/rock_008.png-bb03792c1e1cf798ba35cfd9f6c6ae94.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_009.png b/thumbs/rock_009.png new file mode 100644 index 0000000..25321e5 Binary files /dev/null and b/thumbs/rock_009.png differ diff --git a/thumbs/rock_009.png.import b/thumbs/rock_009.png.import new file mode 100644 index 0000000..32bdd5b --- /dev/null +++ b/thumbs/rock_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dowcg0e426hud" +path="res://.godot/imported/rock_009.png-863a80ca7d9359375762aba7e143265a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_009.png" +dest_files=["res://.godot/imported/rock_009.png-863a80ca7d9359375762aba7e143265a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_010.png b/thumbs/rock_010.png new file mode 100644 index 0000000..0a7a981 Binary files /dev/null and b/thumbs/rock_010.png differ diff --git a/thumbs/rock_010.png.import b/thumbs/rock_010.png.import new file mode 100644 index 0000000..9804a38 --- /dev/null +++ b/thumbs/rock_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cq4q2yec74cmx" +path="res://.godot/imported/rock_010.png-a632f46c1a884903031cfec0915f9d66.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_010.png" +dest_files=["res://.godot/imported/rock_010.png-a632f46c1a884903031cfec0915f9d66.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_011.png b/thumbs/rock_011.png new file mode 100644 index 0000000..a867cc8 Binary files /dev/null and b/thumbs/rock_011.png differ diff --git a/thumbs/rock_011.png.import b/thumbs/rock_011.png.import new file mode 100644 index 0000000..b075b41 --- /dev/null +++ b/thumbs/rock_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://k0yrrki2qabv" +path="res://.godot/imported/rock_011.png-c90924d27851a7a5c7733e6897e391b5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_011.png" +dest_files=["res://.godot/imported/rock_011.png-c90924d27851a7a5c7733e6897e391b5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_012.png b/thumbs/rock_012.png new file mode 100644 index 0000000..e636c34 Binary files /dev/null and b/thumbs/rock_012.png differ diff --git a/thumbs/rock_012.png.import b/thumbs/rock_012.png.import new file mode 100644 index 0000000..637a7be --- /dev/null +++ b/thumbs/rock_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccnxpp4cnt28n" +path="res://.godot/imported/rock_012.png-545709a9f64ca5073c4c4ab9f22eefac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_012.png" +dest_files=["res://.godot/imported/rock_012.png-545709a9f64ca5073c4c4ab9f22eefac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_013.png b/thumbs/rock_013.png new file mode 100644 index 0000000..f21d481 Binary files /dev/null and b/thumbs/rock_013.png differ diff --git a/thumbs/rock_013.png.import b/thumbs/rock_013.png.import new file mode 100644 index 0000000..c7811d9 --- /dev/null +++ b/thumbs/rock_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cful7s2q50dt6" +path="res://.godot/imported/rock_013.png-6c0f643cee343cb3b4251fa4c56c4f15.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_013.png" +dest_files=["res://.godot/imported/rock_013.png-6c0f643cee343cb3b4251fa4c56c4f15.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_014.png b/thumbs/rock_014.png new file mode 100644 index 0000000..b5a3a97 Binary files /dev/null and b/thumbs/rock_014.png differ diff --git a/thumbs/rock_014.png.import b/thumbs/rock_014.png.import new file mode 100644 index 0000000..e467a4d --- /dev/null +++ b/thumbs/rock_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://spstllxv8ka8" +path="res://.godot/imported/rock_014.png-9d827ab2547b61b55597c0f1eb83b9cd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_014.png" +dest_files=["res://.godot/imported/rock_014.png-9d827ab2547b61b55597c0f1eb83b9cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_015.png b/thumbs/rock_015.png new file mode 100644 index 0000000..4eea36c Binary files /dev/null and b/thumbs/rock_015.png differ diff --git a/thumbs/rock_015.png.import b/thumbs/rock_015.png.import new file mode 100644 index 0000000..aca1c5b --- /dev/null +++ b/thumbs/rock_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dywjcswag5dce" +path="res://.godot/imported/rock_015.png-b011539f6ff43fbb09745dd21d221fcd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_015.png" +dest_files=["res://.godot/imported/rock_015.png-b011539f6ff43fbb09745dd21d221fcd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_016.png b/thumbs/rock_016.png new file mode 100644 index 0000000..bde1a02 Binary files /dev/null and b/thumbs/rock_016.png differ diff --git a/thumbs/rock_016.png.import b/thumbs/rock_016.png.import new file mode 100644 index 0000000..9662a5d --- /dev/null +++ b/thumbs/rock_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5s5qpxvic06j" +path="res://.godot/imported/rock_016.png-3d97e45b49f7417cc8cc7ef4c47cb1e6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_016.png" +dest_files=["res://.godot/imported/rock_016.png-3d97e45b49f7417cc8cc7ef4c47cb1e6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_017.png b/thumbs/rock_017.png new file mode 100644 index 0000000..f8ec409 Binary files /dev/null and b/thumbs/rock_017.png differ diff --git a/thumbs/rock_017.png.import b/thumbs/rock_017.png.import new file mode 100644 index 0000000..c249d9e --- /dev/null +++ b/thumbs/rock_017.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drkke18qhu5kh" +path="res://.godot/imported/rock_017.png-53f778954752696b5993481730949cf0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_017.png" +dest_files=["res://.godot/imported/rock_017.png-53f778954752696b5993481730949cf0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_018.png b/thumbs/rock_018.png new file mode 100644 index 0000000..0902abf Binary files /dev/null and b/thumbs/rock_018.png differ diff --git a/thumbs/rock_018.png.import b/thumbs/rock_018.png.import new file mode 100644 index 0000000..9a9b581 --- /dev/null +++ b/thumbs/rock_018.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmlu5x8o8iclr" +path="res://.godot/imported/rock_018.png-de744e2dc659cae08b373186297599da.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_018.png" +dest_files=["res://.godot/imported/rock_018.png-de744e2dc659cae08b373186297599da.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_019.png b/thumbs/rock_019.png new file mode 100644 index 0000000..96ca6a0 Binary files /dev/null and b/thumbs/rock_019.png differ diff --git a/thumbs/rock_019.png.import b/thumbs/rock_019.png.import new file mode 100644 index 0000000..afde03e --- /dev/null +++ b/thumbs/rock_019.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ltakv1s6ut50" +path="res://.godot/imported/rock_019.png-ea6a69d709ac061f707a3eadb2e8c4c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_019.png" +dest_files=["res://.godot/imported/rock_019.png-ea6a69d709ac061f707a3eadb2e8c4c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_020.png b/thumbs/rock_020.png new file mode 100644 index 0000000..6954498 Binary files /dev/null and b/thumbs/rock_020.png differ diff --git a/thumbs/rock_020.png.import b/thumbs/rock_020.png.import new file mode 100644 index 0000000..d2a509d --- /dev/null +++ b/thumbs/rock_020.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cs7u455lf7tj4" +path="res://.godot/imported/rock_020.png-a4ad5a9eb4818b30b6ca0fa8b38a1a86.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_020.png" +dest_files=["res://.godot/imported/rock_020.png-a4ad5a9eb4818b30b6ca0fa8b38a1a86.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_021.png b/thumbs/rock_021.png new file mode 100644 index 0000000..de82092 Binary files /dev/null and b/thumbs/rock_021.png differ diff --git a/thumbs/rock_021.png.import b/thumbs/rock_021.png.import new file mode 100644 index 0000000..2579162 --- /dev/null +++ b/thumbs/rock_021.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3xt18gvmbjc8" +path="res://.godot/imported/rock_021.png-9306fa934e78495ed7e343ed6ba57eb6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_021.png" +dest_files=["res://.godot/imported/rock_021.png-9306fa934e78495ed7e343ed6ba57eb6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_022.png b/thumbs/rock_022.png new file mode 100644 index 0000000..f4f18fd Binary files /dev/null and b/thumbs/rock_022.png differ diff --git a/thumbs/rock_022.png.import b/thumbs/rock_022.png.import new file mode 100644 index 0000000..0a35601 --- /dev/null +++ b/thumbs/rock_022.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0fm4ii5qria6" +path="res://.godot/imported/rock_022.png-88c35d38f0722fc33b2ea5425b3b1988.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_022.png" +dest_files=["res://.godot/imported/rock_022.png-88c35d38f0722fc33b2ea5425b3b1988.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_023.png b/thumbs/rock_023.png new file mode 100644 index 0000000..455b7ec Binary files /dev/null and b/thumbs/rock_023.png differ diff --git a/thumbs/rock_023.png.import b/thumbs/rock_023.png.import new file mode 100644 index 0000000..1dfcf2e --- /dev/null +++ b/thumbs/rock_023.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bme2keecsggkn" +path="res://.godot/imported/rock_023.png-6a57648ee7fed36bbe1d45b7c9472e7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_023.png" +dest_files=["res://.godot/imported/rock_023.png-6a57648ee7fed36bbe1d45b7c9472e7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_024.png b/thumbs/rock_024.png new file mode 100644 index 0000000..cd458c1 Binary files /dev/null and b/thumbs/rock_024.png differ diff --git a/thumbs/rock_024.png.import b/thumbs/rock_024.png.import new file mode 100644 index 0000000..5a073f3 --- /dev/null +++ b/thumbs/rock_024.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btknl71epllp7" +path="res://.godot/imported/rock_024.png-6b3a445767eb00aa2a2aa7e404d2771b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_024.png" +dest_files=["res://.godot/imported/rock_024.png-6b3a445767eb00aa2a2aa7e404d2771b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/rock_038.png b/thumbs/rock_038.png new file mode 100644 index 0000000..ef28a61 Binary files /dev/null and b/thumbs/rock_038.png differ diff --git a/thumbs/rock_038.png.import b/thumbs/rock_038.png.import new file mode 100644 index 0000000..3873e2f --- /dev/null +++ b/thumbs/rock_038.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhnrbk60qw13u" +path="res://.godot/imported/rock_038.png-e65832d80b645f82440aa683dcf8a0fb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/rock_038.png" +dest_files=["res://.godot/imported/rock_038.png-e65832d80b645f82440aa683dcf8a0fb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/sand_001.png b/thumbs/sand_001.png new file mode 100644 index 0000000..1ddf6c2 Binary files /dev/null and b/thumbs/sand_001.png differ diff --git a/thumbs/sand_001.png.import b/thumbs/sand_001.png.import new file mode 100644 index 0000000..325995a --- /dev/null +++ b/thumbs/sand_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4iltdplrcp2l" +path="res://.godot/imported/sand_001.png-e938e680ee3fdddf9f726ecf7a4261bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/sand_001.png" +dest_files=["res://.godot/imported/sand_001.png-e938e680ee3fdddf9f726ecf7a4261bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_001.png b/thumbs/shell_001.png new file mode 100644 index 0000000..a3af9cb Binary files /dev/null and b/thumbs/shell_001.png differ diff --git a/thumbs/shell_001.png.import b/thumbs/shell_001.png.import new file mode 100644 index 0000000..3939457 --- /dev/null +++ b/thumbs/shell_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4cchdjjnj3ao" +path="res://.godot/imported/shell_001.png-8ad2749145bab9aa6a8198539339edbb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_001.png" +dest_files=["res://.godot/imported/shell_001.png-8ad2749145bab9aa6a8198539339edbb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_002.png b/thumbs/shell_002.png new file mode 100644 index 0000000..435fd48 Binary files /dev/null and b/thumbs/shell_002.png differ diff --git a/thumbs/shell_002.png.import b/thumbs/shell_002.png.import new file mode 100644 index 0000000..984b115 --- /dev/null +++ b/thumbs/shell_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csm3cf7cs65" +path="res://.godot/imported/shell_002.png-5b0ae0e83ddffc4b4e2b5a3b671ac11d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_002.png" +dest_files=["res://.godot/imported/shell_002.png-5b0ae0e83ddffc4b4e2b5a3b671ac11d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_003.png b/thumbs/shell_003.png new file mode 100644 index 0000000..08c35d7 Binary files /dev/null and b/thumbs/shell_003.png differ diff --git a/thumbs/shell_003.png.import b/thumbs/shell_003.png.import new file mode 100644 index 0000000..5fc5384 --- /dev/null +++ b/thumbs/shell_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://by2uh7amsyob2" +path="res://.godot/imported/shell_003.png-3758afc2c054032dfabec7fb26316bce.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_003.png" +dest_files=["res://.godot/imported/shell_003.png-3758afc2c054032dfabec7fb26316bce.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_004.png b/thumbs/shell_004.png new file mode 100644 index 0000000..e51f303 Binary files /dev/null and b/thumbs/shell_004.png differ diff --git a/thumbs/shell_004.png.import b/thumbs/shell_004.png.import new file mode 100644 index 0000000..13aaa06 --- /dev/null +++ b/thumbs/shell_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbtfllusr5amv" +path="res://.godot/imported/shell_004.png-8992c656b7d6dc59367555ad046c8ed7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_004.png" +dest_files=["res://.godot/imported/shell_004.png-8992c656b7d6dc59367555ad046c8ed7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_005.png b/thumbs/shell_005.png new file mode 100644 index 0000000..d73701c Binary files /dev/null and b/thumbs/shell_005.png differ diff --git a/thumbs/shell_005.png.import b/thumbs/shell_005.png.import new file mode 100644 index 0000000..95bf1d2 --- /dev/null +++ b/thumbs/shell_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dl62knhaie5ml" +path="res://.godot/imported/shell_005.png-00c5f35369a06655f9761bcf14793142.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_005.png" +dest_files=["res://.godot/imported/shell_005.png-00c5f35369a06655f9761bcf14793142.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_006.png b/thumbs/shell_006.png new file mode 100644 index 0000000..87bcf9b Binary files /dev/null and b/thumbs/shell_006.png differ diff --git a/thumbs/shell_006.png.import b/thumbs/shell_006.png.import new file mode 100644 index 0000000..84c6a1f --- /dev/null +++ b/thumbs/shell_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blhhuqo0lcegx" +path="res://.godot/imported/shell_006.png-e468d9564b02b8bb74d51ad8ec70a387.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_006.png" +dest_files=["res://.godot/imported/shell_006.png-e468d9564b02b8bb74d51ad8ec70a387.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_007.png b/thumbs/shell_007.png new file mode 100644 index 0000000..cd28e2f Binary files /dev/null and b/thumbs/shell_007.png differ diff --git a/thumbs/shell_007.png.import b/thumbs/shell_007.png.import new file mode 100644 index 0000000..c65b9c3 --- /dev/null +++ b/thumbs/shell_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxdeoes1nseti" +path="res://.godot/imported/shell_007.png-761e7fda77085e2e72554e2deebe6196.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_007.png" +dest_files=["res://.godot/imported/shell_007.png-761e7fda77085e2e72554e2deebe6196.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/shell_008.png b/thumbs/shell_008.png new file mode 100644 index 0000000..1abc069 Binary files /dev/null and b/thumbs/shell_008.png differ diff --git a/thumbs/shell_008.png.import b/thumbs/shell_008.png.import new file mode 100644 index 0000000..674e7ce --- /dev/null +++ b/thumbs/shell_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ysfusuctrrrx" +path="res://.godot/imported/shell_008.png-ae8d7e107b1bd74e275a361562c88218.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/shell_008.png" +dest_files=["res://.godot/imported/shell_008.png-ae8d7e107b1bd74e275a361562c88218.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/solar_battery_001.png b/thumbs/solar_battery_001.png new file mode 100644 index 0000000..be98faf Binary files /dev/null and b/thumbs/solar_battery_001.png differ diff --git a/thumbs/solar_battery_001.png.import b/thumbs/solar_battery_001.png.import new file mode 100644 index 0000000..f3834c5 --- /dev/null +++ b/thumbs/solar_battery_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4c77x5wdjodx" +path="res://.godot/imported/solar_battery_001.png-cac0d107b06e7d519b28fd5c0f73f2f2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/solar_battery_001.png" +dest_files=["res://.godot/imported/solar_battery_001.png-cac0d107b06e7d519b28fd5c0f73f2f2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_015.png b/thumbs/stone_015.png new file mode 100644 index 0000000..675889b Binary files /dev/null and b/thumbs/stone_015.png differ diff --git a/thumbs/stone_015.png.import b/thumbs/stone_015.png.import new file mode 100644 index 0000000..7714ce0 --- /dev/null +++ b/thumbs/stone_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtb1og3iwfwe" +path="res://.godot/imported/stone_015.png-894c1e79d658c36c3fdba8f7f9d3490e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_015.png" +dest_files=["res://.godot/imported/stone_015.png-894c1e79d658c36c3fdba8f7f9d3490e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_016.png b/thumbs/stone_016.png new file mode 100644 index 0000000..c26145b Binary files /dev/null and b/thumbs/stone_016.png differ diff --git a/thumbs/stone_016.png.import b/thumbs/stone_016.png.import new file mode 100644 index 0000000..1fb13e8 --- /dev/null +++ b/thumbs/stone_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dm2tlla3fnnkd" +path="res://.godot/imported/stone_016.png-19fc96b377baf215288fe5fd7ccd17b4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_016.png" +dest_files=["res://.godot/imported/stone_016.png-19fc96b377baf215288fe5fd7ccd17b4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_017.png b/thumbs/stone_017.png new file mode 100644 index 0000000..4c6cb9f Binary files /dev/null and b/thumbs/stone_017.png differ diff --git a/thumbs/stone_017.png.import b/thumbs/stone_017.png.import new file mode 100644 index 0000000..23638eb --- /dev/null +++ b/thumbs/stone_017.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csker0nqhe4bk" +path="res://.godot/imported/stone_017.png-5c30ba648fbb65a8fc2be2733cf94caf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_017.png" +dest_files=["res://.godot/imported/stone_017.png-5c30ba648fbb65a8fc2be2733cf94caf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_018.png b/thumbs/stone_018.png new file mode 100644 index 0000000..6864ce3 Binary files /dev/null and b/thumbs/stone_018.png differ diff --git a/thumbs/stone_018.png.import b/thumbs/stone_018.png.import new file mode 100644 index 0000000..4a694c8 --- /dev/null +++ b/thumbs/stone_018.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckq7e5ly4in85" +path="res://.godot/imported/stone_018.png-de9025f8d98af760b20fc5af6219d3ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_018.png" +dest_files=["res://.godot/imported/stone_018.png-de9025f8d98af760b20fc5af6219d3ad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_019.png b/thumbs/stone_019.png new file mode 100644 index 0000000..e387f59 Binary files /dev/null and b/thumbs/stone_019.png differ diff --git a/thumbs/stone_019.png.import b/thumbs/stone_019.png.import new file mode 100644 index 0000000..5ae0217 --- /dev/null +++ b/thumbs/stone_019.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnbgtgfmkftoi" +path="res://.godot/imported/stone_019.png-ed78dca9c9ae8571f83bf8341e358dd8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_019.png" +dest_files=["res://.godot/imported/stone_019.png-ed78dca9c9ae8571f83bf8341e358dd8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_020.png b/thumbs/stone_020.png new file mode 100644 index 0000000..b99fa49 Binary files /dev/null and b/thumbs/stone_020.png differ diff --git a/thumbs/stone_020.png.import b/thumbs/stone_020.png.import new file mode 100644 index 0000000..bbbc556 --- /dev/null +++ b/thumbs/stone_020.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://diqog2ulfs17e" +path="res://.godot/imported/stone_020.png-6704c168da74cf7ca23874aada6a2076.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_020.png" +dest_files=["res://.godot/imported/stone_020.png-6704c168da74cf7ca23874aada6a2076.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_021.png b/thumbs/stone_021.png new file mode 100644 index 0000000..3bc82af Binary files /dev/null and b/thumbs/stone_021.png differ diff --git a/thumbs/stone_021.png.import b/thumbs/stone_021.png.import new file mode 100644 index 0000000..ac8ca9e --- /dev/null +++ b/thumbs/stone_021.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckrnyf636v4xr" +path="res://.godot/imported/stone_021.png-15f4fccf7f134a3e8ce278bf9ae3a722.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_021.png" +dest_files=["res://.godot/imported/stone_021.png-15f4fccf7f134a3e8ce278bf9ae3a722.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_022.png b/thumbs/stone_022.png new file mode 100644 index 0000000..ce47763 Binary files /dev/null and b/thumbs/stone_022.png differ diff --git a/thumbs/stone_022.png.import b/thumbs/stone_022.png.import new file mode 100644 index 0000000..191d260 --- /dev/null +++ b/thumbs/stone_022.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://byiyff8j7nipx" +path="res://.godot/imported/stone_022.png-0e94135ed479e72ed5828db34c612de2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_022.png" +dest_files=["res://.godot/imported/stone_022.png-0e94135ed479e72ed5828db34c612de2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_023.png b/thumbs/stone_023.png new file mode 100644 index 0000000..7764adc Binary files /dev/null and b/thumbs/stone_023.png differ diff --git a/thumbs/stone_023.png.import b/thumbs/stone_023.png.import new file mode 100644 index 0000000..a4752cb --- /dev/null +++ b/thumbs/stone_023.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvc26b5luodxv" +path="res://.godot/imported/stone_023.png-a971677b47b3e9df1d5bcf14e1d92ddb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_023.png" +dest_files=["res://.godot/imported/stone_023.png-a971677b47b3e9df1d5bcf14e1d92ddb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_024.png b/thumbs/stone_024.png new file mode 100644 index 0000000..0e76d80 Binary files /dev/null and b/thumbs/stone_024.png differ diff --git a/thumbs/stone_024.png.import b/thumbs/stone_024.png.import new file mode 100644 index 0000000..b1bce82 --- /dev/null +++ b/thumbs/stone_024.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3h56equ5a4fp" +path="res://.godot/imported/stone_024.png-56e46fc89968a975421c0649c991de80.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_024.png" +dest_files=["res://.godot/imported/stone_024.png-56e46fc89968a975421c0649c991de80.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_025.png b/thumbs/stone_025.png new file mode 100644 index 0000000..a7a6ec2 Binary files /dev/null and b/thumbs/stone_025.png differ diff --git a/thumbs/stone_025.png.import b/thumbs/stone_025.png.import new file mode 100644 index 0000000..4b5068b --- /dev/null +++ b/thumbs/stone_025.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bj3dcn4b4nmm5" +path="res://.godot/imported/stone_025.png-b43ae463d9c2cc9252414ab2aa42293a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_025.png" +dest_files=["res://.godot/imported/stone_025.png-b43ae463d9c2cc9252414ab2aa42293a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/stone_026.png b/thumbs/stone_026.png new file mode 100644 index 0000000..e5e8aea Binary files /dev/null and b/thumbs/stone_026.png differ diff --git a/thumbs/stone_026.png.import b/thumbs/stone_026.png.import new file mode 100644 index 0000000..b050e8b --- /dev/null +++ b/thumbs/stone_026.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tn4oy7opc5x2" +path="res://.godot/imported/stone_026.png-38e3ed718926a8f0da542ad755766c25.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/stone_026.png" +dest_files=["res://.godot/imported/stone_026.png-38e3ed718926a8f0da542ad755766c25.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/table_001.png b/thumbs/table_001.png new file mode 100644 index 0000000..5b1a291 Binary files /dev/null and b/thumbs/table_001.png differ diff --git a/thumbs/table_001.png.import b/thumbs/table_001.png.import new file mode 100644 index 0000000..e30862b --- /dev/null +++ b/thumbs/table_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i7clqq0rdhnc" +path="res://.godot/imported/table_001.png-9b4965ff76480ff9c434eeb8868f44d7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/table_001.png" +dest_files=["res://.godot/imported/table_001.png-9b4965ff76480ff9c434eeb8868f44d7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/table_002.png b/thumbs/table_002.png new file mode 100644 index 0000000..2160156 Binary files /dev/null and b/thumbs/table_002.png differ diff --git a/thumbs/table_002.png.import b/thumbs/table_002.png.import new file mode 100644 index 0000000..ff3fc08 --- /dev/null +++ b/thumbs/table_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cn6bj5h58agkc" +path="res://.godot/imported/table_002.png-921835c096565377ac851f8559043288.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/table_002.png" +dest_files=["res://.godot/imported/table_002.png-921835c096565377ac851f8559043288.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_001.png b/thumbs/tank_001.png new file mode 100644 index 0000000..5f2bec5 Binary files /dev/null and b/thumbs/tank_001.png differ diff --git a/thumbs/tank_001.png.import b/thumbs/tank_001.png.import new file mode 100644 index 0000000..2d531c2 --- /dev/null +++ b/thumbs/tank_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s7da2jrq1bsj" +path="res://.godot/imported/tank_001.png-1e029747fdebeba3bfd419becf067735.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_001.png" +dest_files=["res://.godot/imported/tank_001.png-1e029747fdebeba3bfd419becf067735.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_002.png b/thumbs/tank_002.png new file mode 100644 index 0000000..3b1cab9 Binary files /dev/null and b/thumbs/tank_002.png differ diff --git a/thumbs/tank_002.png.import b/thumbs/tank_002.png.import new file mode 100644 index 0000000..bb3caab --- /dev/null +++ b/thumbs/tank_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bq5qxregnaxa3" +path="res://.godot/imported/tank_002.png-11d6cb0882eeb82c362961414d5102f0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_002.png" +dest_files=["res://.godot/imported/tank_002.png-11d6cb0882eeb82c362961414d5102f0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_003.png b/thumbs/tank_003.png new file mode 100644 index 0000000..4f90901 Binary files /dev/null and b/thumbs/tank_003.png differ diff --git a/thumbs/tank_003.png.import b/thumbs/tank_003.png.import new file mode 100644 index 0000000..c37049d --- /dev/null +++ b/thumbs/tank_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0s33henaeqsq" +path="res://.godot/imported/tank_003.png-9a06152cf5fc52ccc02ce1ed3cd750ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_003.png" +dest_files=["res://.godot/imported/tank_003.png-9a06152cf5fc52ccc02ce1ed3cd750ad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_004.png b/thumbs/tank_004.png new file mode 100644 index 0000000..bf834c0 Binary files /dev/null and b/thumbs/tank_004.png differ diff --git a/thumbs/tank_004.png.import b/thumbs/tank_004.png.import new file mode 100644 index 0000000..9ee0b6c --- /dev/null +++ b/thumbs/tank_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc7figva0ic8y" +path="res://.godot/imported/tank_004.png-58ee25ced666f4d510e39059f9079d09.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_004.png" +dest_files=["res://.godot/imported/tank_004.png-58ee25ced666f4d510e39059f9079d09.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_005.png b/thumbs/tank_005.png new file mode 100644 index 0000000..18604a1 Binary files /dev/null and b/thumbs/tank_005.png differ diff --git a/thumbs/tank_005.png.import b/thumbs/tank_005.png.import new file mode 100644 index 0000000..253a3af --- /dev/null +++ b/thumbs/tank_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://m5ah6ofker4o" +path="res://.godot/imported/tank_005.png-02a3369a99b88568fa7312676f272360.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_005.png" +dest_files=["res://.godot/imported/tank_005.png-02a3369a99b88568fa7312676f272360.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_006.png b/thumbs/tank_006.png new file mode 100644 index 0000000..aa645af Binary files /dev/null and b/thumbs/tank_006.png differ diff --git a/thumbs/tank_006.png.import b/thumbs/tank_006.png.import new file mode 100644 index 0000000..137d508 --- /dev/null +++ b/thumbs/tank_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1ho0muqdt5gc" +path="res://.godot/imported/tank_006.png-636d9710c0942fdb0662ef2f5913ae08.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_006.png" +dest_files=["res://.godot/imported/tank_006.png-636d9710c0942fdb0662ef2f5913ae08.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_car_001.png b/thumbs/tank_car_001.png new file mode 100644 index 0000000..deb37b6 Binary files /dev/null and b/thumbs/tank_car_001.png differ diff --git a/thumbs/tank_car_001.png.import b/thumbs/tank_car_001.png.import new file mode 100644 index 0000000..571fb9d --- /dev/null +++ b/thumbs/tank_car_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1uspdcqy671j" +path="res://.godot/imported/tank_car_001.png-5fd8a4828dea78395c66ed5fe8909e79.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_car_001.png" +dest_files=["res://.godot/imported/tank_car_001.png-5fd8a4828dea78395c66ed5fe8909e79.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_car_002.png b/thumbs/tank_car_002.png new file mode 100644 index 0000000..55b553d Binary files /dev/null and b/thumbs/tank_car_002.png differ diff --git a/thumbs/tank_car_002.png.import b/thumbs/tank_car_002.png.import new file mode 100644 index 0000000..530836b --- /dev/null +++ b/thumbs/tank_car_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crjupmrbyekbf" +path="res://.godot/imported/tank_car_002.png-49c3d1c03a40077701fc18374f4e62d1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_car_002.png" +dest_files=["res://.godot/imported/tank_car_002.png-49c3d1c03a40077701fc18374f4e62d1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_001.png b/thumbs/tank_gun_001.png new file mode 100644 index 0000000..bbd0da5 Binary files /dev/null and b/thumbs/tank_gun_001.png differ diff --git a/thumbs/tank_gun_001.png.import b/thumbs/tank_gun_001.png.import new file mode 100644 index 0000000..aeb8e9e --- /dev/null +++ b/thumbs/tank_gun_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjvtdvrb7eiy0" +path="res://.godot/imported/tank_gun_001.png-53dc1951392e3af6837e9c48fe437abd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_001.png" +dest_files=["res://.godot/imported/tank_gun_001.png-53dc1951392e3af6837e9c48fe437abd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_002.png b/thumbs/tank_gun_002.png new file mode 100644 index 0000000..bbd0da5 Binary files /dev/null and b/thumbs/tank_gun_002.png differ diff --git a/thumbs/tank_gun_002.png.import b/thumbs/tank_gun_002.png.import new file mode 100644 index 0000000..2a7b0d0 --- /dev/null +++ b/thumbs/tank_gun_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://yrjbjeu7drjq" +path="res://.godot/imported/tank_gun_002.png-c58bd5d378dd1a241a107b4c4dcc0a23.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_002.png" +dest_files=["res://.godot/imported/tank_gun_002.png-c58bd5d378dd1a241a107b4c4dcc0a23.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_003.png b/thumbs/tank_gun_003.png new file mode 100644 index 0000000..41b51f4 Binary files /dev/null and b/thumbs/tank_gun_003.png differ diff --git a/thumbs/tank_gun_003.png.import b/thumbs/tank_gun_003.png.import new file mode 100644 index 0000000..9c71623 --- /dev/null +++ b/thumbs/tank_gun_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rfgcwnh7b6cg" +path="res://.godot/imported/tank_gun_003.png-e4990d0f84718dc2c88845db48d8c0dc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_003.png" +dest_files=["res://.godot/imported/tank_gun_003.png-e4990d0f84718dc2c88845db48d8c0dc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_004.png b/thumbs/tank_gun_004.png new file mode 100644 index 0000000..41b51f4 Binary files /dev/null and b/thumbs/tank_gun_004.png differ diff --git a/thumbs/tank_gun_004.png.import b/thumbs/tank_gun_004.png.import new file mode 100644 index 0000000..1ff4026 --- /dev/null +++ b/thumbs/tank_gun_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccqoxatj1ltv5" +path="res://.godot/imported/tank_gun_004.png-2b1c925232665e7706f3b1ea96441da6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_004.png" +dest_files=["res://.godot/imported/tank_gun_004.png-2b1c925232665e7706f3b1ea96441da6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_005.png b/thumbs/tank_gun_005.png new file mode 100644 index 0000000..0ef4a51 Binary files /dev/null and b/thumbs/tank_gun_005.png differ diff --git a/thumbs/tank_gun_005.png.import b/thumbs/tank_gun_005.png.import new file mode 100644 index 0000000..2c868ed --- /dev/null +++ b/thumbs/tank_gun_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bljeq26jlq7bs" +path="res://.godot/imported/tank_gun_005.png-cf18e26634eecb8c7287dfb84e745cd5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_005.png" +dest_files=["res://.godot/imported/tank_gun_005.png-cf18e26634eecb8c7287dfb84e745cd5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_gun_006.png b/thumbs/tank_gun_006.png new file mode 100644 index 0000000..0ef4a51 Binary files /dev/null and b/thumbs/tank_gun_006.png differ diff --git a/thumbs/tank_gun_006.png.import b/thumbs/tank_gun_006.png.import new file mode 100644 index 0000000..c46b238 --- /dev/null +++ b/thumbs/tank_gun_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bao5dy3lx3w4l" +path="res://.godot/imported/tank_gun_006.png-c7b73dd1c8b8d2629eec79dd0f593867.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_gun_006.png" +dest_files=["res://.godot/imported/tank_gun_006.png-c7b73dd1c8b8d2629eec79dd0f593867.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_001.png b/thumbs/tank_tower_001.png new file mode 100644 index 0000000..1a00e70 Binary files /dev/null and b/thumbs/tank_tower_001.png differ diff --git a/thumbs/tank_tower_001.png.import b/thumbs/tank_tower_001.png.import new file mode 100644 index 0000000..7fb50b0 --- /dev/null +++ b/thumbs/tank_tower_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccrk564gir3dh" +path="res://.godot/imported/tank_tower_001.png-3784daec8cdc5e146f0be12bb92e57c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_001.png" +dest_files=["res://.godot/imported/tank_tower_001.png-3784daec8cdc5e146f0be12bb92e57c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_002.png b/thumbs/tank_tower_002.png new file mode 100644 index 0000000..606b614 Binary files /dev/null and b/thumbs/tank_tower_002.png differ diff --git a/thumbs/tank_tower_002.png.import b/thumbs/tank_tower_002.png.import new file mode 100644 index 0000000..83808d9 --- /dev/null +++ b/thumbs/tank_tower_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc5i07vpia11j" +path="res://.godot/imported/tank_tower_002.png-79f5bbe100430f6115f52fa2c5b11acb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_002.png" +dest_files=["res://.godot/imported/tank_tower_002.png-79f5bbe100430f6115f52fa2c5b11acb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_003.png b/thumbs/tank_tower_003.png new file mode 100644 index 0000000..cb18c82 Binary files /dev/null and b/thumbs/tank_tower_003.png differ diff --git a/thumbs/tank_tower_003.png.import b/thumbs/tank_tower_003.png.import new file mode 100644 index 0000000..8e7d781 --- /dev/null +++ b/thumbs/tank_tower_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6lw82uah2oto" +path="res://.godot/imported/tank_tower_003.png-b9a7bbd7d320493884f67a7a97c5c24f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_003.png" +dest_files=["res://.godot/imported/tank_tower_003.png-b9a7bbd7d320493884f67a7a97c5c24f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_004.png b/thumbs/tank_tower_004.png new file mode 100644 index 0000000..4d088e7 Binary files /dev/null and b/thumbs/tank_tower_004.png differ diff --git a/thumbs/tank_tower_004.png.import b/thumbs/tank_tower_004.png.import new file mode 100644 index 0000000..1e85c33 --- /dev/null +++ b/thumbs/tank_tower_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bx0j38ylnr6w8" +path="res://.godot/imported/tank_tower_004.png-f80677739e40127675fe3aed1829a5a8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_004.png" +dest_files=["res://.godot/imported/tank_tower_004.png-f80677739e40127675fe3aed1829a5a8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_005.png b/thumbs/tank_tower_005.png new file mode 100644 index 0000000..93cd878 Binary files /dev/null and b/thumbs/tank_tower_005.png differ diff --git a/thumbs/tank_tower_005.png.import b/thumbs/tank_tower_005.png.import new file mode 100644 index 0000000..4bf47d9 --- /dev/null +++ b/thumbs/tank_tower_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vh6e3dio2mv4" +path="res://.godot/imported/tank_tower_005.png-cd1a0f0deb4aa3511d715da8e99f7dbf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_005.png" +dest_files=["res://.godot/imported/tank_tower_005.png-cd1a0f0deb4aa3511d715da8e99f7dbf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tank_tower_006.png b/thumbs/tank_tower_006.png new file mode 100644 index 0000000..7db461d Binary files /dev/null and b/thumbs/tank_tower_006.png differ diff --git a/thumbs/tank_tower_006.png.import b/thumbs/tank_tower_006.png.import new file mode 100644 index 0000000..b9cdf99 --- /dev/null +++ b/thumbs/tank_tower_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://do7q0034128nw" +path="res://.godot/imported/tank_tower_006.png-2aa7cde95e9c8f1eeaf84b7fcb158169.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tank_tower_006.png" +dest_files=["res://.godot/imported/tank_tower_006.png-2aa7cde95e9c8f1eeaf84b7fcb158169.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/target_001.png b/thumbs/target_001.png new file mode 100644 index 0000000..6c7648b Binary files /dev/null and b/thumbs/target_001.png differ diff --git a/thumbs/target_001.png.import b/thumbs/target_001.png.import new file mode 100644 index 0000000..3bccdd6 --- /dev/null +++ b/thumbs/target_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://g3x5f182upqf" +path="res://.godot/imported/target_001.png-c6426b6c4c8c479fbb0375762333d747.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/target_001.png" +dest_files=["res://.godot/imported/target_001.png-c6426b6c4c8c479fbb0375762333d747.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/target_002.png b/thumbs/target_002.png new file mode 100644 index 0000000..e207767 Binary files /dev/null and b/thumbs/target_002.png differ diff --git a/thumbs/target_002.png.import b/thumbs/target_002.png.import new file mode 100644 index 0000000..8b1ed5b --- /dev/null +++ b/thumbs/target_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lms6ht050t8p" +path="res://.godot/imported/target_002.png-db6c25752e581aff45bab21b0e144f30.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/target_002.png" +dest_files=["res://.godot/imported/target_002.png-db6c25752e581aff45bab21b0e144f30.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_001.png b/thumbs/tent_001.png new file mode 100644 index 0000000..749ec44 Binary files /dev/null and b/thumbs/tent_001.png differ diff --git a/thumbs/tent_001.png.import b/thumbs/tent_001.png.import new file mode 100644 index 0000000..db8d389 --- /dev/null +++ b/thumbs/tent_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dt4bphl5s2u3s" +path="res://.godot/imported/tent_001.png-cd47255fe329e68d76e68adc29c9e7dc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_001.png" +dest_files=["res://.godot/imported/tent_001.png-cd47255fe329e68d76e68adc29c9e7dc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_002.png b/thumbs/tent_002.png new file mode 100644 index 0000000..57f3589 Binary files /dev/null and b/thumbs/tent_002.png differ diff --git a/thumbs/tent_002.png.import b/thumbs/tent_002.png.import new file mode 100644 index 0000000..c089733 --- /dev/null +++ b/thumbs/tent_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsu6fo4pny11u" +path="res://.godot/imported/tent_002.png-09a0de2c3fef1b57c782c846fb8e62fd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_002.png" +dest_files=["res://.godot/imported/tent_002.png-09a0de2c3fef1b57c782c846fb8e62fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_003.png b/thumbs/tent_003.png new file mode 100644 index 0000000..19d1cdb Binary files /dev/null and b/thumbs/tent_003.png differ diff --git a/thumbs/tent_003.png.import b/thumbs/tent_003.png.import new file mode 100644 index 0000000..5679e7f --- /dev/null +++ b/thumbs/tent_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crg6kt3c26gdj" +path="res://.godot/imported/tent_003.png-f03c74857880fdf54264dcc18bc8f796.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_003.png" +dest_files=["res://.godot/imported/tent_003.png-f03c74857880fdf54264dcc18bc8f796.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_004.png b/thumbs/tent_004.png new file mode 100644 index 0000000..0a54a5f Binary files /dev/null and b/thumbs/tent_004.png differ diff --git a/thumbs/tent_004.png.import b/thumbs/tent_004.png.import new file mode 100644 index 0000000..1c6fee5 --- /dev/null +++ b/thumbs/tent_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvfrvy68xi38m" +path="res://.godot/imported/tent_004.png-aae165d254da12b202afa5df354a9597.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_004.png" +dest_files=["res://.godot/imported/tent_004.png-aae165d254da12b202afa5df354a9597.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_005.png b/thumbs/tent_005.png new file mode 100644 index 0000000..af1f00e Binary files /dev/null and b/thumbs/tent_005.png differ diff --git a/thumbs/tent_005.png.import b/thumbs/tent_005.png.import new file mode 100644 index 0000000..5976f05 --- /dev/null +++ b/thumbs/tent_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccomssilu5yod" +path="res://.godot/imported/tent_005.png-9baa1944ff86ea41fd0ac529047a1f6b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_005.png" +dest_files=["res://.godot/imported/tent_005.png-9baa1944ff86ea41fd0ac529047a1f6b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_006.png b/thumbs/tent_006.png new file mode 100644 index 0000000..9d3a6d1 Binary files /dev/null and b/thumbs/tent_006.png differ diff --git a/thumbs/tent_006.png.import b/thumbs/tent_006.png.import new file mode 100644 index 0000000..13d343c --- /dev/null +++ b/thumbs/tent_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2mniupi8lq33" +path="res://.godot/imported/tent_006.png-26b801f29d9f9cb2b13785567da84fb4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_006.png" +dest_files=["res://.godot/imported/tent_006.png-26b801f29d9f9cb2b13785567da84fb4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_007.png b/thumbs/tent_007.png new file mode 100644 index 0000000..85d903d Binary files /dev/null and b/thumbs/tent_007.png differ diff --git a/thumbs/tent_007.png.import b/thumbs/tent_007.png.import new file mode 100644 index 0000000..5422b3a --- /dev/null +++ b/thumbs/tent_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://datgl70wb1ail" +path="res://.godot/imported/tent_007.png-b28e355a44128512cda80e7cf5298170.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_007.png" +dest_files=["res://.godot/imported/tent_007.png-b28e355a44128512cda80e7cf5298170.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_008.png b/thumbs/tent_008.png new file mode 100644 index 0000000..536c5bf Binary files /dev/null and b/thumbs/tent_008.png differ diff --git a/thumbs/tent_008.png.import b/thumbs/tent_008.png.import new file mode 100644 index 0000000..527adce --- /dev/null +++ b/thumbs/tent_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xff8xuxq62p3" +path="res://.godot/imported/tent_008.png-365a89c0e689eda53d96304f5395865b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_008.png" +dest_files=["res://.godot/imported/tent_008.png-365a89c0e689eda53d96304f5395865b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_009.png b/thumbs/tent_009.png new file mode 100644 index 0000000..c9e38b8 Binary files /dev/null and b/thumbs/tent_009.png differ diff --git a/thumbs/tent_009.png.import b/thumbs/tent_009.png.import new file mode 100644 index 0000000..e162128 --- /dev/null +++ b/thumbs/tent_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjlho0rj4ensq" +path="res://.godot/imported/tent_009.png-ca13bceafcaf14744ba03b90954bc773.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_009.png" +dest_files=["res://.godot/imported/tent_009.png-ca13bceafcaf14744ba03b90954bc773.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_010.png b/thumbs/tent_010.png new file mode 100644 index 0000000..b562db8 Binary files /dev/null and b/thumbs/tent_010.png differ diff --git a/thumbs/tent_010.png.import b/thumbs/tent_010.png.import new file mode 100644 index 0000000..e329d63 --- /dev/null +++ b/thumbs/tent_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uijfkbw2kitk" +path="res://.godot/imported/tent_010.png-fcc004d0730143c0cd392c57405a5aab.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_010.png" +dest_files=["res://.godot/imported/tent_010.png-fcc004d0730143c0cd392c57405a5aab.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_011.png b/thumbs/tent_011.png new file mode 100644 index 0000000..2e288e7 Binary files /dev/null and b/thumbs/tent_011.png differ diff --git a/thumbs/tent_011.png.import b/thumbs/tent_011.png.import new file mode 100644 index 0000000..eb56935 --- /dev/null +++ b/thumbs/tent_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1qlj52ut2qme" +path="res://.godot/imported/tent_011.png-a0a225a754aa52883d67f92e4fa4b349.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_011.png" +dest_files=["res://.godot/imported/tent_011.png-a0a225a754aa52883d67f92e4fa4b349.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_012.png b/thumbs/tent_012.png new file mode 100644 index 0000000..77ed004 Binary files /dev/null and b/thumbs/tent_012.png differ diff --git a/thumbs/tent_012.png.import b/thumbs/tent_012.png.import new file mode 100644 index 0000000..efaefac --- /dev/null +++ b/thumbs/tent_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtrerhrrr6bbu" +path="res://.godot/imported/tent_012.png-c91e8efafc09047740b3f76651a0732a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_012.png" +dest_files=["res://.godot/imported/tent_012.png-c91e8efafc09047740b3f76651a0732a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_013.png b/thumbs/tent_013.png new file mode 100644 index 0000000..572b6c3 Binary files /dev/null and b/thumbs/tent_013.png differ diff --git a/thumbs/tent_013.png.import b/thumbs/tent_013.png.import new file mode 100644 index 0000000..920160e --- /dev/null +++ b/thumbs/tent_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c73gf472npmoo" +path="res://.godot/imported/tent_013.png-9db85535198770c5aa434b92f63e77b2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_013.png" +dest_files=["res://.godot/imported/tent_013.png-9db85535198770c5aa434b92f63e77b2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tent_014.png b/thumbs/tent_014.png new file mode 100644 index 0000000..e396378 Binary files /dev/null and b/thumbs/tent_014.png differ diff --git a/thumbs/tent_014.png.import b/thumbs/tent_014.png.import new file mode 100644 index 0000000..84c5b1f --- /dev/null +++ b/thumbs/tent_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpskwwxwixkjv" +path="res://.godot/imported/tent_014.png-93a2bb1c29cd9d7c36d77b55f7f562f1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tent_014.png" +dest_files=["res://.godot/imported/tent_014.png-93a2bb1c29cd9d7c36d77b55f7f562f1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tire_001.png b/thumbs/tire_001.png new file mode 100644 index 0000000..bcfba99 Binary files /dev/null and b/thumbs/tire_001.png differ diff --git a/thumbs/tire_001.png.import b/thumbs/tire_001.png.import new file mode 100644 index 0000000..d87f0c8 --- /dev/null +++ b/thumbs/tire_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsf76mlk4u1it" +path="res://.godot/imported/tire_001.png-aa411baf69dd3848f4593e2330641a27.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tire_001.png" +dest_files=["res://.godot/imported/tire_001.png-aa411baf69dd3848f4593e2330641a27.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tires_001.png b/thumbs/tires_001.png new file mode 100644 index 0000000..28cb7df Binary files /dev/null and b/thumbs/tires_001.png differ diff --git a/thumbs/tires_001.png.import b/thumbs/tires_001.png.import new file mode 100644 index 0000000..06909a0 --- /dev/null +++ b/thumbs/tires_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7twmv0psktwm" +path="res://.godot/imported/tires_001.png-031fdcd1718207098ddd5ef93fd0c399.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tires_001.png" +dest_files=["res://.godot/imported/tires_001.png-031fdcd1718207098ddd5ef93fd0c399.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/toilet_001.png b/thumbs/toilet_001.png new file mode 100644 index 0000000..1820f09 Binary files /dev/null and b/thumbs/toilet_001.png differ diff --git a/thumbs/toilet_001.png.import b/thumbs/toilet_001.png.import new file mode 100644 index 0000000..0233c3d --- /dev/null +++ b/thumbs/toilet_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7lb2lxa6y3d8" +path="res://.godot/imported/toilet_001.png-0178f20b01ea8322b2abb14960ca6f04.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/toilet_001.png" +dest_files=["res://.godot/imported/toilet_001.png-0178f20b01ea8322b2abb14960ca6f04.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/toilet_002.png b/thumbs/toilet_002.png new file mode 100644 index 0000000..6420195 Binary files /dev/null and b/thumbs/toilet_002.png differ diff --git a/thumbs/toilet_002.png.import b/thumbs/toilet_002.png.import new file mode 100644 index 0000000..6d1df4e --- /dev/null +++ b/thumbs/toilet_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2evauos4cjmc" +path="res://.godot/imported/toilet_002.png-3456db6e77d680f24d4c515a817a3abd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/toilet_002.png" +dest_files=["res://.godot/imported/toilet_002.png-3456db6e77d680f24d4c515a817a3abd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tower_001.png b/thumbs/tower_001.png new file mode 100644 index 0000000..c47a20d Binary files /dev/null and b/thumbs/tower_001.png differ diff --git a/thumbs/tower_001.png.import b/thumbs/tower_001.png.import new file mode 100644 index 0000000..3236f70 --- /dev/null +++ b/thumbs/tower_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btie0u7u5us1j" +path="res://.godot/imported/tower_001.png-91c2f86403c14f45fb839c0ae2b49f9e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tower_001.png" +dest_files=["res://.godot/imported/tower_001.png-91c2f86403c14f45fb839c0ae2b49f9e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tower_002.png b/thumbs/tower_002.png new file mode 100644 index 0000000..bd73c6b Binary files /dev/null and b/thumbs/tower_002.png differ diff --git a/thumbs/tower_002.png.import b/thumbs/tower_002.png.import new file mode 100644 index 0000000..d1895dc --- /dev/null +++ b/thumbs/tower_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg8egveebkqtn" +path="res://.godot/imported/tower_002.png-55f31a0aceba2b7e4b1611daaf434151.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tower_002.png" +dest_files=["res://.godot/imported/tower_002.png-55f31a0aceba2b7e4b1611daaf434151.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tower_003.png b/thumbs/tower_003.png new file mode 100644 index 0000000..99ee0ef Binary files /dev/null and b/thumbs/tower_003.png differ diff --git a/thumbs/tower_003.png.import b/thumbs/tower_003.png.import new file mode 100644 index 0000000..1ea42cf --- /dev/null +++ b/thumbs/tower_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqefoivn65l07" +path="res://.godot/imported/tower_003.png-2aa924c8ab1012498085cdd4114c8e0c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tower_003.png" +dest_files=["res://.godot/imported/tower_003.png-2aa924c8ab1012498085cdd4114c8e0c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tower_004.png b/thumbs/tower_004.png new file mode 100644 index 0000000..35b0a10 Binary files /dev/null and b/thumbs/tower_004.png differ diff --git a/thumbs/tower_004.png.import b/thumbs/tower_004.png.import new file mode 100644 index 0000000..b89aa6e --- /dev/null +++ b/thumbs/tower_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcbh5utqixwk0" +path="res://.godot/imported/tower_004.png-6ae6dc699a0e3a8bb84571927a82542c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tower_004.png" +dest_files=["res://.godot/imported/tower_004.png-6ae6dc699a0e3a8bb84571927a82542c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/tower_005.png b/thumbs/tower_005.png new file mode 100644 index 0000000..4364599 Binary files /dev/null and b/thumbs/tower_005.png differ diff --git a/thumbs/tower_005.png.import b/thumbs/tower_005.png.import new file mode 100644 index 0000000..3bc81b5 --- /dev/null +++ b/thumbs/tower_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgiiaafka6icd" +path="res://.godot/imported/tower_005.png-711ec9dce2f394c1335fba09a772767d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/tower_005.png" +dest_files=["res://.godot/imported/tower_005.png-711ec9dce2f394c1335fba09a772767d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_001.png b/thumbs/training_item_001.png new file mode 100644 index 0000000..d709eb9 Binary files /dev/null and b/thumbs/training_item_001.png differ diff --git a/thumbs/training_item_001.png.import b/thumbs/training_item_001.png.import new file mode 100644 index 0000000..43f8cf6 --- /dev/null +++ b/thumbs/training_item_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d260clhi0wwyy" +path="res://.godot/imported/training_item_001.png-746cd8c21d3c85a000e222a1bb380d09.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_001.png" +dest_files=["res://.godot/imported/training_item_001.png-746cd8c21d3c85a000e222a1bb380d09.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_002.png b/thumbs/training_item_002.png new file mode 100644 index 0000000..08dc236 Binary files /dev/null and b/thumbs/training_item_002.png differ diff --git a/thumbs/training_item_002.png.import b/thumbs/training_item_002.png.import new file mode 100644 index 0000000..11c61da --- /dev/null +++ b/thumbs/training_item_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://legpdh1bx38h" +path="res://.godot/imported/training_item_002.png-232692ad33ae84b8d2393caf35639360.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_002.png" +dest_files=["res://.godot/imported/training_item_002.png-232692ad33ae84b8d2393caf35639360.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_003.png b/thumbs/training_item_003.png new file mode 100644 index 0000000..f9e899c Binary files /dev/null and b/thumbs/training_item_003.png differ diff --git a/thumbs/training_item_003.png.import b/thumbs/training_item_003.png.import new file mode 100644 index 0000000..f361a36 --- /dev/null +++ b/thumbs/training_item_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr10bl6u2v5no" +path="res://.godot/imported/training_item_003.png-804075d7307d18beec92f1d050921d92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_003.png" +dest_files=["res://.godot/imported/training_item_003.png-804075d7307d18beec92f1d050921d92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_004.png b/thumbs/training_item_004.png new file mode 100644 index 0000000..cc342ae Binary files /dev/null and b/thumbs/training_item_004.png differ diff --git a/thumbs/training_item_004.png.import b/thumbs/training_item_004.png.import new file mode 100644 index 0000000..50b593d --- /dev/null +++ b/thumbs/training_item_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2562ekpsuvoa" +path="res://.godot/imported/training_item_004.png-2a5c409d0bb9faa4228fac0d702a8f51.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_004.png" +dest_files=["res://.godot/imported/training_item_004.png-2a5c409d0bb9faa4228fac0d702a8f51.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_005.png b/thumbs/training_item_005.png new file mode 100644 index 0000000..05a21ae Binary files /dev/null and b/thumbs/training_item_005.png differ diff --git a/thumbs/training_item_005.png.import b/thumbs/training_item_005.png.import new file mode 100644 index 0000000..8bca724 --- /dev/null +++ b/thumbs/training_item_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf6184tgvvaxh" +path="res://.godot/imported/training_item_005.png-122c28c8f1572cf44b125c1455a52164.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_005.png" +dest_files=["res://.godot/imported/training_item_005.png-122c28c8f1572cf44b125c1455a52164.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_006.png b/thumbs/training_item_006.png new file mode 100644 index 0000000..525605e Binary files /dev/null and b/thumbs/training_item_006.png differ diff --git a/thumbs/training_item_006.png.import b/thumbs/training_item_006.png.import new file mode 100644 index 0000000..4bf21c9 --- /dev/null +++ b/thumbs/training_item_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2paoa51kaa24" +path="res://.godot/imported/training_item_006.png-bd0a155421b07558b35a9059386cadf9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_006.png" +dest_files=["res://.godot/imported/training_item_006.png-bd0a155421b07558b35a9059386cadf9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/training_item_007.png b/thumbs/training_item_007.png new file mode 100644 index 0000000..7ff78ed Binary files /dev/null and b/thumbs/training_item_007.png differ diff --git a/thumbs/training_item_007.png.import b/thumbs/training_item_007.png.import new file mode 100644 index 0000000..dda1462 --- /dev/null +++ b/thumbs/training_item_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcxu0tl1870tx" +path="res://.godot/imported/training_item_007.png-a37b679adb2c9d10fb3deca75df580c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/training_item_007.png" +dest_files=["res://.godot/imported/training_item_007.png-a37b679adb2c9d10fb3deca75df580c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_001.png b/thumbs/truck_001.png new file mode 100644 index 0000000..75e3ca9 Binary files /dev/null and b/thumbs/truck_001.png differ diff --git a/thumbs/truck_001.png.import b/thumbs/truck_001.png.import new file mode 100644 index 0000000..326b90d --- /dev/null +++ b/thumbs/truck_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhy60eturmo2b" +path="res://.godot/imported/truck_001.png-ee421c40fa315b11e46d401a37dfd2f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_001.png" +dest_files=["res://.godot/imported/truck_001.png-ee421c40fa315b11e46d401a37dfd2f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_002.png b/thumbs/truck_002.png new file mode 100644 index 0000000..37d8f5d Binary files /dev/null and b/thumbs/truck_002.png differ diff --git a/thumbs/truck_002.png.import b/thumbs/truck_002.png.import new file mode 100644 index 0000000..cfa99a1 --- /dev/null +++ b/thumbs/truck_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2y7a7x3uuj56" +path="res://.godot/imported/truck_002.png-7cce12ffddc77f2620d609644dd1a789.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_002.png" +dest_files=["res://.godot/imported/truck_002.png-7cce12ffddc77f2620d609644dd1a789.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_002_aufbau.png b/thumbs/truck_002_aufbau.png new file mode 100644 index 0000000..37d8f5d Binary files /dev/null and b/thumbs/truck_002_aufbau.png differ diff --git a/thumbs/truck_002_aufbau.png.import b/thumbs/truck_002_aufbau.png.import new file mode 100644 index 0000000..afeae15 --- /dev/null +++ b/thumbs/truck_002_aufbau.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cus8pd1c8hrp" +path="res://.godot/imported/truck_002_aufbau.png-794bbda7e9f8c061f0cd15a509216ccf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_002_aufbau.png" +dest_files=["res://.godot/imported/truck_002_aufbau.png-794bbda7e9f8c061f0cd15a509216ccf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_002_tank.png b/thumbs/truck_002_tank.png new file mode 100644 index 0000000..37d8f5d Binary files /dev/null and b/thumbs/truck_002_tank.png differ diff --git a/thumbs/truck_002_tank.png.import b/thumbs/truck_002_tank.png.import new file mode 100644 index 0000000..3214162 --- /dev/null +++ b/thumbs/truck_002_tank.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dryok08p6820j" +path="res://.godot/imported/truck_002_tank.png-30ef7181841358146c9da5c85cd22453.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_002_tank.png" +dest_files=["res://.godot/imported/truck_002_tank.png-30ef7181841358146c9da5c85cd22453.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_003.png b/thumbs/truck_003.png new file mode 100644 index 0000000..360177a Binary files /dev/null and b/thumbs/truck_003.png differ diff --git a/thumbs/truck_003.png.import b/thumbs/truck_003.png.import new file mode 100644 index 0000000..d1b2c53 --- /dev/null +++ b/thumbs/truck_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsgsqlhoy81t" +path="res://.godot/imported/truck_003.png-4f0bfd9b2fb6cb3fc39166fe53705849.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_003.png" +dest_files=["res://.godot/imported/truck_003.png-4f0bfd9b2fb6cb3fc39166fe53705849.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_004.png b/thumbs/truck_004.png new file mode 100644 index 0000000..569c6fb Binary files /dev/null and b/thumbs/truck_004.png differ diff --git a/thumbs/truck_004.png.import b/thumbs/truck_004.png.import new file mode 100644 index 0000000..b08344b --- /dev/null +++ b/thumbs/truck_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bp0jmoifx53om" +path="res://.godot/imported/truck_004.png-9adf13b39ff74ae4d2b9baa493481709.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_004.png" +dest_files=["res://.godot/imported/truck_004.png-9adf13b39ff74ae4d2b9baa493481709.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_004_aufbau.png b/thumbs/truck_004_aufbau.png new file mode 100644 index 0000000..569c6fb Binary files /dev/null and b/thumbs/truck_004_aufbau.png differ diff --git a/thumbs/truck_004_aufbau.png.import b/thumbs/truck_004_aufbau.png.import new file mode 100644 index 0000000..2d75739 --- /dev/null +++ b/thumbs/truck_004_aufbau.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnlx2tqytt6ia" +path="res://.godot/imported/truck_004_aufbau.png-16602cfc43d3fcc07d692f9d6bf9fb3e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_004_aufbau.png" +dest_files=["res://.godot/imported/truck_004_aufbau.png-16602cfc43d3fcc07d692f9d6bf9fb3e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_004_plane.png b/thumbs/truck_004_plane.png new file mode 100644 index 0000000..569c6fb Binary files /dev/null and b/thumbs/truck_004_plane.png differ diff --git a/thumbs/truck_004_plane.png.import b/thumbs/truck_004_plane.png.import new file mode 100644 index 0000000..90871f3 --- /dev/null +++ b/thumbs/truck_004_plane.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfxqbg0fympbw" +path="res://.godot/imported/truck_004_plane.png-1dd029b88c1625603c6927d694586fe1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_004_plane.png" +dest_files=["res://.godot/imported/truck_004_plane.png-1dd029b88c1625603c6927d694586fe1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_001.png b/thumbs/truck_body_001.png new file mode 100644 index 0000000..59ca593 Binary files /dev/null and b/thumbs/truck_body_001.png differ diff --git a/thumbs/truck_body_001.png.import b/thumbs/truck_body_001.png.import new file mode 100644 index 0000000..2f5029f --- /dev/null +++ b/thumbs/truck_body_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckr7nlw5sxgii" +path="res://.godot/imported/truck_body_001.png-84f77501a1f79377dbb92f122f127a47.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_001.png" +dest_files=["res://.godot/imported/truck_body_001.png-84f77501a1f79377dbb92f122f127a47.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_002.png b/thumbs/truck_body_002.png new file mode 100644 index 0000000..94d0dc2 Binary files /dev/null and b/thumbs/truck_body_002.png differ diff --git a/thumbs/truck_body_002.png.import b/thumbs/truck_body_002.png.import new file mode 100644 index 0000000..eff11fc --- /dev/null +++ b/thumbs/truck_body_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmuoiyehf5fxd" +path="res://.godot/imported/truck_body_002.png-39ce38f8309739df0a5de4645e231e92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_002.png" +dest_files=["res://.godot/imported/truck_body_002.png-39ce38f8309739df0a5de4645e231e92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_003.png b/thumbs/truck_body_003.png new file mode 100644 index 0000000..639120f Binary files /dev/null and b/thumbs/truck_body_003.png differ diff --git a/thumbs/truck_body_003.png.import b/thumbs/truck_body_003.png.import new file mode 100644 index 0000000..d80209f --- /dev/null +++ b/thumbs/truck_body_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ei5kwymgtmcx" +path="res://.godot/imported/truck_body_003.png-ac50887a1d0ce8c991859cc5427bbae0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_003.png" +dest_files=["res://.godot/imported/truck_body_003.png-ac50887a1d0ce8c991859cc5427bbae0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_004.png b/thumbs/truck_body_004.png new file mode 100644 index 0000000..2fab29b Binary files /dev/null and b/thumbs/truck_body_004.png differ diff --git a/thumbs/truck_body_004.png.import b/thumbs/truck_body_004.png.import new file mode 100644 index 0000000..037b452 --- /dev/null +++ b/thumbs/truck_body_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buu5pqdjf8ko3" +path="res://.godot/imported/truck_body_004.png-2573db4d9d2dc6273750e34b95f09a27.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_004.png" +dest_files=["res://.godot/imported/truck_body_004.png-2573db4d9d2dc6273750e34b95f09a27.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_005.png b/thumbs/truck_body_005.png new file mode 100644 index 0000000..0848772 Binary files /dev/null and b/thumbs/truck_body_005.png differ diff --git a/thumbs/truck_body_005.png.import b/thumbs/truck_body_005.png.import new file mode 100644 index 0000000..11a13c9 --- /dev/null +++ b/thumbs/truck_body_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://073gsyir4ypg" +path="res://.godot/imported/truck_body_005.png-0b227355f61e0f20f68e91321c2b807d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_005.png" +dest_files=["res://.godot/imported/truck_body_005.png-0b227355f61e0f20f68e91321c2b807d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/truck_body_006.png b/thumbs/truck_body_006.png new file mode 100644 index 0000000..748f066 Binary files /dev/null and b/thumbs/truck_body_006.png differ diff --git a/thumbs/truck_body_006.png.import b/thumbs/truck_body_006.png.import new file mode 100644 index 0000000..e621b47 --- /dev/null +++ b/thumbs/truck_body_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc2bac73dm2jw" +path="res://.godot/imported/truck_body_006.png-0558a8829a71a0b93e1e3845aaffa167.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/truck_body_006.png" +dest_files=["res://.godot/imported/truck_body_006.png-0558a8829a71a0b93e1e3845aaffa167.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_001.png b/thumbs/wall_001.png new file mode 100644 index 0000000..89c2f27 Binary files /dev/null and b/thumbs/wall_001.png differ diff --git a/thumbs/wall_001.png.import b/thumbs/wall_001.png.import new file mode 100644 index 0000000..44b231b --- /dev/null +++ b/thumbs/wall_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdahfjyse2yvc" +path="res://.godot/imported/wall_001.png-786d351f1a1aabd7f95d574140511397.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_001.png" +dest_files=["res://.godot/imported/wall_001.png-786d351f1a1aabd7f95d574140511397.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_002.png b/thumbs/wall_002.png new file mode 100644 index 0000000..bb65888 Binary files /dev/null and b/thumbs/wall_002.png differ diff --git a/thumbs/wall_002.png.import b/thumbs/wall_002.png.import new file mode 100644 index 0000000..8f9ba87 --- /dev/null +++ b/thumbs/wall_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://mw7m4gkc4lax" +path="res://.godot/imported/wall_002.png-d248a91aff8c520801dbdf6ca13c7d2c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_002.png" +dest_files=["res://.godot/imported/wall_002.png-d248a91aff8c520801dbdf6ca13c7d2c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_003.png b/thumbs/wall_003.png new file mode 100644 index 0000000..3c64174 Binary files /dev/null and b/thumbs/wall_003.png differ diff --git a/thumbs/wall_003.png.import b/thumbs/wall_003.png.import new file mode 100644 index 0000000..bd68bf9 --- /dev/null +++ b/thumbs/wall_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blnks8o5o5vgb" +path="res://.godot/imported/wall_003.png-8fd8648ffd61f131cb75dc95693e7d7b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_003.png" +dest_files=["res://.godot/imported/wall_003.png-8fd8648ffd61f131cb75dc95693e7d7b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_004.png b/thumbs/wall_004.png new file mode 100644 index 0000000..9dde84f Binary files /dev/null and b/thumbs/wall_004.png differ diff --git a/thumbs/wall_004.png.import b/thumbs/wall_004.png.import new file mode 100644 index 0000000..4f708e8 --- /dev/null +++ b/thumbs/wall_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cas82g46h5lkl" +path="res://.godot/imported/wall_004.png-5bd305de29d2bd29a5126d6467991a74.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_004.png" +dest_files=["res://.godot/imported/wall_004.png-5bd305de29d2bd29a5126d6467991a74.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_005.png b/thumbs/wall_005.png new file mode 100644 index 0000000..6fa7c41 Binary files /dev/null and b/thumbs/wall_005.png differ diff --git a/thumbs/wall_005.png.import b/thumbs/wall_005.png.import new file mode 100644 index 0000000..be6f3e6 --- /dev/null +++ b/thumbs/wall_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cs1l5va2uy7h0" +path="res://.godot/imported/wall_005.png-ce2af2ea151c77db1f3bceb0c63f07dd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_005.png" +dest_files=["res://.godot/imported/wall_005.png-ce2af2ea151c77db1f3bceb0c63f07dd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_006.png b/thumbs/wall_006.png new file mode 100644 index 0000000..32d4a44 Binary files /dev/null and b/thumbs/wall_006.png differ diff --git a/thumbs/wall_006.png.import b/thumbs/wall_006.png.import new file mode 100644 index 0000000..0329bac --- /dev/null +++ b/thumbs/wall_006.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7jolqtw2ooak" +path="res://.godot/imported/wall_006.png-a03fe00f7c57a7a7fea3e50f705a4d33.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_006.png" +dest_files=["res://.godot/imported/wall_006.png-a03fe00f7c57a7a7fea3e50f705a4d33.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_007.png b/thumbs/wall_007.png new file mode 100644 index 0000000..a95cfd2 Binary files /dev/null and b/thumbs/wall_007.png differ diff --git a/thumbs/wall_007.png.import b/thumbs/wall_007.png.import new file mode 100644 index 0000000..f29d97d --- /dev/null +++ b/thumbs/wall_007.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y54evbcyrynm" +path="res://.godot/imported/wall_007.png-6b680f6b365fe0200c9af7c03cefd201.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_007.png" +dest_files=["res://.godot/imported/wall_007.png-6b680f6b365fe0200c9af7c03cefd201.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_008.png b/thumbs/wall_008.png new file mode 100644 index 0000000..1805bc8 Binary files /dev/null and b/thumbs/wall_008.png differ diff --git a/thumbs/wall_008.png.import b/thumbs/wall_008.png.import new file mode 100644 index 0000000..2bcc297 --- /dev/null +++ b/thumbs/wall_008.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cimmo4ydn82bm" +path="res://.godot/imported/wall_008.png-1b6320f3aa57f9544f11d5dbd4f03aa6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_008.png" +dest_files=["res://.godot/imported/wall_008.png-1b6320f3aa57f9544f11d5dbd4f03aa6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_009.png b/thumbs/wall_009.png new file mode 100644 index 0000000..840459e Binary files /dev/null and b/thumbs/wall_009.png differ diff --git a/thumbs/wall_009.png.import b/thumbs/wall_009.png.import new file mode 100644 index 0000000..d03b258 --- /dev/null +++ b/thumbs/wall_009.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckpoj7l3o1mhf" +path="res://.godot/imported/wall_009.png-e1dac71d1d81478531ceb0afe40e5d0c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_009.png" +dest_files=["res://.godot/imported/wall_009.png-e1dac71d1d81478531ceb0afe40e5d0c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_010.png b/thumbs/wall_010.png new file mode 100644 index 0000000..16d1414 Binary files /dev/null and b/thumbs/wall_010.png differ diff --git a/thumbs/wall_010.png.import b/thumbs/wall_010.png.import new file mode 100644 index 0000000..e30713c --- /dev/null +++ b/thumbs/wall_010.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6j1rscrn11vl" +path="res://.godot/imported/wall_010.png-e90977ad441c9088c8bd14d94a186ecc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_010.png" +dest_files=["res://.godot/imported/wall_010.png-e90977ad441c9088c8bd14d94a186ecc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_011.png b/thumbs/wall_011.png new file mode 100644 index 0000000..3e3e50f Binary files /dev/null and b/thumbs/wall_011.png differ diff --git a/thumbs/wall_011.png.import b/thumbs/wall_011.png.import new file mode 100644 index 0000000..9e2ef16 --- /dev/null +++ b/thumbs/wall_011.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cy0vwmslkdn7l" +path="res://.godot/imported/wall_011.png-f5193560a196374e1e84c7c8c75cd6a2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_011.png" +dest_files=["res://.godot/imported/wall_011.png-f5193560a196374e1e84c7c8c75cd6a2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_012.png b/thumbs/wall_012.png new file mode 100644 index 0000000..19c97af Binary files /dev/null and b/thumbs/wall_012.png differ diff --git a/thumbs/wall_012.png.import b/thumbs/wall_012.png.import new file mode 100644 index 0000000..2960418 --- /dev/null +++ b/thumbs/wall_012.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxp8vtp5x1nsg" +path="res://.godot/imported/wall_012.png-95ea4c8116e16dbb87fc796d8b2dfcba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_012.png" +dest_files=["res://.godot/imported/wall_012.png-95ea4c8116e16dbb87fc796d8b2dfcba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_013.png b/thumbs/wall_013.png new file mode 100644 index 0000000..3f74fe7 Binary files /dev/null and b/thumbs/wall_013.png differ diff --git a/thumbs/wall_013.png.import b/thumbs/wall_013.png.import new file mode 100644 index 0000000..549e129 --- /dev/null +++ b/thumbs/wall_013.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3wtin1gf2j8k" +path="res://.godot/imported/wall_013.png-398590fd8c0147c27c104ffd20a30566.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_013.png" +dest_files=["res://.godot/imported/wall_013.png-398590fd8c0147c27c104ffd20a30566.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_014.png b/thumbs/wall_014.png new file mode 100644 index 0000000..8b40265 Binary files /dev/null and b/thumbs/wall_014.png differ diff --git a/thumbs/wall_014.png.import b/thumbs/wall_014.png.import new file mode 100644 index 0000000..9985a3b --- /dev/null +++ b/thumbs/wall_014.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwyxruyfoubxc" +path="res://.godot/imported/wall_014.png-95ef8d1734f55bb36b4876a7aa8d8796.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_014.png" +dest_files=["res://.godot/imported/wall_014.png-95ef8d1734f55bb36b4876a7aa8d8796.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_015.png b/thumbs/wall_015.png new file mode 100644 index 0000000..9e49f88 Binary files /dev/null and b/thumbs/wall_015.png differ diff --git a/thumbs/wall_015.png.import b/thumbs/wall_015.png.import new file mode 100644 index 0000000..3bf65fc --- /dev/null +++ b/thumbs/wall_015.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ep5vm4lpim0b" +path="res://.godot/imported/wall_015.png-a907296619146b727d888319661b300d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_015.png" +dest_files=["res://.godot/imported/wall_015.png-a907296619146b727d888319661b300d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_016.png b/thumbs/wall_016.png new file mode 100644 index 0000000..1819600 Binary files /dev/null and b/thumbs/wall_016.png differ diff --git a/thumbs/wall_016.png.import b/thumbs/wall_016.png.import new file mode 100644 index 0000000..4b98f62 --- /dev/null +++ b/thumbs/wall_016.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw106wuwba7i5" +path="res://.godot/imported/wall_016.png-a63ff01cae0b09bb921b20f7f04ee105.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_016.png" +dest_files=["res://.godot/imported/wall_016.png-a63ff01cae0b09bb921b20f7f04ee105.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_017.png b/thumbs/wall_017.png new file mode 100644 index 0000000..3fa6712 Binary files /dev/null and b/thumbs/wall_017.png differ diff --git a/thumbs/wall_017.png.import b/thumbs/wall_017.png.import new file mode 100644 index 0000000..6cfe7fc --- /dev/null +++ b/thumbs/wall_017.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dv5mp0kjndnlq" +path="res://.godot/imported/wall_017.png-4d2f2293c56d5ceed7cb998eb614fa4d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_017.png" +dest_files=["res://.godot/imported/wall_017.png-4d2f2293c56d5ceed7cb998eb614fa4d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_018.png b/thumbs/wall_018.png new file mode 100644 index 0000000..62160ae Binary files /dev/null and b/thumbs/wall_018.png differ diff --git a/thumbs/wall_018.png.import b/thumbs/wall_018.png.import new file mode 100644 index 0000000..78a8382 --- /dev/null +++ b/thumbs/wall_018.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwiegiboem8dv" +path="res://.godot/imported/wall_018.png-b4f5189ad52b70223160e9e8fd50cbe9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_018.png" +dest_files=["res://.godot/imported/wall_018.png-b4f5189ad52b70223160e9e8fd50cbe9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_019.png b/thumbs/wall_019.png new file mode 100644 index 0000000..44ff7c0 Binary files /dev/null and b/thumbs/wall_019.png differ diff --git a/thumbs/wall_019.png.import b/thumbs/wall_019.png.import new file mode 100644 index 0000000..24bdff1 --- /dev/null +++ b/thumbs/wall_019.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hko1isqno3q3" +path="res://.godot/imported/wall_019.png-4eb4b2eb3d1e3d7d24b4c645d5787de0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_019.png" +dest_files=["res://.godot/imported/wall_019.png-4eb4b2eb3d1e3d7d24b4c645d5787de0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_020.png b/thumbs/wall_020.png new file mode 100644 index 0000000..dcd180e Binary files /dev/null and b/thumbs/wall_020.png differ diff --git a/thumbs/wall_020.png.import b/thumbs/wall_020.png.import new file mode 100644 index 0000000..f3defe6 --- /dev/null +++ b/thumbs/wall_020.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dldpyeay7prkp" +path="res://.godot/imported/wall_020.png-0d74240dc06fdfaa9e6f9d01e9424293.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_020.png" +dest_files=["res://.godot/imported/wall_020.png-0d74240dc06fdfaa9e6f9d01e9424293.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_021.png b/thumbs/wall_021.png new file mode 100644 index 0000000..5bc4216 Binary files /dev/null and b/thumbs/wall_021.png differ diff --git a/thumbs/wall_021.png.import b/thumbs/wall_021.png.import new file mode 100644 index 0000000..3ba7974 --- /dev/null +++ b/thumbs/wall_021.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://nscatca2vouo" +path="res://.godot/imported/wall_021.png-972b7d16653e43a4f691817ae1f0dda7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_021.png" +dest_files=["res://.godot/imported/wall_021.png-972b7d16653e43a4f691817ae1f0dda7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_022.png b/thumbs/wall_022.png new file mode 100644 index 0000000..3c933ac Binary files /dev/null and b/thumbs/wall_022.png differ diff --git a/thumbs/wall_022.png.import b/thumbs/wall_022.png.import new file mode 100644 index 0000000..0ecf9e0 --- /dev/null +++ b/thumbs/wall_022.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://pxqa5mil2jvr" +path="res://.godot/imported/wall_022.png-577ff2d1f588c8e6f1055bf6a933037a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_022.png" +dest_files=["res://.godot/imported/wall_022.png-577ff2d1f588c8e6f1055bf6a933037a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_023.png b/thumbs/wall_023.png new file mode 100644 index 0000000..aee4037 Binary files /dev/null and b/thumbs/wall_023.png differ diff --git a/thumbs/wall_023.png.import b/thumbs/wall_023.png.import new file mode 100644 index 0000000..50f3ea5 --- /dev/null +++ b/thumbs/wall_023.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbxs2ywe7ddhy" +path="res://.godot/imported/wall_023.png-46ae83fa6ce554a669aec8a32e16ddda.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_023.png" +dest_files=["res://.godot/imported/wall_023.png-46ae83fa6ce554a669aec8a32e16ddda.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_024.png b/thumbs/wall_024.png new file mode 100644 index 0000000..4a6ca10 Binary files /dev/null and b/thumbs/wall_024.png differ diff --git a/thumbs/wall_024.png.import b/thumbs/wall_024.png.import new file mode 100644 index 0000000..a2c2734 --- /dev/null +++ b/thumbs/wall_024.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://k0afrdeyer1o" +path="res://.godot/imported/wall_024.png-969d30da2226ab9178a91e07f289ce1d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_024.png" +dest_files=["res://.godot/imported/wall_024.png-969d30da2226ab9178a91e07f289ce1d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_025.png b/thumbs/wall_025.png new file mode 100644 index 0000000..d065861 Binary files /dev/null and b/thumbs/wall_025.png differ diff --git a/thumbs/wall_025.png.import b/thumbs/wall_025.png.import new file mode 100644 index 0000000..677ba5d --- /dev/null +++ b/thumbs/wall_025.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbrag8q6l2l0m" +path="res://.godot/imported/wall_025.png-ed2a05a5b1e90ecf1485631357b6bae6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_025.png" +dest_files=["res://.godot/imported/wall_025.png-ed2a05a5b1e90ecf1485631357b6bae6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_051.png b/thumbs/wall_051.png new file mode 100644 index 0000000..d8a2ff1 Binary files /dev/null and b/thumbs/wall_051.png differ diff --git a/thumbs/wall_051.png.import b/thumbs/wall_051.png.import new file mode 100644 index 0000000..22b4519 --- /dev/null +++ b/thumbs/wall_051.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cguhfqfqmmh3i" +path="res://.godot/imported/wall_051.png-305b7aa2e7c03efbaef32687c5863565.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_051.png" +dest_files=["res://.godot/imported/wall_051.png-305b7aa2e7c03efbaef32687c5863565.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_052.png b/thumbs/wall_052.png new file mode 100644 index 0000000..0d896a3 Binary files /dev/null and b/thumbs/wall_052.png differ diff --git a/thumbs/wall_052.png.import b/thumbs/wall_052.png.import new file mode 100644 index 0000000..f2fca71 --- /dev/null +++ b/thumbs/wall_052.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bikrkhbmh0nl8" +path="res://.godot/imported/wall_052.png-e22e70926688d02881d2218a97b6d58f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_052.png" +dest_files=["res://.godot/imported/wall_052.png-e22e70926688d02881d2218a97b6d58f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_053.png b/thumbs/wall_053.png new file mode 100644 index 0000000..0b39499 Binary files /dev/null and b/thumbs/wall_053.png differ diff --git a/thumbs/wall_053.png.import b/thumbs/wall_053.png.import new file mode 100644 index 0000000..39243f3 --- /dev/null +++ b/thumbs/wall_053.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://m03vdoy81uu4" +path="res://.godot/imported/wall_053.png-c12a32700dfc9e48c051bd6414ac67df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_053.png" +dest_files=["res://.godot/imported/wall_053.png-c12a32700dfc9e48c051bd6414ac67df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_054.png b/thumbs/wall_054.png new file mode 100644 index 0000000..f5e8391 Binary files /dev/null and b/thumbs/wall_054.png differ diff --git a/thumbs/wall_054.png.import b/thumbs/wall_054.png.import new file mode 100644 index 0000000..dd5b3e4 --- /dev/null +++ b/thumbs/wall_054.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqs1fsqey5vbg" +path="res://.godot/imported/wall_054.png-ba4e13511f1593fd6213a30430bbd830.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_054.png" +dest_files=["res://.godot/imported/wall_054.png-ba4e13511f1593fd6213a30430bbd830.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_055.png b/thumbs/wall_055.png new file mode 100644 index 0000000..19f3f57 Binary files /dev/null and b/thumbs/wall_055.png differ diff --git a/thumbs/wall_055.png.import b/thumbs/wall_055.png.import new file mode 100644 index 0000000..5b41102 --- /dev/null +++ b/thumbs/wall_055.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4snr0e1bn4jg" +path="res://.godot/imported/wall_055.png-c29a889ace87dcca0a2be1785ad80b6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_055.png" +dest_files=["res://.godot/imported/wall_055.png-c29a889ace87dcca0a2be1785ad80b6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_056.png b/thumbs/wall_056.png new file mode 100644 index 0000000..cda5fdf Binary files /dev/null and b/thumbs/wall_056.png differ diff --git a/thumbs/wall_056.png.import b/thumbs/wall_056.png.import new file mode 100644 index 0000000..fce3261 --- /dev/null +++ b/thumbs/wall_056.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://326x51ump384" +path="res://.godot/imported/wall_056.png-0848d7276eda03eac9ecdfffb0cc2881.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_056.png" +dest_files=["res://.godot/imported/wall_056.png-0848d7276eda03eac9ecdfffb0cc2881.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_057.png b/thumbs/wall_057.png new file mode 100644 index 0000000..62ccaa5 Binary files /dev/null and b/thumbs/wall_057.png differ diff --git a/thumbs/wall_057.png.import b/thumbs/wall_057.png.import new file mode 100644 index 0000000..5dd009b --- /dev/null +++ b/thumbs/wall_057.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwjm5rrc8vriu" +path="res://.godot/imported/wall_057.png-4f90ccd424adcbddeaab2407797e0c1d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_057.png" +dest_files=["res://.godot/imported/wall_057.png-4f90ccd424adcbddeaab2407797e0c1d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_058.png b/thumbs/wall_058.png new file mode 100644 index 0000000..ee733fb Binary files /dev/null and b/thumbs/wall_058.png differ diff --git a/thumbs/wall_058.png.import b/thumbs/wall_058.png.import new file mode 100644 index 0000000..2345b65 --- /dev/null +++ b/thumbs/wall_058.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rv6fd6v786hs" +path="res://.godot/imported/wall_058.png-9b563245365aefe09cff4c49efdcf5ef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_058.png" +dest_files=["res://.godot/imported/wall_058.png-9b563245365aefe09cff4c49efdcf5ef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_059.png b/thumbs/wall_059.png new file mode 100644 index 0000000..63ed43f Binary files /dev/null and b/thumbs/wall_059.png differ diff --git a/thumbs/wall_059.png.import b/thumbs/wall_059.png.import new file mode 100644 index 0000000..f3d57ca --- /dev/null +++ b/thumbs/wall_059.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1l8a8gmrh2ck" +path="res://.godot/imported/wall_059.png-5860ebf7218104e7e549a48d9fe7d4cb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_059.png" +dest_files=["res://.godot/imported/wall_059.png-5860ebf7218104e7e549a48d9fe7d4cb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_060.png b/thumbs/wall_060.png new file mode 100644 index 0000000..7e89685 Binary files /dev/null and b/thumbs/wall_060.png differ diff --git a/thumbs/wall_060.png.import b/thumbs/wall_060.png.import new file mode 100644 index 0000000..fec9a37 --- /dev/null +++ b/thumbs/wall_060.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cv5sd2q6b16af" +path="res://.godot/imported/wall_060.png-0b03777e47a0cd59eefe0c1b620f998a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_060.png" +dest_files=["res://.godot/imported/wall_060.png-0b03777e47a0cd59eefe0c1b620f998a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_061.png b/thumbs/wall_061.png new file mode 100644 index 0000000..2b32ba6 Binary files /dev/null and b/thumbs/wall_061.png differ diff --git a/thumbs/wall_061.png.import b/thumbs/wall_061.png.import new file mode 100644 index 0000000..312412d --- /dev/null +++ b/thumbs/wall_061.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bl3kxy3i0hklf" +path="res://.godot/imported/wall_061.png-7662bd0796216e92122954c106d7ce89.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_061.png" +dest_files=["res://.godot/imported/wall_061.png-7662bd0796216e92122954c106d7ce89.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_062.png b/thumbs/wall_062.png new file mode 100644 index 0000000..4544de4 Binary files /dev/null and b/thumbs/wall_062.png differ diff --git a/thumbs/wall_062.png.import b/thumbs/wall_062.png.import new file mode 100644 index 0000000..85366dd --- /dev/null +++ b/thumbs/wall_062.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://33vtilvkfhl" +path="res://.godot/imported/wall_062.png-a2333ed2476f9984836ed6570d18bb01.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_062.png" +dest_files=["res://.godot/imported/wall_062.png-a2333ed2476f9984836ed6570d18bb01.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_063.png b/thumbs/wall_063.png new file mode 100644 index 0000000..7551f37 Binary files /dev/null and b/thumbs/wall_063.png differ diff --git a/thumbs/wall_063.png.import b/thumbs/wall_063.png.import new file mode 100644 index 0000000..c1e362f --- /dev/null +++ b/thumbs/wall_063.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dy1uco0gdpw0y" +path="res://.godot/imported/wall_063.png-0840cd8ba105f347650428a4b00950c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_063.png" +dest_files=["res://.godot/imported/wall_063.png-0840cd8ba105f347650428a4b00950c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_064.png b/thumbs/wall_064.png new file mode 100644 index 0000000..ae41973 Binary files /dev/null and b/thumbs/wall_064.png differ diff --git a/thumbs/wall_064.png.import b/thumbs/wall_064.png.import new file mode 100644 index 0000000..a57415f --- /dev/null +++ b/thumbs/wall_064.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gf12vtq7c7tq" +path="res://.godot/imported/wall_064.png-349125c8c3c84c7f50134d03a077f4c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_064.png" +dest_files=["res://.godot/imported/wall_064.png-349125c8c3c84c7f50134d03a077f4c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_065.png b/thumbs/wall_065.png new file mode 100644 index 0000000..99b2dd5 Binary files /dev/null and b/thumbs/wall_065.png differ diff --git a/thumbs/wall_065.png.import b/thumbs/wall_065.png.import new file mode 100644 index 0000000..957ae68 --- /dev/null +++ b/thumbs/wall_065.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d087j5h5wkuta" +path="res://.godot/imported/wall_065.png-d5471e783912ac590992b27d0c36ac23.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_065.png" +dest_files=["res://.godot/imported/wall_065.png-d5471e783912ac590992b27d0c36ac23.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_066.png b/thumbs/wall_066.png new file mode 100644 index 0000000..2451ec5 Binary files /dev/null and b/thumbs/wall_066.png differ diff --git a/thumbs/wall_066.png.import b/thumbs/wall_066.png.import new file mode 100644 index 0000000..c3eee4c --- /dev/null +++ b/thumbs/wall_066.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brhftds4yts0y" +path="res://.godot/imported/wall_066.png-6fb790fa35c2a764df381aa82a818eaa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_066.png" +dest_files=["res://.godot/imported/wall_066.png-6fb790fa35c2a764df381aa82a818eaa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_067.png b/thumbs/wall_067.png new file mode 100644 index 0000000..b417f58 Binary files /dev/null and b/thumbs/wall_067.png differ diff --git a/thumbs/wall_067.png.import b/thumbs/wall_067.png.import new file mode 100644 index 0000000..432c98b --- /dev/null +++ b/thumbs/wall_067.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://nly86scuolck" +path="res://.godot/imported/wall_067.png-89dde7c230123a94a0cdf39a159c7afb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_067.png" +dest_files=["res://.godot/imported/wall_067.png-89dde7c230123a94a0cdf39a159c7afb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_068.png b/thumbs/wall_068.png new file mode 100644 index 0000000..66d76dc Binary files /dev/null and b/thumbs/wall_068.png differ diff --git a/thumbs/wall_068.png.import b/thumbs/wall_068.png.import new file mode 100644 index 0000000..9d294c6 --- /dev/null +++ b/thumbs/wall_068.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duh3kumdeynpe" +path="res://.godot/imported/wall_068.png-86a71fe0c0fed7b06bae0646a3a66cf9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_068.png" +dest_files=["res://.godot/imported/wall_068.png-86a71fe0c0fed7b06bae0646a3a66cf9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_069.png b/thumbs/wall_069.png new file mode 100644 index 0000000..0640e24 Binary files /dev/null and b/thumbs/wall_069.png differ diff --git a/thumbs/wall_069.png.import b/thumbs/wall_069.png.import new file mode 100644 index 0000000..9c01cb9 --- /dev/null +++ b/thumbs/wall_069.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7d4qh1u4m13r" +path="res://.godot/imported/wall_069.png-df9da5ba51074ba6bd37d832d3217e8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_069.png" +dest_files=["res://.godot/imported/wall_069.png-df9da5ba51074ba6bd37d832d3217e8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_070.png b/thumbs/wall_070.png new file mode 100644 index 0000000..d05b5d3 Binary files /dev/null and b/thumbs/wall_070.png differ diff --git a/thumbs/wall_070.png.import b/thumbs/wall_070.png.import new file mode 100644 index 0000000..13bd77c --- /dev/null +++ b/thumbs/wall_070.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b430r88s7n4pr" +path="res://.godot/imported/wall_070.png-2e7662f117a46e477a788cc880a8b775.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_070.png" +dest_files=["res://.godot/imported/wall_070.png-2e7662f117a46e477a788cc880a8b775.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_071.png b/thumbs/wall_071.png new file mode 100644 index 0000000..cfadff0 Binary files /dev/null and b/thumbs/wall_071.png differ diff --git a/thumbs/wall_071.png.import b/thumbs/wall_071.png.import new file mode 100644 index 0000000..864bf5d --- /dev/null +++ b/thumbs/wall_071.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgcjm0epv3qkp" +path="res://.godot/imported/wall_071.png-b70359800c74e5edc5be0c7196471436.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_071.png" +dest_files=["res://.godot/imported/wall_071.png-b70359800c74e5edc5be0c7196471436.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_072.png b/thumbs/wall_072.png new file mode 100644 index 0000000..b40480b Binary files /dev/null and b/thumbs/wall_072.png differ diff --git a/thumbs/wall_072.png.import b/thumbs/wall_072.png.import new file mode 100644 index 0000000..abcc0e7 --- /dev/null +++ b/thumbs/wall_072.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cewasyojsk17n" +path="res://.godot/imported/wall_072.png-2ad893f4e196bb08701bbd37a5a055df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_072.png" +dest_files=["res://.godot/imported/wall_072.png-2ad893f4e196bb08701bbd37a5a055df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_073.png b/thumbs/wall_073.png new file mode 100644 index 0000000..8cee78c Binary files /dev/null and b/thumbs/wall_073.png differ diff --git a/thumbs/wall_073.png.import b/thumbs/wall_073.png.import new file mode 100644 index 0000000..ef9456b --- /dev/null +++ b/thumbs/wall_073.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqe2wcli55th0" +path="res://.godot/imported/wall_073.png-83ec490132b1fd9ff3ed7ed6e1229aaa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_073.png" +dest_files=["res://.godot/imported/wall_073.png-83ec490132b1fd9ff3ed7ed6e1229aaa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_074.png b/thumbs/wall_074.png new file mode 100644 index 0000000..323c2b9 Binary files /dev/null and b/thumbs/wall_074.png differ diff --git a/thumbs/wall_074.png.import b/thumbs/wall_074.png.import new file mode 100644 index 0000000..21e95ab --- /dev/null +++ b/thumbs/wall_074.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qlsxw6w5bcpl" +path="res://.godot/imported/wall_074.png-cfadd592d81af1d2213e9c26541e93c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_074.png" +dest_files=["res://.godot/imported/wall_074.png-cfadd592d81af1d2213e9c26541e93c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wall_100.png b/thumbs/wall_100.png new file mode 100644 index 0000000..5d5148f Binary files /dev/null and b/thumbs/wall_100.png differ diff --git a/thumbs/wall_100.png.import b/thumbs/wall_100.png.import new file mode 100644 index 0000000..4f5ee7e --- /dev/null +++ b/thumbs/wall_100.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dioish4lg282" +path="res://.godot/imported/wall_100.png-8bab351ae8e296ce26ecad69dfe5f0cd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wall_100.png" +dest_files=["res://.godot/imported/wall_100.png-8bab351ae8e296ce26ecad69dfe5f0cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/water_tower_001.png b/thumbs/water_tower_001.png new file mode 100644 index 0000000..5226d4c Binary files /dev/null and b/thumbs/water_tower_001.png differ diff --git a/thumbs/water_tower_001.png.import b/thumbs/water_tower_001.png.import new file mode 100644 index 0000000..40177e2 --- /dev/null +++ b/thumbs/water_tower_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbyyca7ufkc04" +path="res://.godot/imported/water_tower_001.png-e97c3e1c2f4cdb90d647705735ae75d6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/water_tower_001.png" +dest_files=["res://.godot/imported/water_tower_001.png-e97c3e1c2f4cdb90d647705735ae75d6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/water_tower_002.png b/thumbs/water_tower_002.png new file mode 100644 index 0000000..a60a073 Binary files /dev/null and b/thumbs/water_tower_002.png differ diff --git a/thumbs/water_tower_002.png.import b/thumbs/water_tower_002.png.import new file mode 100644 index 0000000..6ad256e --- /dev/null +++ b/thumbs/water_tower_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqerb5dftnhky" +path="res://.godot/imported/water_tower_002.png-b651c93dcb12ea354ad7f3e281a6ca10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/water_tower_002.png" +dest_files=["res://.godot/imported/water_tower_002.png-b651c93dcb12ea354ad7f3e281a6ca10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/water_tower_003.png b/thumbs/water_tower_003.png new file mode 100644 index 0000000..ea4263f Binary files /dev/null and b/thumbs/water_tower_003.png differ diff --git a/thumbs/water_tower_003.png.import b/thumbs/water_tower_003.png.import new file mode 100644 index 0000000..9025fad --- /dev/null +++ b/thumbs/water_tower_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0a5mo4f3t4w6" +path="res://.godot/imported/water_tower_003.png-1ed09037e9181bd68d2fac6bea8dede2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/water_tower_003.png" +dest_files=["res://.godot/imported/water_tower_003.png-1ed09037e9181bd68d2fac6bea8dede2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/water_tower_004.png b/thumbs/water_tower_004.png new file mode 100644 index 0000000..78a501e Binary files /dev/null and b/thumbs/water_tower_004.png differ diff --git a/thumbs/water_tower_004.png.import b/thumbs/water_tower_004.png.import new file mode 100644 index 0000000..60cd090 --- /dev/null +++ b/thumbs/water_tower_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7qo223o3c0id" +path="res://.godot/imported/water_tower_004.png-83f9f8eba088227103f17cfe52e2d894.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/water_tower_004.png" +dest_files=["res://.godot/imported/water_tower_004.png-83f9f8eba088227103f17cfe52e2d894.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wheel_001.png b/thumbs/wheel_001.png new file mode 100644 index 0000000..52530a4 Binary files /dev/null and b/thumbs/wheel_001.png differ diff --git a/thumbs/wheel_001.png.import b/thumbs/wheel_001.png.import new file mode 100644 index 0000000..2ccc56d --- /dev/null +++ b/thumbs/wheel_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6evv8d2xbmok" +path="res://.godot/imported/wheel_001.png-1a25545ed8be48cbc0780b179b828059.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wheel_001.png" +dest_files=["res://.godot/imported/wheel_001.png-1a25545ed8be48cbc0780b179b828059.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wheel_002.png b/thumbs/wheel_002.png new file mode 100644 index 0000000..baf1f43 Binary files /dev/null and b/thumbs/wheel_002.png differ diff --git a/thumbs/wheel_002.png.import b/thumbs/wheel_002.png.import new file mode 100644 index 0000000..0aae0f0 --- /dev/null +++ b/thumbs/wheel_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdvlrgqd2xfk" +path="res://.godot/imported/wheel_002.png-36f402c273455057d3505c7e36b934e0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wheel_002.png" +dest_files=["res://.godot/imported/wheel_002.png-36f402c273455057d3505c7e36b934e0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wheel_003.png b/thumbs/wheel_003.png new file mode 100644 index 0000000..70a2640 Binary files /dev/null and b/thumbs/wheel_003.png differ diff --git a/thumbs/wheel_003.png.import b/thumbs/wheel_003.png.import new file mode 100644 index 0000000..20d8676 --- /dev/null +++ b/thumbs/wheel_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cetyew4wx62hy" +path="res://.godot/imported/wheel_003.png-76c4dfce1b2773d3ae64093206b71d8b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wheel_003.png" +dest_files=["res://.godot/imported/wheel_003.png-76c4dfce1b2773d3ae64093206b71d8b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wheel_004.png b/thumbs/wheel_004.png new file mode 100644 index 0000000..3188e8d Binary files /dev/null and b/thumbs/wheel_004.png differ diff --git a/thumbs/wheel_004.png.import b/thumbs/wheel_004.png.import new file mode 100644 index 0000000..fc5d444 --- /dev/null +++ b/thumbs/wheel_004.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1t3f3sgiu15d" +path="res://.godot/imported/wheel_004.png-a0711a78e020f0781b8804d92f5087bf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wheel_004.png" +dest_files=["res://.godot/imported/wheel_004.png-a0711a78e020f0781b8804d92f5087bf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wheel_005.png b/thumbs/wheel_005.png new file mode 100644 index 0000000..8787074 Binary files /dev/null and b/thumbs/wheel_005.png differ diff --git a/thumbs/wheel_005.png.import b/thumbs/wheel_005.png.import new file mode 100644 index 0000000..d15c32f --- /dev/null +++ b/thumbs/wheel_005.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0l0vc16t8oy8" +path="res://.godot/imported/wheel_005.png-4f1e42cc47d439a1e5578dd2a422d372.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wheel_005.png" +dest_files=["res://.godot/imported/wheel_005.png-4f1e42cc47d439a1e5578dd2a422d372.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wire_001.png b/thumbs/wire_001.png new file mode 100644 index 0000000..6dc044e Binary files /dev/null and b/thumbs/wire_001.png differ diff --git a/thumbs/wire_001.png.import b/thumbs/wire_001.png.import new file mode 100644 index 0000000..20d1609 --- /dev/null +++ b/thumbs/wire_001.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cu5lejlaa6bb8" +path="res://.godot/imported/wire_001.png-ec8bc1c7b9acdbba2ac1393ed0398d12.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wire_001.png" +dest_files=["res://.godot/imported/wire_001.png-ec8bc1c7b9acdbba2ac1393ed0398d12.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wire_002.png b/thumbs/wire_002.png new file mode 100644 index 0000000..499f97e Binary files /dev/null and b/thumbs/wire_002.png differ diff --git a/thumbs/wire_002.png.import b/thumbs/wire_002.png.import new file mode 100644 index 0000000..0b958ba --- /dev/null +++ b/thumbs/wire_002.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cn2co73gakqqt" +path="res://.godot/imported/wire_002.png-ea6f3513581dbf00195cc2ca794b7405.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wire_002.png" +dest_files=["res://.godot/imported/wire_002.png-ea6f3513581dbf00195cc2ca794b7405.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/thumbs/wire_003.png b/thumbs/wire_003.png new file mode 100644 index 0000000..f8823c5 Binary files /dev/null and b/thumbs/wire_003.png differ diff --git a/thumbs/wire_003.png.import b/thumbs/wire_003.png.import new file mode 100644 index 0000000..f99a359 --- /dev/null +++ b/thumbs/wire_003.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhsml5w8kxhgk" +path="res://.godot/imported/wire_003.png-f80c594db88f591bdf12a8d4e78a092e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://core/thumbs/wire_003.png" +dest_files=["res://.godot/imported/wire_003.png-f80c594db88f591bdf12a8d4e78a092e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/ui/help_text.gd b/ui/help_text.gd new file mode 100644 index 0000000..06d32b9 --- /dev/null +++ b/ui/help_text.gd @@ -0,0 +1,97 @@ +extends RefCounted +## Kurzhilfe-Overlay (Karte t_11b5c25b, Karte 3/3) — H schaltet das Panel an, +## H/Esc aus. Der Text spiegelt docs/tasten.md (Source of truth der Shortcut- +## Karten) als Kompakt-Tabelle für den Editor-Bildschirm. Bei neuen Tasten: +## docs/tasten.md UND hier pflegen. +## +## BBCode-Tabelle ([table=2]): Taste | Wirkung. Bei neuen Tasten die Zeile in +## der passenden Sektion ergänzen (text() aufbauen, _rows() macht die Zellen). + +static func text() -> String: + var out := "" + out += _header("Kamera & Steuerung") + out += _table(_rows([ + ["Mittlere Maustaste ziehen", "Orbit: um die Karte drehen"], + ["Mittlere Maustaste + Umschalt", "Pan: Karte verschieben"], + ["Mausrad", "Zoom"], + ["Rechte Maustaste halten", "Freelook-Flug in jedem Werkzeug: Maus = Umsehen, W/A/S/D = Bewegen, Leertaste hoch, Umschalt runter"], + ["Strg / Alt (im Flug)", "schneller (×3) / langsamer (×0,3); Mausrad ändert die Grundgeschwindigkeit"], + ["Umschalt+F", "Flugmodus dauerhaft an/aus"], + ["F", "Auswahl ins Bild zentrieren"], + ["L", "Höhenlinien fixieren (sonst nur im Terrain-Werkzeug)"], + ["H", "diese Hilfe an/aus (Esc schließt auch)"], + ])) + out += _header("Objekte auswählen & platzieren (F2)") + out += _table(_rows([ + ["Linksklick", "Objekt wählen; Umschalt/Strg+Klick: Mehrfachauswahl"], + ["Rahmen aufziehen", "alle Objekte im Rahmen wählen (Umschalt: hinzufügen)"], + ["Bibliothek: Klick / Doppelklick", "Objekt auswählen / Platzieren starten; Klick in die Karte setzt, Esc bricht ab"], + ["G", "greifen: Objekt hängt an der Maus (Klick setzt, Esc zurück)"], + ["Pfeiltasten", "im 2-m-Raster verschieben"], + ["Bild↑ / Bild↓", "0,25 m anheben / absenken; Strg+Bild↑ = auf den Boden setzen"], + ["Strg halten", "Snap-Zwang auf ganze Meter"], + ["Strg+D", "duplizieren"], + ["Entf", "löschen"], + ["Strg+G / Strg+Umschalt+G", "Gruppieren / Gruppe lösen"], + ["Esc", "abbrechen, dann Auswahl aufheben"], + ])) + out += _header("Objekte verschieben, drehen, kippen (Werkzeugleiste links oben in der 3D-Ansicht)") + out += _table(_rows([ + ["Verschieben", "Pfeile am Objekt ziehen (X rot, Y grün, Z blau); Mausrad ändert die Schrittweite"], + ["Drehen", "Ringe am Objekt ziehen: um alle drei Achsen, in 15°-Schritten; R = 90° um Y (Umschalt+R = −90°)"], + ["Kippen", "Strg+R / Strg+Umschalt+R = um X ±15°; Alt+R / Alt+Umschalt+R = um Z ±15° (auch beim Platzieren)"], + ["Spiegeln", "an der Stelle links↔rechts oder vor↔zurück (Panel Auswahl)"], + ["Strg+Z / Strg+Y", "jeder Zug ist ein Undo-Schritt"], + ])) + out += _header("Terrain formen (F1)") + out += _table(_rows([ + ["1 / 2 / 3", "Heben / Senken / Glätten"], + ["5 / 6", "Einebnen (Höhe vom Strichstart) / Rampe (Start klicken, ziehen, loslassen)"], + ["[ / ]", "Pinselgröße Klein / Mittel / Groß"], + ["Linksklick ziehen", "Boden formen; Strg+Klick = Gegenteil (Heben ↔ Senken)"], + ["Tablet/Controller", "Stärke folgt dem Druck (Maus = voll)"], + ])) + out += _header("Malen (4)") + out += _table(_rows([ + ["Material wählen", "Tab Malen: Kacheln mit Vorschau"], + ["Linksklick ziehen", "Material auf den Boden malen; Strg+Klick malt Steppe zurück"], + ["Kante", "Regler: hart ↔ weich auslaufend"], + ])) + out += _header("Bauen (F3)") + out += _table(_rows([ + ["Teil im Tab Bauen", "wählen, dann in die Karte klicken — Wände rasten an die Zellkante, Böden/Dächer in die Zellmitte"], + ["Klick auf Wandoberkante", "stapelt das nächste Geschoss; Klick auf die Seite ersetzt das Teil (Wand → Tür)"], + ["R / Esc", "Teil umdrehen bzw. drehen / Bauen beenden"], + ["Gebäude", "jedes Haus ist eine Gruppe: „Ganzes Gebäude auswählen“, kopieren, löschen im Tab Bauen"], + ])) + out += _header("Spawn-Zonen (F4) und Test-Start (T)") + out += _table(_rows([ + ["F4: Linksklick", "Zone setzen; Ziehen verschiebt, Entf löscht; Team und Radius im Tab Prüfen"], + ["T: Linksklick", "Test-Startpunkt setzen (nur für F5, kein Spawn)"], + ["F5", "▶ Karte testen"], + ])) + out += _header("Datei & Rückgängig") + out += _table(_rows([ + ["Strg+Z", "Rückgängig"], + ["Strg+Y / Strg+Umschalt+Z", "Wiederherstellen"], + ["Strg+S / Strg+Umschalt+S", "Speichern / Speichern unter"], + ["Strg+O / Strg+N", "Öffnen / Neue Map"], + ["Datei → Hand-Haltung der Waffen", "öffnet das Tool für Hände und Finger pro Waffe (eigenes Fenster)"], + ["Datei → Updates prüfen", "prüft auf eine neue Version; Banner „Jetzt updaten“ lädt und startet neu"], + ])) + return out + + +static func _header(title: String) -> String: + return "\n[color=#8cc9f2][b]%s[/b][/color]\n\n" % title + + +static func _table(rows: String) -> String: + return "[table=2]%s[/table]\n" % rows + + +static func _rows(pairs: Array) -> String: + var out := "" + for pair in pairs: + out += "[cell][b]%s[/b][/cell][cell]%s[/cell]" % [str(pair[0]), str(pair[1])] + return out diff --git a/ui/help_text.gd.uid b/ui/help_text.gd.uid new file mode 100644 index 0000000..c1a819e --- /dev/null +++ b/ui/help_text.gd.uid @@ -0,0 +1 @@ +uid://cmq500ivk0ro2 diff --git a/ui/inspector_panel.gd b/ui/inspector_panel.gd new file mode 100644 index 0000000..629dfa8 --- /dev/null +++ b/ui/inspector_panel.gd @@ -0,0 +1,700 @@ +extends VBoxContainer +## Objekt-Inspektor (Karte t_bb0d11c9) — Godot/Unity-Style Eigenschaften-Panel +## oben im rechten Panel: Position/Drehung/Größe der markierten Objekte +## TIPPBAR (LineEdit) und ZIEHBAR (am Label ziehen, Godot-Konvention). +## +## Verdrahtung (main.gd): der Inspektor wird in _build_right_panel() als +## ERSTER Abschnitt eingehängt; refresh() läuft in _selection_status und +## _sync_selection_panel — damit füllen ALLE Auswahl-Wege das Panel +## (3D-Klick, Rahmen-Ausziehen, Map-Inhalt-Baum). +## +## Regeln der Karte: +## - Änderungen wirken LIVE im 3D-Fenster (Marker direkt umgesetzt, kein Rebuild). +## - JEDE Änderung ist EIN Strg+Z-Schritt: Tippen = ein Schritt beim Bestätigen +## (Enter/Klick woanders), Ziehen = ein Schritt beim Loslassen. +## - Mehrfachauswahl: Änderungen wirken auf ALLE markierten; ungleiche Werte +## zeigen „—“ und werden erst beim Tippen/Ziehen angeglichen. +## - SCALE IST FEST (Karte t_e166e656, Sly-Wunsch „Realismus“): die scale-Zeile +## ist bewusst ENTFERNT — neue Objekte bekommen immer scale 1.0, alte Karten +## laden mit ihrem gespeicherten scale (Format bleibt kompatibel). Hier gibt +## es nur den lesenden Hinweis „Größe fix“. +## - KIPPEN IN SÄMTLICHEN ACHSEN (Karte t_e166e656): das svmap-Feld `rot` +## (Euler x/y/z, OPTIONAL) trägt X/Z-Kippen; fehlt es, gilt rot_y_deg. +## rot/rot_y_deg bleiben über SvmapIO.rot_setzen synchron. +## - X/Z werden an der Map geklemmt, Y an den Match-Grenzen — Versenken UNTER +## die Terrain-Höhe ist ausdrücklich erlaubt (Steine im Boden), die +## Info-Zeile zeigt die Versenk-Tiefe. + +const EPS := 0.005 # Toleranz „gleicher Wert“ (Anzeige-Konsens) +const MIXED := "—" # Anzeige für ungleiche Werte in der Auswahl + + +func _clamp_norm_deg(f: float) -> float: + return fposmod(f + 180.0, 360.0) - 180.0 + + +class DragValue extends HBoxContainer: + ## Eine Zeile „Label + Wert“: am Label ziehen ändert den Wert live + ## (Godot-Inspector-Konvention), ins LineEdit tippen setzt ihn exakt. + ## Die Logik (Wert lesen/anwenden/Undo) liegt beim Inspektor — dieses + ## Widget ist nur die Eingabe-Maschine und ruft die Callables. + var step := 0.05 # Wert-Änderung pro Pixel beim Ziehen + var fmt := "%.2f" # Anzeige-Format + var suffix := "" + var min_v := -1000000.0 # Zieh-Grenzen (Anzeige + Doc werden geklemmt) + var max_v := 1000000.0 + var get_value: Callable # -> float (Wert des ersten markierten Objekts) + var is_mixed: Callable # -> bool (true = Werte in der Auswahl ungleich) + var begin_live: Callable # Undo-Basis merken (Zieh-Start) + var live_apply: Callable # (float) — live anwenden OHNE Undo-Commit + var end_live: Callable # (final: float, original: float) — Commit beim Loslassen + var commit: Callable # (float) — Tippen: Snapshot + anwenden + EIN Commit + + var label: Label + var edit: LineEdit + var _dragging := false + var _drag_base := 0.0 + var _drag_accum := 0.0 + + func _init() -> void: + add_theme_constant_override("separation", 6) + + func build(p_label: String, tip: String, edit_width: int) -> void: + label = Label.new() + label.text = p_label + # KEINE feste Mindestbreite — die kompakte X/Y/Z-Zeile (3 Widgetpaare + # nebeneinander) darf das Panel NICHT breiter drücken als das Fenster + # (Layout-Bruch bei UI-Größe Groß, Beweis-Probe 19.10.). Natürliche + # Breite von „X“/„Grad“/„Faktor“ reicht als Zieh-Fläche. + label.mouse_filter = Control.MOUSE_FILTER_STOP + label.tooltip_text = tip + label.gui_input.connect(_on_label_input) + add_child(label) + edit = LineEdit.new() + edit.custom_minimum_size = Vector2(edit_width, 0) + edit.select_all_on_focus = true + edit.text_submitted.connect(_on_text_submitted) + edit.focus_exited.connect(_on_focus_exited) + add_child(edit) + + func set_shown(v: float) -> void: + edit.text = (fmt % v) + suffix + + func set_mixed() -> void: + edit.text = "—" + + ## Probes/Tests: Ziehen programmgesteuert ausführen (gleicher Pfad wie Maus). + func probe_drag(pixels: float) -> void: + _start_drag() + _drag_accum += pixels + _apply_drag() + _finish_drag() + + func _start_drag() -> void: + _dragging = true + _drag_accum = 0.0 + _drag_base = float(get_value.call()) + begin_live.call() + + func _apply_drag() -> void: + var v := _clamp(_drag_base + _drag_accum * step) + live_apply.call(v) + set_shown(v) + + func _finish_drag() -> void: + if not _dragging: + return + _dragging = false + end_live.call(_clamp(_drag_base + _drag_accum * step), _drag_base) + + func _clamp(f: float) -> float: + return clampf(f, min_v, max_v) + + func _on_label_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: + if event.pressed: + _start_drag() + else: + _finish_drag() + elif event is InputEventMouseMotion and _dragging: + _drag_accum += event.relative.x + _apply_drag() + + func _parse(text: String) -> float: + var t := text.strip_edges().replace(",", ".") + if t.is_empty() or not t.is_valid_float(): + return NAN + return t.to_float() + + func _apply_text(text: String) -> void: + var v := _parse(text) + if is_nan(v): + set_shown(float(get_value.call())) # „—“/Müll → Anzeige wiederherstellen + return + commit.call(_clamp(v)) + + func _on_text_submitted(text: String) -> void: + _apply_text(text) + + func _on_focus_exited() -> void: + # Nur nachziehen, wenn der Text NICHT frisch bestätigt wurde (sonst + # würde Enter + Klick woanders DOPPELT committen). + if edit.text.is_empty(): + return + if edit.text.begins_with("—"): + return + # Bestätigte Werte wurden beim submitted bereits gesetzt und neu + # formatiert; weicht der Text noch ab (Klick weg ohne Enter), gilt das + # als bestätigt — EIN Undo-Schritt. + _apply_text(edit.text) + + +var _main # core/ui/main.gd (ungewollt typisiert — dynamisch) +var _head: Label +var _sub: Label +var _grid: GridContainer +var _info: Label +var _hint: Label +var _ground_btn: Button +var _farben_grid: GridContainer # Farbfeld-Raster (Karte t_b0edb807) +var _farbe_reset_btn: Button +var _farbe_status: Label +var _rows := {} # field -> DragValue +var _built := false + + +func setup(p_main) -> void: + _main = p_main + add_theme_constant_override("separation", 6) + + _head = Label.new() + _head.text = "Objekt-Inspektor" + _head.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(_head) + + _sub = Label.new() + _sub.text = "" + _sub.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(_sub) + + _hint = Label.new() + _hint.text = "Nichts ausgewählt.\nKlicke im Viewport ein Objekt an." + _hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(_hint) + + _grid = GridContainer.new() + _grid.columns = 1 + _grid.add_theme_constant_override("h_separation", 6) + _grid.add_theme_constant_override("v_separation", 6) + add_child(_grid) + + # --- Position X/Y/Z --- + _grid.add_child(_field("Position")) + var px := _make_row("pos_x", "X", "Position X in Meter — ZIEHEN am Label ändert live, TIPPEN setzt exakt (Enter bestätigt).", 0.05, "%.2f", " m") + var py := _make_row("pos_y", "Y", "Höhe (Fußpunkt) in Meter — ziehen/tippen. UNTER die Terrain-Höhe absenken ist erlaubt (Versenken, Karte t_e166e656), Untergrenze = Match-Grenze.", 0.05, "%.2f", " m") + var pz := _make_row("pos_z", "Z", "Position Z in Meter — ziehen/tippen.", 0.05, "%.2f", " m") + # Zieh-Grenzen grob an der Map orientieren (doc-Apply klemmt exakt). + var half_span: float = 1024.0 + px.min_v = -half_span + px.max_v = half_span + py.min_v = -200.0 # Versenken erlaubt (Karte t_e166e656) — _apply klemmt exakt an bounds.min.y + py.max_v = 80.0 + pz.min_v = -half_span + pz.max_v = half_span + var pos_row := HBoxContainer.new() + pos_row.add_theme_constant_override("separation", 6) + pos_row.add_child(px) + pos_row.add_child(py) + pos_row.add_child(pz) + _grid.add_child(pos_row) + + # --- Drehen & Kippen (Karte t_e166e656) --- + _grid.add_child(_field("Drehen & Kippen")) + _grid.add_child(_make_row("rot_y", "Drehen Y", "Blickrichtung in Grad (−180…180) — ziehen/tippen. Tasten: R (90°), Mausrad (15° fein).", 0.5, "%.1f", "°", -180.0, 180.0)) + _grid.add_child(_make_row("rot_x", "Kippen X", "Kippen um die X-Achse in Grad (−180…180) — vor/zurück neigen. Tasten: Strg+R / Strg+Umschalt+R.", 0.5, "%.1f", "°", -180.0, 180.0)) + _grid.add_child(_make_row("rot_z", "Kippen Z", "Kippen um die Z-Achse in Grad (−180…180) — links/rechts neigen. Tasten: Alt+R / Alt+Umschalt+R.", 0.5, "%.1f", "°", -180.0, 180.0)) + + # --- Größe (FEST, Karte t_e166e656) --- + _grid.add_child(_field("Größe")) + var gross_fix := Label.new() + gross_fix.text = "Größe fix (Original) — Assets bleiben in ihrer echten Größe." + gross_fix.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _grid.add_child(gross_fix) + + var btn_row := HBoxContainer.new() + btn_row.add_theme_constant_override("separation", 6) + _ground_btn = Button.new() + _ground_btn.text = "Auf Boden setzen" + _ground_btn.custom_minimum_size = UIScale.v2(0, 40) + _ground_btn.tooltip_text = "Alle markierten Objekte exakt auf die Terrain-Höhe setzen (Strg+Bild↑ geht auch) — EIN Rückgängig-Schritt" + _ground_btn.pressed.connect(_on_ground) + btn_row.add_child(_ground_btn) + _grid.add_child(btn_row) + + # --- Farbe / Material (Karte t_b0edb807) --- + # Feste Palette als Farbfeld-Raster; Klick = Farbe auf ALLE markierten + # Objekte (EIN Undo-Schritt). „Zurücksetzen“ entfernt das tint-Feld → + # wieder die Pack-Farbe (Rückwärtskompatibel, wie vor der Karte). + _grid.add_child(_field("Farbe / Material")) + _farben_grid = GridContainer.new() + _farben_grid.columns = 4 + _farben_grid.add_theme_constant_override("h_separation", 6) + _farben_grid.add_theme_constant_override("v_separation", 6) + _grid.add_child(_farben_grid) + for eintrag in SvmapTint.PALETTE: + _farben_grid.add_child(_swatch(eintrag[1], str(eintrag[0]))) + var farbe_row := HBoxContainer.new() + farbe_row.add_theme_constant_override("separation", 6) + _farbe_reset_btn = Button.new() + _farbe_reset_btn.text = "Zurücksetzen (Pack-Farbe)" + _farbe_reset_btn.custom_minimum_size = UIScale.v2(0, 40) + _farbe_reset_btn.tooltip_text = "Farbe der markierten Objekte entfernen — sie zeigen wieder die Pack-Farbe — EIN Rückgängig-Schritt" + _farbe_reset_btn.pressed.connect(_on_farbe_reset) + farbe_row.add_child(_farbe_reset_btn) + _grid.add_child(farbe_row) + _farbe_status = Label.new() + _farbe_status.text = "" + _grid.add_child(_farbe_status) + + # --- Info (nur lesen) --- + _info = Label.new() + _info.text = "" + _info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(_info) + + # Startzustand: keine Auswahl → nur Hinweis sichtbar (refresh() schaltet um). + _grid.visible = false + _info.visible = false + _ground_btn.visible = false + _built = true + + +func _field(text: String) -> Label: + var l := Label.new() + l.text = text + l.tooltip_text = "Werte gelten für ALLE markierten Objekte. „—“ = Werte sind verschieden und werden erst beim Tippen/Ziehen angeglichen." + return l + + +## Ein Farbfeld-Knopf (Karte t_b0edb807): flacher Farb-Button mit Tooltip. +func _swatch(c: Color, name: String) -> Button: + var btn := Button.new() + btn.custom_minimum_size = UIScale.v2(34, 34) + btn.tooltip_text = "%s — Farbe auf alle markierten Objekte anwenden (EIN Rückgängig-Schritt)" % name + btn.focus_mode = Control.FOCUS_NONE + var sb := StyleBoxFlat.new() + sb.bg_color = c + sb.set_corner_radius_all(6) + btn.add_theme_stylebox_override("normal", sb) + var sb_hover: StyleBoxFlat = sb.duplicate() + sb_hover.bg_color = c.lightened(0.08) # U7: Aufhellung statt Manuell-Mathe + btn.add_theme_stylebox_override("hover", sb_hover) + btn.add_theme_stylebox_override("pressed", sb_hover) + btn.add_theme_stylebox_override("focus", StyleBoxEmpty.new()) + btn.pressed.connect(_on_farbe_gewaehlt.bind(c, name)) + return btn + + +# ------------------------------------------------------------- Farbe/Material --- + +## Palette-Farbe auf ALLE markierten Objekte — EIN Undo-Schritt. +func _on_farbe_gewaehlt(c: Color, name: String) -> void: + var indices := _selected() + if indices.is_empty(): + return + var before: Dictionary = _main.history.snapshot(["objects"]) + var objects: Array = _main.doc.get("objects", []) + var changed := false + for i in indices: + var obj: Dictionary = objects[i] + var alt := SvmapTint.von(obj) + SvmapTint.setze(obj, c) + if not SvmapTint.gleich(alt, c): + changed = true + if not changed: + return # gleiche Farbe erneut → kein Leer-Schritt + _commit_common("Inspektor: Farbe %s" % name, before) + _main.view_builder.call("tint_refresh", indices) + + +## tint-Feld entfernen → Pack-Farbe — EIN Undo-Schritt. +func _on_farbe_reset() -> void: + var indices := _selected() + if indices.is_empty(): + return + var before: Dictionary = _main.history.snapshot(["objects"]) + var objects: Array = _main.doc.get("objects", []) + var changed := false + for i in indices: + var obj: Dictionary = objects[i] + if SvmapTint.hat(obj): + SvmapTint.entferne(obj) + changed = true + if not changed: + return # nichts hatte eine Farbe → kein Leer-Schritt + _commit_common("Inspektor: Farbe zurückgesetzt (Pack-Farbe)", before) + _main.view_builder.call("tint_refresh", indices) + + +## Status-Zeile der Farbsektion: Pack-Farbe / Palettenname / eigene Farbe / +## gemischt (Mehrfachauswahl mit verschiedenen Farben). +func _farbe_text(objs: Array) -> String: + if objs.is_empty(): + return "" + if objs.size() == 1: + var obj: Dictionary = objs[0] + if not SvmapTint.hat(obj): + return "Farbe: Pack-Farbe" + var c := SvmapTint.von(obj) + var pn := SvmapTint.palette_name(c) + return "Farbe: %s" % (pn if pn != "" else "eigene Farbe") + # Mehrfachauswahl: alle ohne Feld = Pack-Farbe, alle gleich = Name, sonst gemischt. + var first_hat := SvmapTint.hat(objs[0]) + var first_c := SvmapTint.von(objs[0]) + for obj in objs: + if SvmapTint.hat(obj) != first_hat: + return "Farbe: gemischt" + if first_hat and not SvmapTint.gleich(SvmapTint.von(obj), first_c): + return "Farbe: gemischt" + if not first_hat: + return "Farbe: Pack-Farbe" + var pn2 := SvmapTint.palette_name(first_c) + return "Farbe: %s" % (pn2 if pn2 != "" else "eigene Farbe") + + +func _make_row(field: String, lbl: String, tip: String, step: float, fmt: String, suffix: String, + min_v := -1000000.0, max_v := 1000000.0) -> DragValue: + var w := DragValue.new() + w.step = step + w.fmt = fmt + w.suffix = suffix + w.min_v = min_v + w.max_v = max_v + w.get_value = _get_value.bind(field) + w.is_mixed = _is_mixed.bind(field) + w.begin_live = _begin_live + w.live_apply = _live_apply.bind(field) + w.end_live = _end_live.bind(field) + w.commit = _commit.bind(field) + w.build(lbl, tip, UIScale.px(64)) + _rows[field] = w + return w + + +# ------------------------------------------------------------- Datenzugriff -- + +func _selected() -> Array: + if _main == null or _main.get("interaction") == null: + return [] + var objects: Array = _main.doc.get("objects", []) + var out: Array = [] + for i in _main.interaction.selected_indices: + if i is int and i >= 0 and i < objects.size(): + out.append(i) + return out + + +func _sel_objects() -> Array: + var objects: Array = _main.doc.get("objects", []) + var out: Array = [] + for i in _selected(): + var obj = objects[i] + if obj is Dictionary: + out.append(obj) + return out + + +func _entry(obj: Dictionary) -> Dictionary: + var manifest: Dictionary = _main.get("manifest") + if manifest == null or manifest.is_empty(): + return {} + var lib: Dictionary = manifest.get("objects", {}) + return lib.get(str(obj.get("id", "")), {}) + + +func _field_value(obj: Dictionary, field: String) -> float: + match field: + "pos_x": + var p: Array = obj.get("pos", [0.0, 0.0, 0.0]) + return float(p[0]) if (p as Array).size() == 3 else 0.0 + "pos_y": + var p: Array = obj.get("pos", [0.0, 0.0, 0.0]) + return float(p[1]) if (p as Array).size() == 3 else 0.0 + "pos_z": + var p: Array = obj.get("pos", [0.0, 0.0, 0.0]) + return float(p[2]) if (p as Array).size() == 3 else 0.0 + "rot_y": + return _clamp_norm_deg(SvmapIO.objekt_rotation(obj).y) + "rot_x": + return _clamp_norm_deg(SvmapIO.objekt_rotation(obj).x) + "rot_z": + return _clamp_norm_deg(SvmapIO.objekt_rotation(obj).z) + return 0.0 + + +func _get_value(field: String) -> float: + var objs := _sel_objects() + if objs.is_empty(): + return 0.0 + return _field_value(objs[0], field) + + +func _is_mixed(field: String) -> bool: + var objs := _sel_objects() + if objs.size() < 2: + return false + var first := _field_value(objs[0], field) + for obj in objs: + if absf(_field_value(obj, field) - first) > EPS: + return true + return false + + +# ------------------------------------------------------------ Anzeige (refresh) -- + +func refresh() -> void: + if not _built or _main == null or _main.doc.is_empty(): + return + var indices := _selected() + var objects: Array = _main.doc.get("objects", []) + var has := not indices.is_empty() + _grid.visible = has + _ground_btn.visible = has + _info.visible = has + _hint.visible = not has + if not has: + _head.text = "Objekt-Inspektor" + _sub.text = "" + return + + var objs := _sel_objects() + var m := objs.size() + # Kopf: Name(n) + ID + Kategorie + „N von M markiert“ + Poly. + var names := PackedStringArray() + for obj in objs: + if names.size() < 3: + names.append(str(LibraryBrowser.object_label(str(obj.get("id", "?"))))) + var more := " … +%d" % (m - 3) if m > 3 else "" + var name_txt := (" · ".join(names) + more) if m > 1 else str(LibraryBrowser.object_label(str(objs[0].get("id", "?")))) + _head.text = name_txt + var kat := "?" + var oid := str((objs[0] as Dictionary).get("id", "")) + var entry: Dictionary = _entry(objs[0]) + if not entry.is_empty(): + kat = str(entry.get("kategorie", "?")) + var poly := 0.0 + for obj in objs: + var e: Dictionary = _entry(obj) + poly += float(e.get("poly", 0)) * float(obj.get("scale", 1.0)) * float(obj.get("scale", 1.0)) + _sub.text = "ID: %s · Kategorie: %s · %d von %d Objekten markiert · Poly: %d" % [ + oid if m == 1 else "%s …" % oid, kat, m, objects.size(), int(poly)] + + for field in ["pos_x", "pos_y", "pos_z", "rot_y", "rot_x", "rot_z"]: + var w: DragValue = _rows[field] + if w.edit.has_focus(): + continue # nicht unter dem tippenden Nutzer wegziehen + if _is_mixed(field): + w.set_mixed() + else: + w.set_shown(_get_value(field)) + + # Farbsektion (Karte t_b0edb807): aktueller Farbstand der Auswahl. + _farbe_status.text = _farbe_text(objs) + + # Info-Block (nur lesen). + var lift := ObjectOps.lift_offset(_main.doc, indices[0]) + var lift_txt: String + if absf(lift) < ObjectOps.FLOAT_EPSILON: + lift_txt = "am Boden" + elif lift > 0.0: + lift_txt = "%+.2f m über dem Boden" % lift + else: + lift_txt = "%.2f m im Boden versenkt" % -lift + var e0: Dictionary = _entry(objs[0]) + var col: Dictionary = e0.get("collision", {}) + var col_size: Array = col.get("size", []) + var col_txt := "—" + if (col_size as Array).size() == 3: + var s := float(objs[0].get("scale", 1.0)) + col_txt = "%s %s × %s × %s m" % [ + ("Box" if str(col.get("shape", "box")) == "box" else str(col.get("shape", "?"))), + ("%.1f" % (float(col_size[0]) * s)), ("%.1f" % (float(col_size[1]) * s)), ("%.1f" % (float(col_size[2]) * s))] + var echt := _echt_modell(indices[0]) + _info.text = "Fußpunkt: y = %.2f m (%s)\nKollision: %s\nEchtes Modell: %s" % [ + float((objs[0] as Dictionary).get("pos", [0.0, 0.0, 0.0])[1]) if ((objs[0] as Dictionary).get("pos", [0.0, 0.0, 0.0]) as Array).size() == 3 else 0.0, + lift_txt, col_txt, "ja" if echt else "nein (Platzhalter)"] + + +func _echt_modell(idx: int) -> bool: + var markers: Array = _main.view_builder.object_markers + if idx < markers.size(): + var marker = markers[idx] + if marker != null and is_instance_valid(marker) and marker.has_meta("glb"): + return bool(marker.get_meta("glb")) + return not str(_entry(_main.doc["objects"][idx]).get("glb", "")).is_empty() + + +# ------------------------------------------------------------ Anwenden + Undo -- + +var _live_before: Dictionary = {} # Undo-Basis während des Ziehens +var _live_changed := false # hat das Ziehen das Doc wirklich geändert? + +func _begin_live() -> void: + _live_before = _main.history.snapshot(["objects"]) + _live_changed = false + +## Achtung Callable.bind hängt die gebundenen Argumente HINTER die +## Aufruf-Argumente: commit.call(v) → _commit(v, field). +func _live_apply(v: float, field: String) -> void: + if _apply(field, v): + _live_changed = true + +func _end_live(final: float, original: float, field: String) -> void: + if not _live_changed or absf(final - original) < 0.0001: + return # Ziehen ohne Änderung → kein Leer-Undo-Schritt + _commit_common("Inspektor: %s" % _desc_for(field, final), _live_before) + +func _commit(v: float, field: String) -> void: + var before: Dictionary = _main.history.snapshot(["objects"]) + if not _apply(field, v): + return # nichts geändert (alles geklemmt/identisch) → kein Leer-Schritt + _commit_common("Inspektor: %s" % _desc_for(field, v), before) + + +func _commit_common(desc: String, before: Dictionary) -> void: + _main.history.commit(desc, before, _main.history.snapshot(["objects"])) + _main.dirty = true + _main.call("_refresh_file_label") + _main.call("_refresh_stats") + _main.call("_refresh_history_buttons") + _main.call("_refresh_spawn_status") + _main.call("_set_status", "%s — Strg+Z macht es in EINEM Schritt rückgängig." % desc) + refresh() + + +func _desc_for(field: String, v: float) -> String: + var n := _sel_objects().size() + var mehr := "%d Objekte: " % n if n > 1 else "" + match field: + "pos_x": + return "%sPosition X = %.2f m" % [mehr, v] + "pos_y": + return "%sHöhe (Y) = %.2f m" % [mehr, v] + "pos_z": + return "%sPosition Z = %.2f m" % [mehr, v] + "rot_y": + return "%sDrehen (Y) = %.1f°" % [mehr, v] + "rot_x": + return "%sKippen (X) = %.1f°" % [mehr, v] + "rot_z": + return "%sKippen (Z) = %.1f°" % [mehr, v] + return "Inspektor-Änderung" + + +## Wert auf ALLE markierten Objekte anwenden + 3D-Ansicht live aktualisieren. +## false = nichts geändert (keine Auswahl / alles geklemmt). +func _apply(field: String, v: float) -> bool: + var indices := _selected() + if indices.is_empty(): + return false + var objects: Array = _main.doc.get("objects", []) + var half: Vector2 = _terrain_half() + var max_y := _max_y() + var min_y := _min_y() + var changed := 0 + # Position-Felder bei Mehrfachauswahl: der eingetippte Wert gilt für das + # ERSTE markierte Objekt (Anker), alle anderen wandern mit DEMSELBEN Delta + # mit → Abstände bleiben erhalten (Relativ-Move, wie beim Greifen). + # Bei 1 Objekt ist das Delta = absoluter Wert → Verhalten unverändert. + var rel_delta := 0.0 + if field == "pos_x" or field == "pos_y" or field == "pos_z": + var anchor_pos: Array = (objects[indices[0]] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + if (anchor_pos as Array).size() == 3: + var comp := 0 if field == "pos_x" else (1 if field == "pos_y" else 2) + rel_delta = v - float(anchor_pos[comp]) + for i in indices: + var obj: Dictionary = objects[i] + var pos: Array = obj.get("pos", [0.0, 0.0, 0.0]) + if (pos as Array).size() != 3: + continue + var x := float(pos[0]) + var y := float(pos[1]) + var z := float(pos[2]) + if field == "rot_y" or field == "rot_x" or field == "rot_z": + # Karte t_e166e656: rot/rot_y_deg bleiben über rot_setzen synchron + # (reines Y → kein rot-Feld in der Datei). Anzeige normalisiert + # auf −180…180, gespeichert wird der Wert wie getippt. + var euler := SvmapIO.objekt_rotation(obj) + var neu := Vector3(euler.x, euler.y, euler.z) + if field == "rot_y": + neu.y = _clamp_norm_deg(v) + elif field == "rot_x": + neu.x = _clamp_norm_deg(v) + else: + neu.z = _clamp_norm_deg(v) + SvmapIO.rot_setzen(obj, neu) + changed += 1 + continue + if field == "pos_x": + x = clampf(float(pos[0]) + rel_delta, -half.x, half.x) + elif field == "pos_y": + # Karte t_e166e656: Versenken erlaubt — Untergrenze ist die + # Match-Grenze (bounds.min.y), NICHT mehr die Terrain-Höhe. + y = clampf(float(pos[1]) + rel_delta, min_y, max_y) + elif field == "pos_z": + z = clampf(float(pos[2]) + rel_delta, -half.y, half.y) + if absf(x - float(pos[0])) > 0.0001 or absf(y - float(pos[1])) > 0.0001 or absf(z - float(pos[2])) > 0.0001: + obj["pos"] = [x, y, z] + changed += 1 + _sync_markers(indices) + return changed > 0 + + +func _terrain_half() -> Vector2: + var terrain: Dictionary = _main.doc.get("terrain", {}) + return Vector2(float(terrain.get("size_x_m", 1024.0)), float(terrain.get("size_z_m", 1024.0))) * 0.5 + + +func _max_y() -> float: + var bounds: Dictionary = _main.doc.get("bounds", {}) + if (bounds.get("max", null) is Array) and ((bounds["max"] as Array).size() == 3): + return float(bounds["max"][1]) + return 80.0 + + +## Untere Match-Grenze (Karte t_e166e656): Versenken im Boden ist erlaubt, +## klemmt an bounds.min.y statt an der Terrain-Höhe. +func _min_y() -> float: + var bounds: Dictionary = _main.doc.get("bounds", {}) + if (bounds.get("min", null) is Array) and ((bounds["min"] as Array).size() == 3): + return float(bounds["min"][1]) + return -80.0 + + +func _sync_markers(indices: Array) -> void: + var markers: Array = _main.view_builder.object_markers + var objects: Array = _main.doc.get("objects", []) + for i in indices: + if i >= markers.size(): + continue + var marker = markers[i] + if marker == null or not is_instance_valid(marker): + continue + var pos: Array = (objects[i] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + if (pos as Array).size() == 3: + marker.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + marker.rotation_degrees = SvmapIO.objekt_rotation(objects[i] as Dictionary) + + +# ------------------------------------------------------------------- Knöpfe -- + +func _on_ground() -> void: + # Bewährte Boden-Logik (interaction.snap_selection_to_floor): snapshotet, + # committet selbst EINEN Schritt und meldet in der Statuszeile. + _main.interaction.call("snap_selection_to_floor") + refresh() + + +## Für Tests/Probes: Widget zu einem Feld holen ("pos_x"…"rot_z"). +func widget(field: String) -> DragValue: + return _rows.get(field) diff --git a/ui/inspector_panel.gd.uid b/ui/inspector_panel.gd.uid new file mode 100644 index 0000000..3076705 --- /dev/null +++ b/ui/inspector_panel.gd.uid @@ -0,0 +1 @@ +uid://dndmcn006xggw diff --git a/ui/main.gd b/ui/main.gd new file mode 100644 index 0000000..8a91d07 --- /dev/null +++ b/ui/main.gd @@ -0,0 +1,2920 @@ +extends Control +## ScopedVR Map-Editor — Hauptfenster (M2 UX-Fundament, Karte t_40d855d0; +## U1-Modularisierung, Karte t_eaccbfed). +## +## U1 (Unity-artiges Layout-Fundament): diese Datei ist jetzt ein DÜNNER +## KOORDINATOR — Dokument/History, Werkzeug-Umschaltung, Shortcuts, Dialoge, +## Update, Playtest. Die Oberflächen-Bereiche wohnen in eigenen Modulen unter +## core/ui/panels/ und melden sich DATENGETRIEBEN in +## core/ui/panels/registry.gd an; das Layout wird ausschließlich aus der +## Registry gebaut (Operator-Vorgabe 20.10. — die Umbau-Karten U2–U7 ändern +## je nur IHR Modul + eine Registry-Zeile, nie wieder main.gd). +## +## Layout (Schaubild in /opt/data/scopedvr/KARTEN_UI_UNITY_vc27.md): +## Toolbar oben · links Tab "Map-Inhalt" · Mitte 3D-Ansicht · +## rechts Tabs (Inspektor/Auswahl/Map/Terrain/Prüfen) · +## unten Assets (Objekt-Bibliothek) · Statuszeile. +## Alles über HSplit/HSplit/VSplit größenverstellbar; die Splitter-Lage +## bleibt in user://scopedvr_editor.cfg ([fenster] Abschnitt) gemerkt. +## +## Farben/Namen kommen aus theme/editor_theme.tres (einzige Farbquelle). +## +## UX-Fundament (Chef-Grundsatz Sly — map-editor-README): +## 1. Undo/Redo (Strg+Z / Strg+Y) über ActionHistory — spätere Werkzeuge +## (Terrain, Spawn-Zonen) melden sich mit snapshot/commit einfach an. +## 2. Ghost-Preview: Platzier-Vorschau schwebt transparent am Cursor +## (interaction.gd), Klick setzt das Objekt. +## 3. Auswahl-Anfassen: Klick wählt, Ziehen verschiebt, +## R ODER Mausrad dreht (15°-Raster), Entf löscht. +## 4. Helfende Meldungen: Klartext + DER NÄCHSTE SCHRITT, keine Godot-Texte. +## 5. Deutsch & groß: Schrift ≥ 14 px, klickbare Flächen ≥ 40 px Höhe. + +const APP_TITLE := "ScopedVR Map-Editor" +# Shortcut-Infrastruktur (Karte t_c46d31c2): InputMap-Actions + Esc-Priorität. +const ShortcutKeys := preload("res://core/editor/shortcut_keys.gd") +const ShortcutPolicy := preload("res://core/editor/shortcut_policy.gd") +# Shortcuts-Mathe Karte 2/3 (t_55257792): Pfeil-Raster + Pinsel-Presets. +const MoveMath := preload("res://core/editor/move_math.gd") +# Kurzhilfe-Overlay Karte 3/3 (t_11b5c25b): Gesamte Tastenliste als BBCode. +const HelpText := preload("res://core/ui/help_text.gd") +# Zentrale UI-Skalierung (Karte t_297221b5): EIN Faktor für Schrift/Panels/Zeilen. +const UIScale := preload("res://core/ui/ui_scale.gd") +# U1 (t_eaccbfed): die Registry ist die EINZIGE Layout-Quelle. +const PanelRegistryScript := preload("res://core/ui/panels/registry.gd") +# Editor-Theme (U1): eine Theme-Ressource als einzige Farbquelle. +const THEME_PATH := "res://core/theme/editor_theme.tres" + +var manifest: Dictionary = {} +var doc: Dictionary = {} # das Map-Dictionary (1:1 svmap-Format) +var current_path := "" # leer = noch nie gespeichert +var dirty := false +var grid_snap := false # Raster-Snap (1 m) an/aus — Objekt-Werkzeuge links + +var history := ActionHistory.new() +var interaction # core/editor/interaction.gd (script-erweitert → untypisiert) +var viewport_toolbar # core/ui/viewport_toolbar.gd (Buttons oben links in der 3D-Ansicht) +var gizmo # core/viewport/gizmo.gd (Achsen-Pfeile, Kind von interaction) +var gizmo_step_m := 0.5 # Gizmo-Schrittweite in m (Panel-Feld; überlebt UI-Rebuilds) +var terrain_tool # core/editor/terrain_tool.gd (Pinsel, Karte t_9aba04f1) +var spawn_tool # core/editor/spawn_tool.gd (Zonen, Karte t_c4162c5d) +var tool_mode := "objects" # "objects" | "terrain" | "spawns" | "teststart" (Bauen = Objekt-Platzieren mit Rastern) + +# Symmetrie-Modus (Karte t_f5935023, M1): Modus + Toolbar-Schalter +# + Statuszeilen-Anzeige. Standard = Punktspiegelung (Sly 20.10.). M1 legt nur +# Modus + Anzeige fest; das Spiegeln selbst kommt mit M2–M4. +var sym_mode: int = SymmetryCore.Mode.POINT + +# UI-Referenzen (Koordinator-Sicht auf die Module — Panels bauen selbst) +var status_label: Label +var file_label: Label +var help_label: Label +var view_builder: Node3D +var camera: Camera3D +var inspector: Control # Objekt-Inspektor (Karte t_bb0d11c9) — im Inspektor-Tab + +# U1-Module (aus der Registry gebaut; Toolbar/Status/Kern-Widget-Referenzen +# bleiben hier, damit Tests/Tools denselben Vertrag sehen wie vorher) +var toolbar_mod # core/ui/panels/toolbar.gd +var status_mod # core/ui/panels/status_bar.gd +var selection_mod # core/ui/panels/selection_panel.gd (nur Logik, kein Tab mehr) +var settings_mod # core/ui/panels/settings_panel.gd (im Einstellungen-Fenster) +var drop_overlay: Control # Ablege-Fläche über der 3D-Ansicht (nur während Drag aktiv) +var settings_dialog: AcceptDialog # Fenster „Einstellungen“ (Knopf in der Kopfleiste) +var sym_mod # core/ui/panels/symmetry_panel.gd (im Map-Tab) +var map_mod # core/ui/panels/map_panel.gd +var terrain_mod # core/ui/panels/terrain_panel.gd +var malen_mod # core/ui/panels/malen_panel.gd (Malen-Tab) +var bauen_mod # core/ui/panels/bauen_panel.gd (Bauen-Tab) +var check_mod # core/ui/panels/check_panel.gd +var tree_mod # core/ui/panels/scene_tree_panel.gd +var assets_mod # core/ui/panels/assets_panel.gd +var content_tree: Tree # weiter in tree_mod (Bequemlichkeits-Referenz) +var stats_objects: Label # weiter in status_mod +var stats_terrain: Label # weiter in status_mod +var tool_label: Label # weiter in status_mod +var sym_label: Label # weiter in status_mod +var undo_btn: Button # weiter in toolbar_mod +var redo_btn: Button # weiter in toolbar_mod +var grid_btn: CheckButton # weiter in settings_mod (Einstellungen-Fenster) +var contours_btn: CheckButton # weiter in settings_mod +var size_menu: OptionButton # weiter in settings_mod +var sym_menu: OptionButton # weiter in settings_mod +var prop_name: LineEdit # weiter in map_mod +var gizmo_step_box: SpinBox # weiter in settings_mod +var library_search: LineEdit # weiter in assets_mod +var library_buttons: Dictionary = {} # weiter in assets_mod (Bequemlichkeit) +var _library_group_rows: Array = [] # weiter in assets_mod (Bequemlichkeit) +var spawn_status: Label # weiter in check_mod +var terrain_info: Label # weiter in map_mod +var terrain_mode_lower: Button # weiter in terrain_mod (Test-Vertrag) +var terrain_size_btn_small: Button # weiter in terrain_mod +var terrain_size_btn_medium: Button # weiter in terrain_mod +var terrain_size_btn_large: Button # weiter in terrain_mod +var terrain_palette: VBoxContainer # weiter in terrain_mod +var terrain_radius_slider: HSlider # weiter in terrain_mod +var terrain_radius_label: Label # weiter in terrain_mod +var terrain_strength_slider: HSlider # weiter in terrain_mod +var terrain_mat_buttons: Array = [] # weiter in terrain_mod (Gras/Sand/Fels) +var library_open_all_btn: Button # weiter in assets_mod +var library_close_all_btn: Button # weiter in assets_mod + +# Toolbar-/Panel-Zustand (gehört dem Koordinator) +var _grid_on := false # Nutzer-Wahl für das Maßstabsgitter +# Höhenlinien (Karte t_89c0ec95): _contours_on ist die NUTZER-Wahl „fixiert +# an“ (L-Taste oder Button). Ohne Fix erscheinen die Linien NUR im +# Terrain-Werkzeug (F1) — Sly-Befund 17.10.: dauerhaft sichtbar störte. +# Effektiv = _contours_fix AN ODER tool_mode == "terrain". +var _contours_on := false + +# --- Test-Start (Karte t_96e3c4ee / T1b) ------------------------------------- +# main hält den ZUSTAND und die Haken (Vertrag mit tests/test_playtest_spawn.gd); +# das Panel-Modul test_start_panel.gd zeigt ihn nur an (U1-Muster). +var test_start_point: Variant = null # Vector3 (nur x/z zählt) oder null +var test_start_tool # core/editor/playtest/test_start_tool.gd +var test_start_panel: Control # core/ui/panels/test_start_panel.gd (von der Prüfsuite erwartet) +var test_start_btn: Button # „🎯 Test-Start" in der Leiste (Umschalter) +var _test_start_active := false +var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder + +# Rahmen-Ausziehen (Rubberband) — Overlay über dem 3D-Viewport +var rubber_overlay: Control +var rubber_rect := Rect2() # leer = Overlay aus + +# Playtest (Karte t_1cdc1a72) + Lauf-Modus-Panelverweise +var playtest: Node = null # core/editor/playtest/playtest_mode.gd Instanz +var _playtest_tool_mode := "" # gemerkter Werkzeug-Modus für die Rückkehr +var ui_top_bar: Control # Top-Bar (im Lauf-Modus aus) +var ui_left_panel: Control # linke Spalte (im Lauf-Modus aus) +var ui_right_panel: Control # rechte Tab-Spalte (im Lauf-Modus aus) +var ui_status_bar: Control # Statusleiste (im Lauf-Modus aus) +var ui_dock_bottom: Control # Assets-Bereich unten (im Lauf-Modus aus) +var world3d: Node3D # 3D-Wurzel im SubViewport (Spieler lebt hier) + +# Karte 3/3 (t_11b5c25b): Dirty-Schutz + Kurzhilfe + Werkzeug-Statuszeile. +var dirty_dialog: ConfirmationDialog # „Ungespeicherte Änderungen verwerfen?“ +var resign_dialog: ConfirmationDialog # „Trotzdem laden (Prüfsumme erneuern)“ (t_9814f18f) +var _resign_pending_path: String = "" +var _pending_after_dirty := Callable() # was nach „Verwerfen“ ausgeführt wird +var help_overlay: PanelContainer # H-Kurzhilfe (nicht-blockierend) +var help_overlay_title: Label +var help_overlay_text: RichTextLabel + +# Editor-Selbstupdate (Karte t_d95f087a): Start-Check + Banner + „Jetzt updaten“ +var update_banner: PanelContainer # Leiste „🔄 Update verfügbar …“ (versteckt) +var update_label: Label +var update_btn: Button # „Jetzt updaten“ im Banner +var check_thread: Thread # Update-Check im Hintergrund (Start/Menü) +var update_thread: Thread # Update-Ausführung (pull + Import-Lauf) +var _update_check_busy := false +var _hand_pose_thread: Thread = null +var _update_running := false +var _update_result: Dictionary = {} # letzter Check (Status/Banner-Nachfrage) + +# Dialoge +var new_dialog: AcceptDialog +var open_dialog: FileDialog +var save_dialog: FileDialog +var error_dialog: AcceptDialog +var resize_dialog: ConfirmationDialog # „Kartengröße ändern“ (R3, t_c863b93b) +var error_text: Label + +# Map-Check (Karte prüfen) +var check_dialog: AcceptDialog +var check_text: Label + +# Update-Check-Ergebnis (Menü „🔄 Updates prüfen“, Sly 20.10.: nur die +# Statuszeile las sich wie „tut nichts“ → kleines Ergebnis-Fenster). +var update_dialog: AcceptDialog +var update_text: Label + +# Karte testen (Karte t_b84af40f) +var test_btn: Button + +# U1-Dock-Zustand: Splitter-Lagen ([fenster]-Abschnitt, gemerkt) +var _dock_offsets: Dictionary = {} +var _split_lagen_playtest: Dictionary = {} # Playtest: Trenner-Lagen (Sly-Befund 20.10.) + +const GizmoScript := preload("res://core/viewport/gizmo.gd") +const INSPECTOR_PANEL := preload("res://core/ui/inspector_panel.gd") + + +func _ready() -> void: + # InputMap-Actions der Tastatur-Shortcuts (Karte t_c46d31c2) — VOR UI/Kamera, + # damit die Kamera-Polls (Flug W/A/S/D + Leertaste/Shift) sofort kennen. + ShortcutKeys.register() + manifest = _load_manifest() + _build_ui() + _update_tool_label() + # Symmetrie-Anzeige initialisieren (Karte t_f5935023, M1): Standard = Punkt. + _update_sym_ui() + # Fenster-Zustand (Karte t_297221b5): Default MAXIMIERT (kein Vollbild); + # wer auf Fenstergröße umschaltet, bekommt die gemerkte Lage wieder. + # Close-Request selbst behandeln, damit die Lage VOR dem Quit gemerkt wird. + get_tree().set_auto_accept_quit(false) + _apply_window_state() + _load_dock_state() + _start_with_new_map() + _maybe_start_demo_mode() + # Selbst-Update (Karte t_d95f087a): STILLER Start-Check im Hintergrund — + # nur bei „Update verfügbar“ erscheint das Banner; offline/Tunnel weg + # bleibt alles ruhig (kein Banner, keine Meldung). + _startup_update_check() + + +func _load_manifest() -> Dictionary: + var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(SvmapIO.MANIFEST_PATH)) + if parsed is Dictionary: + LibraryBrowser.setze_manifest(parsed) + return parsed + push_error("Bibliothek library/objects.json konnte nicht geladen werden!") + return {} + + +# ------------------------------------------------------------------ UI-Bau -- +## Layout aus der Registry (U1, Operator-Vorgabe): Top-Bar → Update-Banner → +## Haupt-WSplit (links Tabs | Mitte 3D | rechts Tabs) → unten Assets → +## Statuszeile. Kein Panel wird hier fest verdrahtet — Registry ändert das +## Layout, main.gd bleibt gleich. + +func _build_ui() -> void: + var theme_res: Theme = load(THEME_PATH) + theme_res.default_font_size = UIScale.font_size() + self.theme = theme_res + + var vbox := VBoxContainer.new() + vbox.set_anchors_preset(Control.PRESET_FULL_RECT) + vbox.add_theme_constant_override("separation", UIScale.px(4)) + add_child(vbox) + + # --- Top-Bar (Modul toolbar.gd) --- + var top := PanelContainer.new() + # _make_module hängt das Modul selbst an den Container (doppelt add_child + # würde „already has a parent“ werfen). + var top_h: Control = _make_module(toolbar_script(), top) + vbox.add_child(top) + ui_top_bar = top # Lauf-Modus (t_1cdc1a72): Top-Bar ausblenden + toolbar_mod = top_h + _pull_toolbar_refs() + _update_banner_build(vbox) + + # --- Hauptbereich: links Tabs | Mitte 3D | rechts Tabs --- + var split := HSplitContainer.new() + split.size_flags_vertical = Control.SIZE_EXPAND_FILL + split.size_flags_horizontal = Control.SIZE_EXPAND_FILL + split.split_offset = UIScale.px(-320) + split.name = "MainSplit" + vbox.add_child(split) + + # Lauf-Modus (t_1cdc1a72): linke/rechte Spalte als Member — werden im + # Lauf-Modus ausgeblendet, die 3D-Ansicht (Mitte) bleibt stehen. + var left_tabs := _build_group_tabs("links") + left_tabs.name = "LeftTabs" + ui_left_panel = left_tabs + split.add_child(left_tabs) + split.add_child(_build_center_viewport()) + var right_tabs := _build_group_tabs("rechts") + right_tabs.name = "RightTabs" + ui_right_panel = right_tabs + right_tabs.tab_changed.connect(_on_right_tab_changed) + split.add_child(right_tabs) + _build_map_sections() + + # --- Assets unten (Registry-Gruppe "unten", U1 Auftragspunkt 4) --- + # Die Objekt-Bibliothek wohnt jetzt UNTER dem Hauptbereich (Unity- + # Projekt-Fenster-Platz). Höhe größenverstellbar über den VSplit und + # gemerkt in [fenster] (dock_unten). + var bottom_wrap := VSplitContainer.new() + bottom_wrap.size_flags_vertical = Control.SIZE_EXPAND_FILL + bottom_wrap.size_flags_horizontal = Control.SIZE_EXPAND_FILL + bottom_wrap.name = "BottomSplit" + # WICHTIG: den Haupt-Split ERST aus der VBox trennen, dann in den VSplit + # hängen — add_child auf ein Kind mit Parent ist ein stiller Baum-Fehler + # (Node hängt dann im Nichts: Slider/Skript-Signale laufen nicht). + vbox.remove_child(split) + # VSplit-Kind 1: der Haupt-Split (links|3D|rechts) — er wächst. + split.size_flags_vertical = Control.SIZE_EXPAND_FILL + bottom_wrap.add_child(split) + # VSplit-Kind 2: die Assets-Tabs. + var bottom_tabs := _build_group_tabs("unten") + bottom_tabs.name = "BottomTabs" + bottom_tabs.custom_minimum_size = UIScale.v2(0, 300) + var bottom_panel := PanelContainer.new() + bottom_panel.add_child(bottom_tabs) + bottom_wrap.add_child(bottom_panel) + # Der VSplit ersetzt den Platz des bisherigen Haupt-Splits in der VBox. + vbox.add_child(bottom_wrap) + ui_dock_bottom = bottom_wrap + + # --- Statusleiste (Modul status_bar.gd) --- + var status_panel := PanelContainer.new() + var status_h: Control = _make_module(status_bar_script(), status_panel) + vbox.add_child(status_panel) + ui_status_bar = status_panel # Lauf-Modus (t_1cdc1a72): Statusleiste aus + status_mod = status_h + _pull_status_refs() + status_label = status_mod.status_label + file_label = toolbar_mod.file_label + + _build_dialogs_and_overlays() + + +func toolbar_script() -> GDScript: + return load("res://core/ui/panels/toolbar.gd") + + +func status_bar_script() -> GDScript: + return load("res://core/ui/panels/status_bar.gd") + + +## Ein Modul (EditorPanel-Ableger) instanziieren und bauen; Rückgabe = das +## Panel selbst (Container). Fehler im Modul-Skript = fette Meldung statt +## stiller null-Referenzen. +func _make_module(script: GDScript, parent: Control) -> Control: + var mod: Control = script.new() + parent.add_child(mod) + if mod.has_method("setup"): + mod.setup(self) + if mod.has_method("build"): + mod.call("build") + return mod + + +func _pull_toolbar_refs() -> void: + undo_btn = toolbar_mod.undo_btn + redo_btn = toolbar_mod.redo_btn + + +func _pull_status_refs() -> void: + stats_objects = status_mod.stats_objects + stats_terrain = status_mod.stats_terrain + tool_label = status_mod.tool_label + sym_label = status_mod.sym_label + status_label = status_mod.status_label + + +## Eine Tab-Gruppe aus der Registry bauen (links/rechts/unten). Jeder Bereich +## erscheint GENAU EINMAL (test_ui_layout prüft das). Die Panels werden per +## preload aus dem Registry-Eintrag erzeugt. +func _build_group_tabs(gruppe: String) -> TabContainer: + var tabs := TabContainer.new() + tabs.size_flags_horizontal = Control.SIZE_EXPAND_FILL + tabs.size_flags_vertical = Control.SIZE_EXPAND_FILL + tabs.add_theme_constant_override("separation", UIScale.px(8)) + var entries: Array = PanelRegistryScript.entries(gruppe) + for entry in entries: + var script: GDScript = load(str(entry["skript"])) + var panel: Control = script.new() + panel.name = str(entry["id"]) + panel.setup(self) + # Der Alt-Inspektor (t_bb0d11c9) baut in setup() — build() ist dort + # nicht vorhanden; neue Module bauen in build(). Beides zulassen. + if panel.has_method("build"): + panel.call("build") + tabs.add_child(panel) + tabs.set_tab_title(panel.get_index(), str(entry["titel"])) + _panel_group_built(gruppe, tabs) + return tabs + + +## Gruppen-Nacharbeit: Referenzen ziehen, sobald ihre Tabs stehen. +func _panel_group_built(gruppe: String, tabs: TabContainer) -> void: + match gruppe: + "rechts": + for child in tabs.get_children(): + match child.name: + "inspektor": + inspector = child + "map": + map_mod = child + "terrain": + terrain_mod = child + "malen": + malen_mod = child + "bauen": + bauen_mod = child + prop_name = map_mod.prop_name + terrain_info = map_mod.terrain_info + # Malen-Widgets wohnen im Malen-Tab; das Terrain-Modul hält die + # alten Namen als Aliase (Tests/Werkzeug-Vertrag). + terrain_mod.terrain_palette = malen_mod.palette + terrain_mod.terrain_mat_buttons = malen_mod.mat_buttons + terrain_mod.terrain_weich_label = malen_mod.weich_label + terrain_mod.terrain_weich_slider = malen_mod.weich_slider + terrain_mod.terrain_paint_hint = malen_mod.paint_hint + # Test-/Werkzeug-Vertrag (wie vor der U1-Aufteilung): die Widgets + # wohnen in den Modulen, der Koordinator hält die alten Namen + # als Bequemlichkeits-Referenzen — nichts wird doppelt gebaut. + terrain_mode_lower = terrain_mod.terrain_mode_lower + terrain_size_btn_small = terrain_mod.terrain_size_btn_small + terrain_size_btn_medium = terrain_mod.terrain_size_btn_medium + terrain_size_btn_large = terrain_mod.terrain_size_btn_large + terrain_palette = terrain_mod.terrain_palette + terrain_radius_slider = terrain_mod.terrain_radius_slider + terrain_radius_label = terrain_mod.terrain_radius_label + terrain_strength_slider = terrain_mod.terrain_strength_slider + terrain_mat_buttons = terrain_mod.terrain_mat_buttons + "links": + for child in tabs.get_children(): + if child.name == "szene": + tree_mod = child + content_tree = tree_mod.content_tree + "unten": + for child in tabs.get_children(): + if child.name == "assets": + assets_mod = child + library_search = assets_mod.library_search + library_buttons = assets_mod.library_buttons + _library_group_rows = assets_mod._library_group_rows + library_open_all_btn = assets_mod.library_open_all_btn + library_close_all_btn = assets_mod.library_close_all_btn + + +## Abschnitte im Map-Tab (Registry-Gruppe "map"): Spawn-Zonen, Test-Start-Info, +## Symmetrie. Sie hängen UNTER den Map-Eigenschaften im selben Scroll-Bereich. +func _build_map_sections() -> void: + var host: Control = map_mod.sections_host() + for entry in PanelRegistryScript.entries("map"): + var script: GDScript = load(str(entry["skript"])) + var panel: Control = script.new() + panel.name = str(entry["id"]) + panel.setup(self) + if panel.has_method("build"): + panel.call("build") + host.add_child(HSeparator.new()) + host.add_child(panel) + match str(entry["id"]): + "check": + check_mod = panel + spawn_status = check_mod.spawn_status + "symmetrie": + sym_mod = panel + + +## Einstellungen-Fenster (Knopf ⚙ in der Kopfleiste): Inhalt kommt aus der +## Registry-Gruppe "dialog". Gitter, Höhenlinien, Symmetrie-Modus, UI-Größe, +## Raster-Snap und Gizmo-Schritt wohnen dort (vorher Kopfleiste / Objekt-Werkzeuge). +func _build_settings_dialog() -> void: + settings_dialog = AcceptDialog.new() + settings_dialog.name = "EinstellungenFenster" + settings_dialog.title = "Einstellungen" + settings_dialog.exclusive = false + settings_dialog.ok_button_text = "Schließen" + settings_dialog.min_size = UIScale.v2i(520, 420) + var scroll := ScrollContainer.new() + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + scroll.custom_minimum_size = UIScale.v2(500, 380) + settings_dialog.add_child(scroll) + for entry in PanelRegistryScript.entries("dialog"): + var script: GDScript = load(str(entry["skript"])) + var panel: Control = script.new() + panel.name = str(entry["id"]) + panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + panel.setup(self) + if panel.has_method("build"): + panel.call("build") + scroll.add_child(panel) + settings_mod = panel + add_child(settings_dialog) + grid_btn = settings_mod.grid_btn + contours_btn = settings_mod.contours_btn + size_menu = settings_mod.size_menu + sym_menu = settings_mod.sym_menu + gizmo_step_box = settings_mod.gizmo_step_box + settings_mod.call("refresh") + + +func _open_settings() -> void: + if settings_dialog == null: + return + settings_mod.call("refresh") + # So groß, dass alles ohne Scrollen sichtbar ist (Inhalt + Titel/Knopf), begrenzt aufs Fenster. + var inhalt: Vector2 = settings_mod.get_combined_minimum_size() + var fenster := Vector2(get_window().size) + var breite := clampf(maxf(inhalt.x + UIScale.px(60), UIScale.px(640)), UIScale.px(640), fenster.x * 0.95) + var hoehe := clampf(inhalt.y + UIScale.px(110), UIScale.px(520), fenster.y * 0.95) + settings_dialog.popup_centered(Vector2i(int(breite), int(hoehe))) + + +func _build_dialogs_and_overlays() -> void: + selection_mod = load("res://core/ui/panels/selection_panel.gd").new() + selection_mod.name = "AuswahlLogik" + selection_mod.setup(self) + add_child(selection_mod) + _build_settings_dialog() + # --- Dialoge --- + new_dialog = preload("res://core/ui/new_map_dialog.gd").new() + new_dialog.confirmed.connect(_on_new_map_confirmed) + add_child(new_dialog) + + # R3 (t_c863b93b): „Kartengröße ändern“ — Preview-Dialog, Ausführen als + # EINE Undo-Einheit in _on_resize_confirmed. + resize_dialog = preload("res://core/ui/resize_map_dialog.gd").new() + resize_dialog.confirmed.connect(_on_resize_confirmed) + add_child(resize_dialog) + + open_dialog = FileDialog.new() + open_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE + open_dialog.access = FileDialog.ACCESS_FILESYSTEM + open_dialog.title = "Map öffnen" + open_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"]) + open_dialog.size = UIScale.v2i(800, 560) + open_dialog.file_selected.connect(_on_open_selected) + add_child(open_dialog) + + save_dialog = FileDialog.new() + save_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + save_dialog.access = FileDialog.ACCESS_FILESYSTEM + save_dialog.title = "Map speichern unter" + save_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"]) + save_dialog.size = UIScale.v2i(800, 560) + save_dialog.file_selected.connect(_on_save_selected) + add_child(save_dialog) + + error_dialog = AcceptDialog.new() + error_dialog.title = "Fehler" + error_dialog.ok_button_text = "Alles klar" + error_text = Label.new() + error_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + error_text.custom_minimum_size = UIScale.v2(560, 0) + error_dialog.add_child(error_text) + add_child(error_dialog) + + # Map-Check-Dialog (Karte prüfen, Karte t_c4162c5d). + check_dialog = AcceptDialog.new() + check_dialog.title = "Map-Check" + check_dialog.ok_button_text = "Danke!" + check_text = Label.new() + check_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + check_text.custom_minimum_size = UIScale.v2(620, 0) + check_dialog.add_child(check_text) + add_child(check_dialog) + + # Update-Check-Ergebnis (Sly 20.10.: „tut nichts“ — weil die Rückmeldung + # NUR in der Statuszeile kam und dort schnell wieder weg war). Kleines + # Fenster mit dem Klartext-Ergebnis, Stil wie der Map-Check-Dialog. + update_dialog = AcceptDialog.new() + update_dialog.title = "Update-Check" + update_dialog.ok_button_text = "Alles klar" + update_text = Label.new() + update_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + update_text.custom_minimum_size = UIScale.v2(560, 0) + update_dialog.add_child(update_text) + add_child(update_dialog) + + # Karte 3/3 (t_11b5c25b): Dirty-Schutz vor „Neue Map“ / „Öffnen“ — deutsche + # Klartext-Frage, „Verwerfen“ führt die wartende Aktion aus, „Abbrechen“ tut + # nichts (Map bleibt, wie sie ist). + dirty_dialog = ConfirmationDialog.new() + dirty_dialog.title = "Ungespeicherte Änderungen" + dirty_dialog.dialog_text = "Die Map hat ungespeicherte Änderungen — beim Fortfahren gehen sie verloren.\nJetzt verwerfen und fortfahren?" + dirty_dialog.ok_button_text = "Verwerfen" + dirty_dialog.cancel_button_text = "Abbrechen" + dirty_dialog.confirmed.connect(_on_dirty_confirmed) + add_child(dirty_dialog) + + # Fremdformat-Dialog (t_9814f18f): Lade-Fehler NUR an der Prüfsumme (V13), + # Kern-Validierung grün → „Trotzdem laden (Prüfsumme erneuern)“ statt hartem + # Abbruch; der Editor signiert das Dokument dann mit dem v2-Schema neu. + resign_dialog = ConfirmationDialog.new() + resign_dialog.title = "Prüfsumme stimmt nicht" + resign_dialog.dialog_text = "Der Inhalt der Map ist intakt, aber die Prüfsumme stimmt nicht (die Datei wurde von einem fremden Programm geschrieben oder anders formatiert).\n\nTrotzdem laden? Die Prüfsumme wird beim nächsten Speichern erneuert." + resign_dialog.ok_button_text = "Trotzdem laden (Prüfsumme erneuern)" + resign_dialog.cancel_button_text = "Abbrechen" + resign_dialog.confirmed.connect(_on_resign_confirmed) + add_child(resign_dialog) + + # Kurzhilfe-Overlay (Karte 3/3): H schaltet es an, H/Esc aus. Kein modal + # Dialog — der Editor bleibt bedienbar, das Panel liegt nur obenauf. + help_overlay = PanelContainer.new() + var overlay_style := StyleBoxFlat.new() + # U7: Farben über das Theme (Editor/colors/help_bg, help_border). + overlay_style.bg_color = get_theme_color("help_bg", "Editor") + overlay_style.border_color = get_theme_color("help_border", "Editor") + overlay_style.set_border_width_all(2) + overlay_style.set_corner_radius_all(8) + overlay_style.set_content_margin_all(UIScale.px(14)) + help_overlay.add_theme_stylebox_override("panel", overlay_style) + # Zentriertes Panel statt Vollbild: der Editor bleibt rundherum bedienbar + # (nicht-blockierend), das Panel ist groß genug für die ganze Tabelle. + # Grow BOTH: das Panel wächst SYMMETRISCH um den Mittelpunkt (sonst hängt + # seine linke obere Ecke am Zentrum und es läuft rechts/unten aus dem Bild). + help_overlay.set_anchors_and_offsets_preset(Control.PRESET_CENTER) + help_overlay.grow_horizontal = Control.GROW_DIRECTION_BOTH + help_overlay.grow_vertical = Control.GROW_DIRECTION_BOTH + help_overlay.custom_minimum_size = UIScale.v2(880, 640) + help_overlay.visible = false + var overlay_box := VBoxContainer.new() + overlay_box.add_theme_constant_override("separation", UIScale.px(6)) + help_overlay.add_child(overlay_box) + help_overlay_title = Label.new() + help_overlay_title.text = "Hilfe — Werkzeuge, Steuerung und Tastenkürzel (H oder Esc schließt)" + help_overlay_title.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + help_overlay_title.add_theme_color_override("font_color", get_theme_color("accent", "Editor")) + overlay_box.add_child(help_overlay_title) + help_overlay_text = RichTextLabel.new() + help_overlay_text.bbcode_enabled = true + help_overlay_text.size_flags_vertical = Control.SIZE_EXPAND_FILL + help_overlay_text.custom_minimum_size = UIScale.v2(0, 0) + help_overlay_text.text = HelpText.text() + overlay_box.add_child(help_overlay_text) + add_child(help_overlay) + + +## Ziehen aus der Objekt-Bibliothek: Vorschau folgt der Maus, Loslassen setzt. +func _viewport_can_drop(at_position: Vector2, data: Variant) -> bool: + if not (data is Dictionary) or not (data as Dictionary).has("svr_oid") or doc.is_empty() or assets_mod == null: + return false + assets_mod.drag_hover(str(data["svr_oid"]), at_position) + return true + + +func _viewport_drop(at_position: Vector2, data: Variant) -> void: + if data is Dictionary and (data as Dictionary).has("svr_oid") and assets_mod != null: + assets_mod.drag_drop(str(data["svr_oid"]), at_position) + + +func _build_center_viewport() -> SubViewportContainer: + var container := SubViewportContainer.new() + container.stretch = true + container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + container.size_flags_vertical = Control.SIZE_EXPAND_FILL + # Objekt-Kachel aus der Bibliothek in die Szene ziehen (25.10.). + container.set_drag_forwarding(Callable(), _viewport_can_drop, _viewport_drop) + + var viewport := SubViewport.new() + viewport.own_world_3d = true + viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS + container.add_child(viewport) + + var world := Node3D.new() + viewport.add_child(world) + world3d = world # Lauf-Modus (t_1cdc1a72): Spieler-Body lebt in diesem Baum + + view_builder = Node3D.new() + view_builder.set_script(load("res://core/viewport/map_view_builder.gd")) + world.add_child(view_builder) + + camera = Camera3D.new() + camera.set_script(load("res://core/viewport/orbit_camera.gd")) + camera.current = true + world.add_child(camera) + + interaction = Node3D.new() + interaction.set_script(load("res://core/editor/interaction.gd")) + world.add_child(interaction) + interaction.setup(self, camera, view_builder) + # Achsen-Gizmo (Karte t_e0120dc2) als KIND von interaction — Kinder + # bekommen _unhandled_input ZUERST, es fängt Pfeil-Klicks ab bevor + # interaction sie sieht. interaction.gd bleibt dadurch unverändert. + gizmo = GizmoScript.new() + gizmo.setup(self, camera, view_builder) + interaction.add_child(gizmo) + camera.set("wheel_filter", Callable(interaction, "wheel_blocked")) + # Freelook-Flug-Verdrahtung (Karte t_c46d31c2): Status-Meldungen der + # Kamera laufen ins Statusfeld. Karte t_2470de89: die rechte Maustaste + # gehört IMMER dem Flug — kein Filter mehr, die Werkzeuge fassen RMB + # nicht mehr an. + camera.set("status_cb", Callable(self, "_set_status")) + + # Terrain-Pinsel (Karte t_9aba04f1) — eigener Eingabe-Knoten im Weltraum. + terrain_tool = Node3D.new() + terrain_tool.set_script(load("res://core/editor/terrain_tool.gd")) + world.add_child(terrain_tool) + terrain_tool.setup(self, camera, view_builder, interaction) + + # Spawn-Zonen-Werkzeug (Karte t_c4162c5d) — ditto eigener Eingabe-Knoten. + spawn_tool = Node3D.new() + spawn_tool.set_script(load("res://core/editor/spawn_tool.gd")) + world.add_child(spawn_tool) + spawn_tool.setup(self, camera, view_builder, interaction) + + + # Test-Start-Werkzeug (T1b, Wiederanbindung 20.10.): Marker in der Karte, + # Klick setzt den Startpunkt. Lebt — wie die übrigen Werkzeuge — in main, + # gelesen wird es von test_start_tool.gd über main.test_start_point. + test_start_tool = Node3D.new() + test_start_tool.set_script(load("res://core/editor/playtest/test_start_tool.gd")) + world.add_child(test_start_tool) + test_start_tool.setup(self, camera, view_builder, interaction) + + # Werkzeugleiste oben links (Verschieben / Drehen & Kippen / Aktionen) + viewport_toolbar = preload("res://core/ui/viewport_toolbar.gd").new() + container.add_child(viewport_toolbar) + viewport_toolbar.setup(self) + + # Ablege-Fläche für Bibliotheks-Kacheln: Godot ruft can_drop/drop NICHT auf einem + # SubViewportContainer auf (Test-Befund 25.10.: Ziehen startete, Loslassen setzte + # nichts — echter Maus-Drag, tests/probe_drag_echt.gd). Eine durchsichtige Fläche + # darüber nimmt den Drop an; sie ist nur WÄHREND eines Ziehens aktiv (sonst + # bleibt die Maus für Kamera/Auswahl frei). + drop_overlay = Control.new() + drop_overlay.name = "AblegeFlaeche" + drop_overlay.set_anchors_preset(Control.PRESET_FULL_RECT) + drop_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE + drop_overlay.set_drag_forwarding(Callable(), _viewport_can_drop, _viewport_drop) + container.add_child(drop_overlay) + + # Bedien-Hinweis im Viewport (Overlay) + help_label = Label.new() + help_label.text = "Linksklick: auswählen · Rahmen: mehrere · R: drehen · Entf: löschen · H: Hilfe" + help_label.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) + help_label.offset_left = UIScale.px(12) + help_label.offset_bottom = UIScale.px(-8) + help_label.offset_top = UIScale.px(-46) + # U7: Hinweis-Farbe über Theme (Editor/colors/muted) statt modulate. + help_label.add_theme_color_override("font_color", get_theme_color("muted", "Editor")) + # Karte t_297221b5: OHNE Umbruch würde die Mindestbreite des langen Texts + # bei Stufe „Groß“ die 3D-Ansicht breiter zwingen als das Fenster ist — + # die rechte Spalte rutschte aus dem Bildschirm. Mit Umbruch bleibt die + # Mindestbreite klein, der Hinweis legt sich bei Bedarf in mehr Zeilen. + help_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + # … und wenn der umgebrochene Text höher wird als der reservierte Streifen, + # wächst er nach OBEN (in die 3D-Ansicht) statt nach unten über die + # Statuszeile (grow-Richtung = das Godot-Mittel gegen Mindestgrößen-Überlauf + # bei Anker-Controls). + help_label.grow_vertical = Control.GROW_DIRECTION_BEGIN + container.add_child(help_label) + + # Rahmen-Ausziehen (Karte t_f6f29d86): halbtransparentes Rechteck über + # dem Viewport — mouse_filter IGNORE, stört keine Maus-Ereignisse. + rubber_overlay = Control.new() + rubber_overlay.set_anchors_preset(Control.PRESET_FULL_RECT) + rubber_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE + rubber_overlay.draw.connect(_draw_rubberband) + rubber_overlay.visible = false + container.add_child(rubber_overlay) + return container + + +## Rahmen-Rechteck aktualisieren (aus interaction.gd, Viewport-Koordinaten). +func _rubberband_update(start_pos: Vector2, cur_pos: Vector2) -> void: + rubber_rect = SelectionMath.rect_from(start_pos, cur_pos) + rubber_overlay.visible = true + rubber_overlay.queue_redraw() + + +## Rahmen-Overlay ausblenden (Release/Abbruch). +func _rubberband_clear() -> void: + rubber_rect = Rect2() + rubber_overlay.visible = false + rubber_overlay.queue_redraw() + + +func _draw_rubberband() -> void: + if rubber_rect.size.x <= 0.0 or rubber_rect.size.y <= 0.0: + return + # U7: Rubberband-Farben über das Theme (Editor/colors/rubber_fill/_line). + rubber_overlay.draw_rect(rubber_rect, get_theme_color("rubber_fill", "Editor"), true) + rubber_overlay.draw_rect(rubber_rect, get_theme_color("rubber_line", "Editor"), false, 2.0) +# ------------------------------------------------------------ Map-Aktionen -- + +func _start_with_new_map() -> void: + doc = SvmapDocument.create_new("Neue Map", 1024.0, "mittag") + current_path = "" + dirty = false + history.begin(doc) + _refresh_all() + _set_status("Willkommen! Links ein Objekt aus der Bibliothek anklicken, dann kannst du es in der Karte platzieren. Hilfe gibt's unten in dieser Leiste.") + + +func _on_menu_action(id: int) -> void: + match id: + 0: # Neue Map — bei ungespeicherten Änderungen erst NACHFRAGEN (Karte 3/3) + _request_lossy(func() -> void: new_dialog.popup_centered()) + 1: # Öffnen — ditto Dirty-Schutz + _request_lossy(func() -> void: open_dialog.popup_centered()) + 2: # Speichern + _save() + 3: # Speichern unter + save_dialog.popup_centered() + 4: # Beenden (Fensterlage vorher merken, Karte t_297221b5) + _quit_editor() + 5: # Karte testen + _on_test_run() + 6: # Kartengröße ändern (R3, t_c863b93b) + _open_resize_dialog() + 7: # Updates prüfen (Selbst-Update, t_d95f087a) + _on_update_check_menu() + 8: # Hand-Haltung der Waffen (Tool im Client-Projekt) + _on_hand_pose_tool() + + +# --------------------------------------------- UI-Größe + Fenster (t_297221b5) -- + +## Popup-Häkchen auf die aktuelle Stufe setzen. +func _sync_size_menu() -> void: + if size_menu == null or not is_instance_valid(size_menu): + return + size_menu.select(UIScale.step_index()) + + +## Neue Stufe gewählt: speichern, kompletten UI-Neuaufbau mit dem neuen +## Faktor, Statusmeldung. Wirkt SOFORT — kein Neustart nötig. +func _on_ui_size_selected(index: int) -> void: + if index < 0 or index >= UIScale.step_count(): + return + if index == UIScale.step_index(): + return + if _update_running: + _set_status("Während eines Updates kann die UI-Größe nicht umgestellt werden — gleich danach nochmal.", true) + _sync_size_menu() + return + UIScale.set_step_index(index) + var settings_war_offen := settings_dialog != null and settings_dialog.visible + _rebuild_ui_for_scale() + if settings_war_offen: + _open_settings.call_deferred() + _set_status("UI-Größe: %s — sofort wirksam und gespeichert (bleibt auch nach Neustart)." % UIScale.step_label(index)) + + +## Kompletter UI-Neuaufbau mit dem aktuellen UIScale-Faktor. Die Map (doc) +## bleibt als Objekt UNBERÜHRT (Undo-History bleibt gültig); nur die Controls +## werden neu gebaut und der Zustand (Werkzeug, Gitter, Höhenlinien, Auswahl) +## wiederhergestellt. +func _rebuild_ui_for_scale() -> void: + var prev_mode := tool_mode + for child in get_children(): + remove_child(child) + child.queue_free() + _build_ui() + _update_tool_label() + _clear_edit_state() + _refresh_all() + _refresh_history_buttons() + # Nutzer-Wahl Gitter/Höhenlinien wiederherstellen (Neubau setzt Defaults). + grid_btn.set_pressed_no_signal(_grid_on) + _sync_grid_to_builder() + contours_btn.set_pressed_no_signal(_contours_on) + _sync_contours_to_builder() + # Werkzeug-Modus wiederherstellen (Neubau startet bei „objects“). + if prev_mode != "objects": + tool_mode = "objects" + _set_tool_mode(prev_mode) + # Update-Banner-Zustand wiederherstellen (Start-Check kannte das Ergebnis). + if str(_update_result.get("status")) == "update_available": + _show_update_banner(str(_update_result.get("local", "")), str(_update_result.get("remote", ""))) + _sync_size_menu() + + +## Fenster beim Start: maximiert (Default, kein Vollbild-Zwang) oder die +## gemerkte Fensterlage wiederherstellen. Headless-Läufe (Tests) haben kein +## Fenster und springen raus. +func _apply_window_state() -> void: + if DisplayServer.get_name() == "headless": + return + var cfg := ConfigFile.new() + var maximiert := true + var groesse := Vector2i(1600, 900) + var position := Vector2i() + var hat_geom := false + if cfg.load(UIScale.cfg_path()) == OK: + maximiert = bool(cfg.get_value(UIScale.WIN_SECTION, "maximiert", true)) + var g: Variant = cfg.get_value(UIScale.WIN_SECTION, "groesse", null) + if g is Array and (g as Array).size() == 2: + groesse = Vector2i(int((g as Array)[0]), int((g as Array)[1])) + hat_geom = true + var p: Variant = cfg.get_value(UIScale.WIN_SECTION, "position", null) + if p is Array and (p as Array).size() == 2: + position = Vector2i(int((p as Array)[0]), int((p as Array)[1])) + if maximiert: + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED) + else: + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + if hat_geom: + DisplayServer.window_set_size(groesse) + DisplayServer.window_set_position(position) + + +## Fensterlage merken (beim Schließen): Modus + Größe + Position. +func _save_window_state() -> void: + if DisplayServer.get_name() == "headless": + return + var win := get_window() + var cfg := ConfigFile.new() + cfg.load(UIScale.cfg_path()) + cfg.set_value(UIScale.WIN_SECTION, "maximiert", win.mode == Window.MODE_MAXIMIZED) + cfg.set_value(UIScale.WIN_SECTION, "groesse", [win.size.x, win.size.y]) + cfg.set_value(UIScale.WIN_SECTION, "position", [win.position.x, win.position.y]) + cfg.save(UIScale.cfg_path()) + + +## Ein Ausgang für beide Beenden-Wege (Menü + Fenster-X). +func _quit_editor() -> void: + _save_window_state() + _save_dock_state() + get_tree().quit() + + +func _notification(what: int) -> void: + if drop_overlay != null and is_instance_valid(drop_overlay): + if what == NOTIFICATION_DRAG_BEGIN: + drop_overlay.mouse_filter = Control.MOUSE_FILTER_STOP + elif what == NOTIFICATION_DRAG_END: + drop_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE + if what == NOTIFICATION_WM_CLOSE_REQUEST: + _quit_editor() + + +# ------------------------------------------------- Kartengröße ändern (R3) -- + +## Dialog öffnen: aktueller Stand + Live-Preview (Verlust-Liste/Warnband) füllen. +func _open_resize_dialog() -> void: + if doc.is_empty(): + _set_status("Erst eine Map anlegen (Datei → Neue Map), dann kann die Größe geändert werden.", true) + return + resize_dialog.setup(doc) + resize_dialog.popup_centered() + + +## „Größe ändern“ bestätigt: resize_apply liefert ein FRISCHES Dict (R1/R2 — +## reine Funktion). Der Resize wird als EINE Undo-Einheit in die History +## gehängt: before = kompletter alter doc-Snapshot, after = kompletter neuer. +## Wichtig: doc bleibt dasselbe Dictionary-Objekt (History hält eine Referenz — +## history.begin(doc) nur bei Neu/Laden) — Blöcke werden IN-PLACE ersetzt, +## wie ActionHistory._restore_block es beim Undo macht. +func _on_resize_confirmed() -> void: + var new_x: float = resize_dialog.get_new_size_x() + var new_z: float = resize_dialog.get_new_size_z() + var drop: bool = resize_dialog.get_drop_outside() + var before := history.snapshot(doc.keys()) + var result := SvmapDocument.resize_apply(doc, new_x, new_z, drop) + if result.is_empty(): + _set_status("Kartengröße ändern fehlgeschlagen — die Map wurde nicht verändert.", true) + return + var old_terrain: Dictionary = doc.get("terrain", {}) + _replace_doc_in_place(result) + dirty = true + history.commit("Kartengröße %d × %d m" % [int(new_x), int(new_z)], + before, history.snapshot(doc.keys())) + _after_structure_change() + # Karte K1 (t_950fddaa): nach Resize die GANZE Karte passend einpassen + # (Kartenmitte aus bounds — frame_map ist weg). + var rb: Dictionary = doc.get("bounds", {}) + var rmn: Array = rb.get("min", []) + var rmx: Array = rb.get("max", []) + if rmn.size() == 3 and rmx.size() == 3: + camera.call("fit_to_bounds", + Vector3(rmn[0], rmn[1], rmn[2]), Vector3(rmx[0], rmx[1], rmx[2])) + var old_sx := float(old_terrain.get("size_x_m", 0.0)) + var old_sz := float(old_terrain.get("size_z_m", 0.0)) + var verb := "verkleinert" if (new_x < old_sx or new_z < old_sz) else "vergrößert" + _set_status("Karte auf %d × %d m %s — Strg+Z stellt den alten Zustand komplett her." % [ + int(new_x), int(new_z), verb]) + + +## Kompletten doc-Inhalt IN-PLACE durch ein frisches Ergebnis-Dict ersetzen +## (Identität von doc und seiner Arrays/Dicts bleibt erhalten — History, +## Tests und Ansichten behalten gültige Referenzen; Muster wie +## ActionHistory._restore_block, aber über ALLE Ober-Blöcke). +func _replace_doc_in_place(result: Dictionary) -> void: + for key in doc.keys(): + if not result.has(key): + doc.erase(key) + for key in result.keys(): + var source: Variant = result[key] + if source is Array: + var arr: Array = doc.get(key, []) + arr.clear() + arr.append_array((source as Array).duplicate(true)) + doc[key] = arr + elif source is Dictionary: + var dict: Dictionary = doc.get(key, {}) + dict.clear() + dict.merge((source as Dictionary).duplicate(true)) + doc[key] = dict + else: + doc[key] = source + + +# --------------------------------------------- Selbst-Update (t_d95f087a) ---- + +## Banner „🔄 Update verfügbar“ (unaufdringliche Leiste unter der Top-Bar) — +## nur sichtbar, wenn ein Check einen Update findet. Offline bleibt alles still. +func _update_banner_build(parent: Container) -> void: + update_banner = PanelContainer.new() + var style := StyleBoxFlat.new() + # U7: Banner-Farben über das Theme (Editor/colors/update_bg/_border). + style.bg_color = get_theme_color("update_bg", "Editor") + style.border_color = get_theme_color("update_border", "Editor") + style.set_border_width_all(2) + style.set_content_margin_all(UIScale.px(8)) + style.set_corner_radius_all(6) + update_banner.add_theme_stylebox_override("panel", style) + var h := HBoxContainer.new() + h.add_theme_constant_override("separation", UIScale.px(14)) + update_banner.add_child(h) + update_label = Label.new() + update_label.add_theme_font_size_override("font_size", UIScale.font_size()) + h.add_child(update_label) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + h.add_child(spacer) + update_btn = Button.new() + update_btn.text = "🔄 Jetzt updaten" + update_btn.custom_minimum_size = UIScale.v2(190, 40) + update_btn.tooltip_text = "Lädt die neue Version (git pull), importiert sie und startet den Editor neu. Bei ungespeicherten Änderungen fragt der Editor vorher." + update_btn.pressed.connect(_on_update_now) + h.add_child(update_btn) + update_banner.visible = false + parent.add_child(update_banner) + + +## Still Start-Check (nur Banner bei „Update verfügbar“ — offline: nichts). +func _startup_update_check() -> void: + _updater_check_async(false) + + +## Hand-Haltungs-Tool starten: eigenes Fenster aus dem Client-Projekt. Nach einem +## neuen Client-Stand erst importieren (im Hintergrund, kann dauern), sonst fehlen +## dem Tool neue Modelle. +func _on_hand_pose_tool() -> void: + if _hand_pose_thread != null: + _set_status("Hand-Haltungs-Tool wird schon vorbereitet…") + return + var cfg := TestRun.load_cfg() + var client := TestRun.find_client(ProjectSettings.globalize_path("res://"), cfg) + if client.is_empty(): + _set_status("Client-Ordner nicht gefunden: er muss neben dem Editor liegen (scopedvr-client) oder in den Einstellungen stehen.", true) + return + var godot := TestRun.find_godot(cfg) + if godot.is_empty(): + _set_status("Godot nicht gefunden: bitte in den Einstellungen eintragen.", true) + return + _set_status("Hand-Haltungs-Tool wird vorbereitet…") + _hand_pose_thread = Thread.new() + _hand_pose_thread.start(_hand_pose_prepare.bind(godot, client)) + + +func _hand_pose_prepare(godot: String, client: String) -> void: + var head := EditorUpdater.git_run(EditorUpdater.git_exe(TestRun.load_cfg()), client, PackedStringArray(["rev-parse", "HEAD"])) + var stand := str(head.get("output", "")).strip_edges() + var cfg := TestRun.load_cfg() + if stand.is_empty() or str(cfg.get_value("test", "hand_pose_import", "")) != stand: + var out: Array = [] + OS.execute(godot, TestRun.import_args(client), out, true) + _hand_pose_started.call_deferred(godot, client, stand) + + +func _hand_pose_started(godot: String, client: String, stand: String) -> void: + _hand_pose_thread.wait_to_finish() + _hand_pose_thread = null + var cfg := TestRun.load_cfg() + cfg.set_value("test", "hand_pose_import", stand) + cfg.save(TestRun.CFG_PATH) + var pid := OS.create_process(godot, TestRun.hand_pose_args(client)) + if pid <= 0: + _set_status("Hand-Haltungs-Tool ließ sich nicht starten.", true) + return + _set_status("Hand-Haltungs-Tool läuft in einem eigenen Fenster.") + + +## Datei-Menü „🔄 Updates prüfen“ (ID 7): gleicher Check mit Ergebnis-Meldung. +func _on_update_check_menu() -> void: + if _update_check_busy: + _set_status("Update-Check läuft schon…") + return + _set_status("Prüfe auf Updates…") + _updater_check_async(true) + + +func _updater_check_async(report: bool) -> void: + _update_check_busy = true + check_thread = Thread.new() + check_thread.start(_updater_check_thread.bind(report)) + + +func _updater_check_thread(report: bool) -> void: + var result := EditorUpdater.check_update( + EditorUpdater.git_exe(TestRun.load_cfg()), + ProjectSettings.globalize_path("res://")) + _update_result = result + call_deferred("_updater_check_done", result, report) + + +## Klartext-Ergebnis-Fenster für den manuellen Update-Check (Sly 20.10.: +## Statuszeile allein las sich wie „tut nichts“). Nur bei MANUELLEM Check +## (report=true), der Still-Start-Check bleibt still. +func _show_update_popup(text: String) -> void: + if update_dialog == null or not is_instance_valid(update_dialog): + return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen + update_text.text = text + update_dialog.popup_centered() + + +func _updater_check_done(result: Dictionary, report: bool) -> void: + _update_check_busy = false + _join(check_thread) + check_thread = null + match str(result.get("status")): + "update_available": + _show_update_banner(str(result["local"]), str(result["remote"])) + if report: + _set_status("🔄 Update verfügbar: %s → %s — im Banner „Jetzt updaten“ klicken." % [ + EditorUpdater.short_sha(str(result["local"])), + EditorUpdater.short_sha(str(result["remote"]))]) + _show_update_popup("🔄 Update verfügbar: %s → %s.\n\nIm Banner oben „Jetzt updaten“ klicken — der Editor holt sich den Stand, baut ihn und startet neu." % [ + EditorUpdater.short_sha(str(result["local"])), + EditorUpdater.short_sha(str(result["remote"]))]) + "up_to_date": + if report: + _set_status("Du bist auf dem neusten Stand (%s)." % EditorUpdater.short_sha(str(result["local"]))) + _show_update_popup("Du bist auf dem neusten Stand (%s).\n\nAktuelle Version ist installiert — es gibt nichts zu tun." % EditorUpdater.short_sha(str(result["local"]))) + "unreachable": + if report: + _set_status("Update-Check nicht erreichbar (offline oder Tunnel weg) — später nochmal probieren.", true) + _show_update_popup("Update-Check nicht erreichbar.\n\nDer Server ist gerade nicht erreichbar (offline oder Tunnel weg) — später nochmal probieren.") + "auth_failed": + # Karte t_1457e935: Zugangsdaten fehlen → klarer Hinweis statt + # GUI-Login-Dialog (Askpass ist im Updater abgeschaltet). + if report: + _set_status(EditorUpdater.AUTH_ERROR_TEXT, true) + _show_update_popup(EditorUpdater.AUTH_ERROR_TEXT) + _: + if report: + _set_status("Update-Check fehlgeschlagen: %s" % str(result.get("error", "")), true) + _show_update_popup("Update-Check fehlgeschlagen:\n%s" % str(result.get("error", ""))) + + +func _show_update_banner(local_sha: String, remote_sha: String) -> void: + if update_banner == null or not is_instance_valid(update_banner): + return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen + update_label.text = EditorUpdater.banner_text(local_sha, remote_sha) + update_banner.visible = true + + +## Thread sauber einsammeln (Ergebnis kam per call_deferred — der Thread ist +## längst fertig, join ist nur Ordnung). +func _join(thread: Thread) -> void: + if thread != null and thread.is_started(): + thread.wait_to_finish() + + +## Banner-Knopf „Jetzt updaten“: Dirty-Schutz wie Strg+O/Strg+N (derselbe +## deutsche Dialog — „Abbrechen“ bricht das Update ab), dann git pull + +## Import-Lauf + Neustart. Import-Fehler = Abbruch mit Meldung, KEIN Neustart +## (Lektion 17.10.: sonst sieht Sly einen Editor ohne Map-Ansicht). +func _on_update_now() -> void: + if _update_running: + _set_status("Ein Update läuft schon…") + return + _request_lossy(_start_update) + + +func _start_update() -> void: + _update_running = true + update_btn.disabled = true + _set_status("Update läuft — neue Version wird geladen (git pull)…") + update_thread = Thread.new() + update_thread.start(_updater_run_thread) + + +func _updater_run_thread() -> void: + var project_dir := ProjectSettings.globalize_path("res://") + var cfg := TestRun.load_cfg() + var pull := EditorUpdater.pull(EditorUpdater.git_exe(cfg), project_dir) + if not bool(pull["ok"]): + call_deferred("_update_finished", false, "Update abgebrochen — git pull fehlgeschlagen:", pull["errors"]) + return + # Karte t_f73a1cc5: Wenn der Pull eigene Dateien automatisch beiseite- + # gelegt hat, zeigt die Statuszeile den Klartext statt der technischen + # Import-Meldung (KEINE rohe Git-Zeile). + var info := str(pull.get("info", "")) + if info.is_empty(): + info = "Update geladen — Version wird importiert (Godot-Import-Lauf)…" + call_deferred("_set_status", info) + var godot := TestRun.find_godot(cfg) + if godot.is_empty(): + call_deferred("_update_finished", false, "Update abgebrochen — kein Godot-Programm für den Import-Lauf:", [ + "Der Editor läuft nicht über ein Godot-Binary und es ist kein godot_path gesetzt.", + "Trage in scopedvr_editor.cfg (Godot-App-Daten) ein:", + " [test]", + " godot_path=\"/pfad/zu/godot\"", + ]) + return + var imp := EditorUpdater.run_import(godot, project_dir) + if not bool(imp["ok"]): + call_deferred("_update_finished", false, "Update ABGEBROCHEN — der Import ist fehlgeschlagen, es wird NICHT neu gestartet:", imp["errors"]) + return + call_deferred("_update_finished", true, "", []) + + +func _update_finished(ok: bool, title: String, details: Array) -> void: + _update_running = false + if not is_instance_valid(update_btn) or not is_instance_valid(update_banner): + return # UI wurde zwischendurch neu aufgebaut (UI-Größe) — nichts anfassen + update_btn.disabled = false + _join(update_thread) + update_thread = null + if not ok: + update_banner.visible = true + _show_errors(title, details) + _set_status("Update fehlgeschlagen — der Editor bleibt unverändert (es ist nichts kaputt gemacht).", true) + return + var restart := EditorUpdater.resolve_restart(TestRun.load_cfg()) + var project_dir := ProjectSettings.globalize_path("res://") + var pid := -1 + if str(restart["mode"]) == "command": + var argv: PackedStringArray = restart["argv"] + pid = OS.create_process(str(argv[0]), argv.slice(1)) + else: + var godot := TestRun.find_godot(TestRun.load_cfg()) + if godot.is_empty(): + _show_errors("Update installiert — Neustart nicht möglich:", [ + "Der neue Stand ist geladen, aber das Godot-Programm für den Neustart ist unbekannt.", + "Editor einfach einmal selbst neu starten.", + ]) + _set_status("Update installiert — bitte den Editor einmal selbst neu starten.") + return + pid = OS.create_process(godot, EditorUpdater.self_restart_args(project_dir)) + if pid <= 0: + _show_errors("Update installiert — Neustart fehlgeschlagen:", [ + "Der neue Stand ist geladen, aber der Neustart wurde vom System abgelehnt.", + "Editor einfach einmal selbst neu starten.", + ]) + _set_status("Update installiert — bitte den Editor einmal selbst neu starten.") + return + _set_status("Update installiert (%s) — Editor startet neu…" % EditorUpdater.short_sha(str(_update_result.get("remote", "")))) + await get_tree().create_timer(1.0).timeout + get_tree().quit() + + +## „▶ Karte testen" (Karte t_1cdc1a72): Lauf-Modus IM Map-Builder selbst — +## erste Person, WASD + Maus, ohne Client-Ordner/VR/Server (Slys verbindlicher +## Wunsch: der Map-Builder ist überall installierbar und testet die Map ohne +## jegliche ScopedVR-Client-Abhängigkeit). Der ALTE Client-Start (Export nach +## Temp + eigener Client-Prozess, Karte t_b84af40f) bleibt als „alt" in +## core/editor/test_run.gd — der Rückbau kommt in einer späteren Karte. +func _on_test_run() -> void: + if doc.is_empty(): + _set_status("Erst eine Map anlegen (Datei → Neue Map), dann testen.", true) + return + _start_playtest() + + +## Lauf-Modus anwerfen: Werkzeuge einfrieren, Panels ausblenden, Maus einfangen, +## PlaytestMode in der 3D-Welt anlegen (Spieler am ersten Spawnpunkt der Map). +func _start_playtest() -> void: + if playtest != null: + return + # Editor-Zustand einfrieren (ohne Auswahl/Dokument/History anzufassen): + # laufende Ghost-Platzierung zurückrollen, Gizmo-Zug abbrechen (beides + # rollt ohne History-Eintrag zurück), Werkzeug-Modus auf Auswahl umschalten. + interaction.call("cancel_placement") + if gizmo != null and gizmo.has_method("cancel_drag_if_active"): + gizmo.call("cancel_drag_if_active") + _playtest_tool_mode = tool_mode + tool_mode = "objects" + _playtest_set_frozen(true) + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + playtest = load("res://core/editor/playtest/playtest_mode.gd").new() + playtest.ui_panels = [ui_top_bar, ui_left_panel, ui_right_panel, ui_status_bar] + # Trenner-Lagen JETZT sichern (Sly-Befund 20.10.: beim Verstecken der + # Panels klappen die Trenner zusammen — nach dem Playtest mussten die + # Seitenbereiche neu gezogen werden). Wiederherstellung im Exit-Handler. + _split_lagen_playtest.clear() + for sname in ["MainSplit", "BottomSplit"]: + var sp := _find_split(sname) + if sp != null: + _split_lagen_playtest[sname] = sp.split_offsets.duplicate() + playtest.ui_status = status_label + playtest.help_label = help_label + playtest.exit_requested.connect(_on_playtest_exit_requested) + world3d.add_child(playtest) + camera.current = false # Editor-Orbit abgeben — die Spiel-Kamera übernimmt + # Karte t_96e3c4ee: Fallback-Reihenfolge mit Klartext-Hinweis — gesetzter + # Test-Start → erster Team-Spawnpunkt → Kartenmitte. + var resolved: Dictionary = TestStart.resolve_start(doc, test_start_point) + playtest.enter(doc, view_builder, resolved["pos"]) + _set_status("▶ Testlauf läuft: WASD laufen, Shift rennen, Space springen, Maus umsehen — ESC zurück zum Editor. (%s)" % str(resolved["status"])) + + +## Editor-Input einfrieren/freigeben (Kamera-Flug, Werkzeuge, Gizmo). +## Nur auf die Werkzeug-Nodes — die Sicht (view_builder) bleibt 1:1 stehen. +func _playtest_set_frozen(on: bool) -> void: + for t in [camera, interaction, terrain_tool, spawn_tool, test_start_tool]: + if t == null: + continue + t.set_process_unhandled_input(not on) + t.set_process(not on) + if gizmo != null: + gizmo.set_process_unhandled_input(not on) + gizmo.set_process(not on) + gizmo.visible = not on + + +## ESC im Lauf-Modus: sauber zurück in den Editor — Werkzeuge + Panels wieder +## an, Maus frei, Kamera + Auswahl unverändert, Dokument + Undo-Stapel unverändert. +func _on_playtest_exit_requested() -> void: + if playtest == null: + return + var mode: Node = playtest + playtest = null + mode.exit() + mode.queue_free() + _playtest_set_frozen(false) + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + camera.current = true + tool_mode = _playtest_tool_mode + _update_tool_label() + _set_status("Testlauf beendet — zurück im Editor (Auswahl und Dokument unverändert).") + # Trenner-Lagen zurücksetzen (Sly-Befund 20.10.): das Re-Layout nach dem + # Wiedereinblenden der Panels setzt die Trenner auf Defaults — die gemerkten + # Lagen deshalb deferred UND im nächsten Frame zurücksetzen (das Layout + # muss die Panels erst neu vermessen haben, sonst clampen sie erneut). + _playtest_split_lagen_zurueck.call_deferred() + get_tree().process_frame.connect(_playtest_split_lagen_zurueck, CONNECT_ONE_SHOT) + + +## Gemerkte Playtest-Trenner-Lagen zurück auf die Splitter schreiben. +func _playtest_split_lagen_zurueck() -> void: + if _split_lagen_playtest.is_empty(): + return + for sname in _split_lagen_playtest.keys(): + var sp := _find_split(str(sname)) + if sp != null: + sp.split_offsets = _split_lagen_playtest[sname] + + +func _on_new_map_confirmed() -> void: + doc = SvmapDocument.create_new(new_dialog.get_map_name(), new_dialog.get_size_m(), new_dialog.get_weather(), new_dialog.get_game_mode()) + current_path = "" + dirty = false + history.begin(doc) + _clear_edit_state() + _refresh_all() + _test_start_reload() # Karte t_96e3c4ee: neue Map → kein Test-Start gesetzt + _set_status("Neue Map „%s“ erstellt (%.0f × %.0f m, Wetter: %s, Modus: %s). Wähle links ein Objekt, um zu bauen!" % [ + new_dialog.get_map_name(), new_dialog.get_size_m(), new_dialog.get_size_m(), new_dialog.get_weather(), + ModusKatalog.MODE_LABELS[new_dialog.get_game_mode()]]) + + +func _on_open_selected(path: String) -> void: + var result := SvmapIO.load_from_file(path) + var migration := {} + if not result["ok"] and SvmapIO.migrierbar(result["errors"]): + # Gelöschte Pack-Objekte (z. B. Pilze): ersetzen/überspringen statt Abbruch. + var migriert := SvmapIO.load_from_file_migriert(path) + if migriert["ok"]: + result = migriert + migration = migriert["migration"] + if not result["ok"]: + # t_9814f18f: NUR die Prüfsumme (V13) schlägt fehl, Kern (V3–V16) grün → + # Dialog „Trotzdem laden (Prüfsumme erneuern)“ statt hartem Abbruch. + if SvmapIO.checksum_only_failure(result["errors"]): + _resign_pending_path = path + resign_dialog.popup_centered() + return + _show_errors("Map konnte NICHT geladen werden:\n(Falls sie in einem anderen Programm geändert wurde, prüfe die Datei.)", result["errors"]) + _set_status("Laden fehlgeschlagen: %s — versuche eine .svmap-Datei aus dem Map-Editor oder vom Server." % path.get_file(), true) + return + doc = result["data"] + # Feines Material-Raster (Karte 24.10.): alte Karten beim Öffnen anheben — + # optisch gleich, ab jetzt ~1 m fein malbar. Die Datei ändert sich erst + # beim nächsten Speichern (deshalb kein dirty). + SvmapIO.upgrade_material_layer(doc.get("terrain", {})) + # Alte Bau-Sektion „gebaeude“ (vor 25.10.) → normale Bauteil-Objekte in einer Gruppe. + var uebernommen := BauOps.migriere_gebaeude(doc) + current_path = path + dirty = uebernommen > 0 or not migration.is_empty() + history.begin(doc) + _clear_edit_state() + _refresh_all() + _test_start_reload() # Karte t_96e3c4ee: Sidecar der geladenen Map lesen + var map_name := str(doc.get("info", {}).get("name", "?")) + if not migration.is_empty() and int(migration.get("gesamt", 0)) > 0: + _set_status("Map „%s“ geladen. Veraltete Objekte angepasst: %s. Bitte prüfen und speichern — vorher bleibt die Datei unverändert." % [ + map_name, SvmapMigration.zusammenfassung(migration, SvmapIO._manifest_or({}).get("objects", {}))]) + elif uebernommen > 0: + _set_status("Map „%s“ geladen. %d alte Bauteile wurden in das neue Bausystem übernommen (Gruppe „Gebäude“) — Wandhöhe ist jetzt 3 m, bitte kurz prüfen und speichern." % [map_name, uebernommen]) + else: + _set_status("Map „%s“ geladen (%d Objekte). Klicke ein Objekt in der Karte an, um es anzupassen." % [map_name, SvmapDocument.object_count(doc)]) + + +func _clear_edit_state() -> void: + if interaction != null: + interaction.call("clear_selection", true) + interaction.call("cancel_placement") + + +func _save() -> void: + if current_path.is_empty(): + save_dialog.popup_centered() + return + _save_to(current_path) + + +func _on_save_selected(path: String) -> void: + if not path.get_extension().is_empty() and path.get_extension() != "svmap": + path += ".svmap" + _save_to(path) + + +func _save_to(path: String) -> void: + # Felder stehen im Dokument (Live-Bindung) — vor dem Speichern noch einmal + # sicherstellen, dass Name/Autor nicht leer sind (verständliche Meldung statt V4-Kauderwelsch). + var name_v := str(doc.get("info", {}).get("name", "")).strip_edges() + if name_v.is_empty(): + _show_errors("Map konnte nicht gespeichert werden:", ["Die Map braucht einen Namen — trag ihn rechts unter „Map-Eigenschaften“ ein."]) + return + var result := SvmapIO.save_to_file(path, doc) + if not result["ok"]: + _show_errors("Map konnte nicht gespeichert werden (ungültige Map-Daten):", result["errors"]) + _set_status("Speichern fehlgeschlagen.", true) + return + current_path = path + dirty = false + _test_start_reload() # Karte t_96e3c4ee: Punkt wandert beim Speichern mit + _refresh_file_label() + _set_status("Map gespeichert: %s" % path) + _refresh_tree() + + +func _show_errors(title: String, errors: Array) -> void: + var lines := "" + for e in errors: + lines += "• %s\n" % str(e) + error_text.text = "%s\n%s" % [title, lines] + error_dialog.title = "Fehler" + error_dialog.popup_centered() + + +# ------------------------------------------- Karte 3/3: Globale Shortcuts --- + +## Verlust-Operation (Neue Map / Öffnen): bei ungespeicherten Änderungen erst +## NACHFRAGEN (deutscher Dialog), sonst direkt ausführen. Die wartende Aktion +## liegt in _pending_after_dirty und läuft bei „Verwerfen“ an. +func _request_lossy(next: Callable) -> void: + if ShortcutPolicy.needs_dirty_confirm(dirty): + _pending_after_dirty = next + dirty_dialog.popup_centered() + return + next.call() + + +## „Verwerfen“ im Dirty-Dialog: die wartende Aktion (Neu/Öffnen) jetzt ausführen. +func _on_dirty_confirmed() -> void: + var next := _pending_after_dirty + _pending_after_dirty = Callable() + if next.is_valid(): + next.call() + + +## Karte 3/3: Dürfen die globalen Tasten (F1–F5, H) gerade wirken? Pure Policy +## in ShortcutPolicy — hier nur die Fakten: Textfeld/SpinBox fokussiert? Ein +## Dialog/Popup offen? (Eigene Fenster des Editors; das Hilfe-Panel ist +## absichtlich KEIN Fenster und zählt nicht.) +func _tool_key_allowed() -> bool: + var focus := get_viewport().gui_get_focus_owner() + var text_focused: bool = focus != null \ + and (focus is LineEdit or focus is TextEdit or focus is SpinBox) + var dialog_open := false + for child in get_children(): + if child is Window and child.visible: + dialog_open = true + break + return ShortcutPolicy.tool_keys_allowed(text_focused, dialog_open) + + +## Kurzhilfe-Overlay umschalten; Statuszeile meldet den Zustand. +func _toggle_help_overlay() -> void: + help_overlay.visible = not help_overlay.visible + if help_overlay.visible: + _set_status("Kurzhilfe an — H oder Esc schließt das Overlay wieder.") + else: + _base_tool_status() + + +## Permanente Werkzeug-Anzeige (Karte 3/3): aktives Werkzeug + Flug-Hint — +## Sly sieht in der Statuszeile immer, wo er ist. +func _update_tool_label() -> void: + if tool_label == null: + return + var names := { + "objects": "Objekte (F2)", + "terrain": "Terrain (F1)", + "spawns": "Spawn-Zonen (F4)", + } + tool_label.text = "Werkzeug: %s" % str(names.get(tool_mode, tool_mode)) + + +## Symmetrie-Schalter (Karte t_f5935023, M1): Modus aus dem Toolbar-Menü übernehmen. +func _on_sym_mode_selected(index: int) -> void: + sym_mode = clampi(index, 0, SymmetryCore.MODE_LABELS.size() - 1) + _update_sym_ui() + if sym_mode == SymmetryCore.Mode.OFF: + _set_status("Symmetrie aus — die Karte wird nicht mehr automatisch gespiegelt.") + else: + _set_status("Symmetrie: %s — Änderungen an einer Seite werden auf die andere übertragen." % + SymmetryCore.mode_label(sym_mode)) + + +## Symmetrie-Status: Toolbar-Text + Haken + Statuszeilen-Label (M1 Fundament). +## Das tatsächliche Spiegeln von Änderungen kommt mit M2–M4. +func _update_sym_ui() -> void: + if sym_menu == null or sym_label == null: + return + var label := "Symmetrie: %s" % SymmetryCore.mode_label(sym_mode) + sym_label.text = label + sym_menu.select(sym_mode) + if sym_mod != null and sym_mod.has_method("refresh_mode"): + sym_mod.call("refresh_mode") + + +# --------------------------------------------------------------- History ---- + +func _do_undo() -> void: + if not history.can_undo(): + _set_status("Nichts zum Rückgängigmachen — die History ist schon am Anfang.") + return + var desc := history.undo() + _after_structure_change() + _set_status("Rückgängig gemacht: %s. Mit Strg+Y kannst du es wiederherstellen." % desc) + + +func _do_redo() -> void: + if not history.can_redo(): + _set_status("Nichts zum Wiederholen — schon am aktuellsten Stand.") + return + var desc := history.redo() + _after_structure_change() + _set_status("Wiederhergestellt: %s." % desc) + + +## F: Auswahl ins Bild zentrieren (Karte t_c46d31c2) — Kamera-Ziel auf den +## Auswahl-Mittelpunkt; im Flugmodus dreht sich nur die Blickrichtung. +func _focus_selection() -> void: + var indices: Array = interaction.selected_indices + if indices.is_empty(): + _set_status("Nichts ausgewählt — F zentriert die Auswahl im Bild. Klicke zuerst ein Objekt an (im Viewport oder im Baum).") + return + var center := SelectionMath.focus_center(view_builder.object_markers, indices) + camera.call("focus_on", center) + _set_status("%d Objekt(e) ins Bild zentriert — Strg+Klick/Strg+LMB markiert weitere dazu, Entf löscht die ganze Auswahl." % indices.size()) + + +## Karte t_2470de89: Der alte Rechtsklick-Filter (_fly_rmb_blocked) ist weg — +## RMB startet den Freelook-Flug in JEDEM Werkzeugzustand (absoluter Vorrang). + + +func _refresh_history_buttons() -> void: + undo_btn.disabled = not history.can_undo() + redo_btn.disabled = not history.can_redo() + undo_btn.tooltip_text = ("Rückgängig machen: %s (Strg+Z)" % history.undo_desc()) if history.can_undo() else "Nichts zum Rückgängigmachen" + redo_btn.tooltip_text = ("Wiederherstellen: %s (Strg+Y)" % history.redo_desc()) if history.can_redo() else "Nichts zum Wiederholen" + + +func _unhandled_key_input(event: InputEvent) -> void: + if not (event is InputEventKey) or not event.is_pressed() or event.is_echo(): + return + var key_evt := event as InputEventKey + var key := key_evt.keycode + # InputEventKey hat KEIN `command_pressed` (Godot-3-API-Relikt — crashete + # bei jeder Taste, GUI-Start-Befund 15.10.). Die Godot-4-Ersatz-API ist + # is_command_or_control_pressed(): Ctrl auf Linux/Windows, Cmd auf macOS. + var ctrl: bool = key_evt.is_command_or_control_pressed() + # Lauf-Modus (t_1cdc1a72): nur ESC behandeln — ALLE anderen Editor-Tasten + # (F1-F5, Undo/Redo, Werkzeuge) sind im Spiel eingefroren. + if playtest != null and playtest.active: + if key == KEY_ESCAPE: + playtest.handle_escape() + get_viewport().set_input_as_handled() + elif key == KEY_T: + # Karte t_96e3c4ee, „jederzeit": T im Testlauf = Test-Start auf den + # Punkt unterm Fadenkreuz setzen + SOFORT dorthin versetzen + # (Augenhöhe 1,70 m, Kollision geprüft). + var tp: Dictionary = playtest.call("teleport_crosshair") + if bool(tp.get("ok")): + _test_start_apply(float(tp.get("x")), float(tp.get("z")), str(tp.get("hinweis"))) + get_viewport().set_input_as_handled() + return + if (key == KEY_PAGEUP or key == KEY_PAGEDOWN) and not ctrl and interaction.placing \ + and BauKatalog.ist_bau(interaction.place_oid): + interaction.bau_geschoss_aendern(1 if key == KEY_PAGEUP else -1) + get_viewport().set_input_as_handled() + return + match key: + KEY_T: + # Karte t_96e3c4ee: T schaltet das Test-Start-Werkzeug um (Editor). + if not ctrl: + _set_tool_mode("teststart" if tool_mode != "teststart" else "objects") + get_viewport().set_input_as_handled() + KEY_Z: + if ctrl: + if event.shift_pressed: + _do_redo() + else: + _do_undo() + get_viewport().set_input_as_handled() + KEY_Y: + if ctrl: + _do_redo() + get_viewport().set_input_as_handled() + KEY_S: + if ctrl: + if event.shift_pressed: + # Strg+Umschalt+S (Karte 3/3): Speichern unter … + save_dialog.popup_centered() + else: + _save() + get_viewport().set_input_as_handled() + KEY_O: + if ctrl: + # Strg+O (Karte 3/3): Map öffnen — bei dirty erst NACHFRAGEN. + _request_lossy(func() -> void: open_dialog.popup_centered()) + get_viewport().set_input_as_handled() + KEY_N: + if ctrl: + # Strg+N (Karte 3/3): Neue Map — bei dirty erst NACHFRAGEN. + _request_lossy(func() -> void: new_dialog.popup_centered()) + get_viewport().set_input_as_handled() + KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5: + # Karte 3/3: Werkzeugwechsel + Test-Platzhalter. NUR wirken, wenn + # kein Textfeld tippt und kein Dialog offen ist (ShortcutPolicy). + if _tool_key_allowed(): + match key: + KEY_F1: + _set_tool_mode("terrain") + KEY_F2: + _set_tool_mode("objects") + KEY_F3: + _open_bauen_tab() + KEY_F4: + _set_tool_mode("spawns") + KEY_F5: + # ▶ Karte testen: der echte Freigabe-/Test-Flow existiert + # (Karte t_b84af40f) — F5 ruft ihn direkt auf. + _on_test_run() + get_viewport().set_input_as_handled() + KEY_H: + # Karte 3/3: Kurzhilfe-Overlay an/aus (Esc schließt ebenfalls). + if _tool_key_allowed(): + _toggle_help_overlay() + get_viewport().set_input_as_handled() + KEY_L: + # Karte t_89c0ec95: Höhenlinien FIXIEREN an/aus — unabhängig vom + # Werkzeug (wer die Linien dauerhaft will, fixiert sie hier). + # Ohne Modifier (kein Strg+L), gleiche Freigabe wie F1–F5/H: + # Tippen in Textfeldern und offene Dialoge gehen vor. + if not ctrl and _tool_key_allowed(): + _on_contours_toggled(not _contours_on) + get_viewport().set_input_as_handled() + KEY_G: + if ctrl: + # Gruppieren/Lösen (Karte t_f6f29d86) + if event.shift_pressed: + interaction.ungroup_selection() + else: + interaction.group_selection() + get_viewport().set_input_as_handled() + elif tool_mode == "objects" and not interaction.placing: + # G = Greifen (Karte t_55257792, Blender-Semantik): Auswahl + # hängt an der Maus, Linksklick setzt neu, Esc bricht ab. + interaction.grab_begin() + get_viewport().set_input_as_handled() + KEY_R: + var r_delta := -90.0 if event.shift_pressed else 90.0 + if ctrl and not event.alt_pressed: + # Strg+R / Strg+Umschalt+R: KIPPEN um X (±15°, Karte t_e166e656 + # — „nicht nur rotieren, sondern auch kippen“). Bauteile werden + # nie gekippt (fest ausgerichtet). + if BauKatalog.ist_bau(interaction.place_oid): + return + var t_delta: float = -interaction.TILT_STEP_DEG if event.shift_pressed else interaction.TILT_STEP_DEG + if interaction.placing: + interaction.place_tilt_x = fposmod(interaction.place_tilt_x + t_delta + 3600.0, 360.0) + interaction._ghost_rotation_anwenden() + else: + interaction.tilt_selected(true, t_delta) + get_viewport().set_input_as_handled() + elif event.alt_pressed and not ctrl: + # Alt+R / Alt+Umschalt+R: KIPPEN um Z (±15°, Karte t_e166e656). + if BauKatalog.ist_bau(interaction.place_oid): + return + var t_delta2: float = -interaction.TILT_STEP_DEG if event.shift_pressed else interaction.TILT_STEP_DEG + if interaction.placing: + interaction.place_tilt_z = fposmod(interaction.place_tilt_z + t_delta2 + 3600.0, 360.0) + interaction._ghost_rotation_anwenden() + else: + interaction.tilt_selected(false, t_delta2) + get_viewport().set_input_as_handled() + elif not ctrl and not event.alt_pressed: + # R = Vierteldrehung 90° (Karte t_55257792): Objekt-Auswahl UND Ghost + # drehen in 90°-Schritten (Umschalt+R = zurück). Feindrehen 15° + # bleibt am Mausrad (Bauteile: 90° pro Rad-Tick). + if interaction.placing: + interaction.place_rot_deg = wrapf(interaction.place_rot_deg + r_delta, 0.0, 360.0) + interaction._ghost_rotation_anwenden() + else: + interaction.rotate_selected(r_delta) + get_viewport().set_input_as_handled() + KEY_F: + if not ctrl: + if event.shift_pressed: + # Umschalt+F: Freelook-Flug dauerhaft an/aus (Karte t_c46d31c2). + camera.call("toggle_fly_lock") + get_viewport().set_input_as_handled() + else: + # F: Auswahl ins Bild zentrieren. + _focus_selection() + get_viewport().set_input_as_handled() + KEY_ESCAPE: + # Karte 3/3: Steht das Hilfe-Overlay offen, gehört Esc IHM (schließen) + # — VOR der normalen Esc-Prioritätskette. + if help_overlay.visible: + _toggle_help_overlay() + get_viewport().set_input_as_handled() + return + # Karte t_e0120dc2: Läuft gerade ein Gizmo-Zug (Achsen-Pfeil), gehört + # Esc IHM — exakt zurückrollen auf die Zug-Startposition (kein + # History-Eintrag), danach NICHT weiter in der Esc-Kette eintauchen. + if gizmo != null and gizmo.cancel_drag_if_active(): + get_viewport().set_input_as_handled() + return + # Esc-Priorität über pure Funktion (Karte t_c46d31c2, testbar): + # Werkzeug verlassen → Greifen zurückrollen → Ghost-Preview + # abbrechen → Auswahl aufheben (Greifen: Karte t_55257792). + var esc := ShortcutPolicy.esc_action(tool_mode, + interaction.placing, + interaction.selected_index != -1 or not interaction.selected_indices.is_empty(), + bool(interaction.get("_grabbing"))) + match esc: + "exit_terrain": + _set_tool_mode("objects") + _set_status("Terrain-Modus beendet — zurück zum Objekt-Werkzeug.") + "exit_spawns": + _set_tool_mode("objects") + _set_status("Spawn-Werkzeug beendet — zurück zum Objekt-Werkzeug.") + "exit_teststart": + # Der gesetzte Test-Start bleibt bestehen (nur Werkzeug aus). + _set_tool_mode("objects") + _set_status("Test-Start-Werkzeug beendet — zurück zum Objekt-Werkzeug.") + "cancel_grab": + interaction.grab_cancel() + "cancel_placement": + interaction.cancel_placement() + _set_status("Platzieren abgebrochen.") + "clear_selection": + interaction.clear_selection() + _set_status("Auswahl aufgehoben.") + if esc != "none": + get_viewport().set_input_as_handled() + KEY_DELETE: + if not ctrl: + if interaction.placing: + return + if tool_mode == "spawns": + spawn_tool.call("delete_selected") + elif tool_mode == "teststart": + # Karte t_96e3c4ee: Entf im Test-Start-Werkzeug = Punkt löschen. + _test_start_delete() + else: + interaction.delete_selected() + get_viewport().set_input_as_handled() + KEY_D: + if ctrl and tool_mode == "objects": + interaction.duplicate_selected() + get_viewport().set_input_as_handled() + KEY_PAGEUP: + if interaction.placing: + return + if ctrl: + # Strg+Bild↑ (Karte t_55257792): Auswahl auf den Boden. + if tool_mode == "objects" and interaction.selected_index >= 0: + interaction.snap_selection_to_floor() + get_viewport().set_input_as_handled() + elif not ctrl: + if tool_mode == "objects" and interaction.selected_index >= 0: + interaction.lift_selected(ObjectOps.LIFT_STEP_M) + get_viewport().set_input_as_handled() + KEY_PAGEDOWN: + if not ctrl and not interaction.placing: + if tool_mode == "objects" and interaction.selected_index >= 0: + interaction.lift_selected(-ObjectOps.LIFT_STEP_M) + get_viewport().set_input_as_handled() + KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT: + # Pfeiltasten (Karte t_55257792): Auswahl im 2-m-Raster verschieben, + # Richtung = Kamera-Ausrichtung nächste Weltachse (MoveMath). Eine + # Zelle pro Tastendruck; beim Platzieren bleibt der Ghost herrschend. + if interaction.placing: + return + var fwd := 0 + var side := 0 + match key: + KEY_UP: + fwd = 1 + KEY_DOWN: + fwd = -1 + KEY_RIGHT: + side = 1 + KEY_LEFT: + side = -1 + var delta := MoveMath.nudge_delta( + float(camera.call("view_yaw_deg")), fwd, side) + if tool_mode == "objects" and interaction.selected_index >= 0: + interaction.nudge_selection(delta) + get_viewport().set_input_as_handled() + KEY_BRACKETLEFT, KEY_BRACKETRIGHT: + # Terrain-Pinsel [ / ] (Karte t_55257792): Größe durch die 3 Presets + # zyklisch — nur im Terrain-Werkzeug (Ziffern-Freiheit, Punkt 6). + if tool_mode == "terrain": + _terrain_brush_size_step(key == KEY_BRACKETRIGHT) + get_viewport().set_input_as_handled() + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6: + # Terrain-Pinsel-Modi 1–4 (Karte t_55257792): Heben/Senken/Glätten/ + # Malen — NUR im Terrain-Werkzeug (Konfliktfreiheit, Punkt 6; im + # Objekt-/Bauen-Modus bleiben die Ziffern unbenutzt). + if tool_mode == "terrain": + _terrain_brush_mode_key(int(key) - int(KEY_1)) + get_viewport().set_input_as_handled() + + +# --------------------------------------------------------- Bibliothek ------ +# U1: die Bibliothek wohnt im Assets-Bereich unten (assets_panel.gd); die +# Tools rufen weiter main.call("_highlight_library") — dünne Weiterleitung. + +func _on_library_pressed(oid: String) -> void: + assets_mod._on_library_pressed(oid) + + +## U1-Delegation: die Bibliotheks-Logik wohnt in assets_panel.gd — die alten +## main.-Aufrufe (Werkzeuge/Tests) bleiben als dünne Delegaten erhalten. +func _apply_library_filter(query: String) -> void: + assets_mod._apply_library_filter(query) + + +func _on_library_expand_all() -> void: + assets_mod._on_library_expand_all() + + +func _on_library_collapse_all() -> void: + assets_mod._on_library_collapse_all() + + +## Der Bibliothek-Button des aktiven Platzier-Objekts leuchtet auf. +func _highlight_library(oid: String) -> void: + assets_mod.highlight_library(oid) + if bauen_mod != null: + bauen_mod.sync_platzierung() + + +## Bauen-Tab: Gebäude-Info nachziehen (nach Setzen/Löschen/Undo). +func _refresh_bau_info() -> void: + if bauen_mod != null: + bauen_mod.refresh_info() + + +## „Neues Gebäude beginnen“: nächstes Teil startet eine neue Gruppe. +func _bau_neues_gebaeude() -> void: + interaction.call("neues_gebaeude") + _refresh_bau_info() + _set_status("Neues Gebäude: das nächste Bauteil beginnt eine neue Gruppe.") + + +## Alle Teile des Gebäudes wählen, zu dem die Auswahl gehört. +func _bau_gebaeude_waehlen() -> bool: + var sel: Array = interaction.selected_indices + if sel.is_empty(): + _set_status("Nichts markiert — klicke zuerst ein Bauteil des Gebäudes an.", true) + return false + var alle := {} + for i in sel: + for m in BauOps.gebaeude_von(doc, int(i)): + alle[int(m)] = true + var liste: Array = alle.keys() + liste.sort() + interaction.call("select_many", liste, true) + _set_status("Ganzes Gebäude gewählt (%d Teile) — Ziehen verschiebt, R dreht, Strg+D kopiert, Entf löscht." % liste.size()) + return true + + +func _bau_gebaeude_kopieren() -> void: + if _bau_gebaeude_waehlen(): + interaction.call("duplicate_selected") + + +func _bau_gebaeude_loeschen() -> void: + if _bau_gebaeude_waehlen(): + interaction.call("delete_selected") + + +func object_label(oid: String) -> String: + return LibraryBrowser.object_label(oid) + + +## Snap to Ground (1 Klick): ALLE Objekte auf die Terrain-Höhe setzen — +## EIN Undo-Eintrag für die ganze Aktion. +func _on_snap_to_ground() -> void: + var objects: Array = doc.get("objects", []) + if objects.is_empty(): + _set_status("Es gibt noch keine Objekte — platziere zuerst eins aus der Bibliothek links.") + return + var before: Dictionary = history.snapshot(["objects"]) + var changed := ObjectOps.snap_to_ground(doc) + if changed == 0: + _set_status("Alle %d Objekte stehen schon auf dem Boden — nichts zu tun." % objects.size()) + return + history.commit("Alle Objekte auf Boden gesetzt (%d)" % changed, + before, history.snapshot(["objects"])) + _after_structure_change() + _set_status("%d von %d Objekten auf den Boden gesetzt — Strg+Z nimmt es zurück." % [changed, objects.size()]) + + +func _on_grid_snap_changed(pressed: bool) -> void: + grid_snap = pressed + _set_status("Raster-Snap %s — Objekte landen jetzt %s." % [ + "AN" if pressed else "AUS", + "auf ganzen Metern (wie Bauklötze nebeneinander)" if pressed else "dort, wo du hinklickst"]) + + +# ---------------------------------------------------- Werkzeug-Status ------ + +## Standard-Hilfstext, wenn gerade nichts "Im Gange" ist. +func _base_tool_status() -> void: + _set_status("Bereit. Links ein Objekt anklicken zum Platzieren oder in der Karte ein Objekt auswählen.") + + +# ------------------------------------------------------ Terrain-Werkzeug ---- +# (Karte t_9aba04f1 — Höhenfeld heben/senken/glätten) + +## U1-Delegation: die Widgets wohnen in den Modulen (terrain_panel.gd, +## check_panel.gd); der Koordinator hält die Werkzeug-Logik und steuert die +## Modul-Widgets über deren öffentliche Felder. Methoden, die die Tools per +## main.call(...) erreichen (_on_terrain_stroke_committed, _sync_spawn_selection, +## _refresh_spawn_status, _mark_tree_selection…), bleiben unter denselben +## Namen hier (dünne Weiterleitung) — der Werkzeug-Vertrag ist unverändert. + +## Tab Terrain/Malen/Bauen angeklickt → das Werkzeug ist sofort aktiv, kein +## zweiter Klick auf einen Start-Knopf nötig (Befund 25.10.). +func _on_right_tab_changed(idx: int) -> void: + if terrain_mod == null or malen_mod == null or bauen_mod == null or doc.is_empty(): + return + var tab: Node = (ui_right_panel as TabContainer).get_child(idx) + if tab == terrain_mod: + if tool_mode == "terrain" and not terrain_mod.terrain_mode_paint.button_pressed: + return + if terrain_mod.terrain_mode_paint.button_pressed: + terrain_mod.terrain_mode_raise.button_pressed = true + _set_tool_mode("terrain") + elif tab == malen_mod: + if tool_mode == "terrain" and terrain_mod.terrain_mode_paint.button_pressed: + return + terrain_mod.terrain_mode_paint.button_pressed = true + _set_tool_mode("terrain") + elif tab == bauen_mod: + if tool_mode != "objects": + _set_tool_mode("objects") + # Das Verlassen des Terrain-Werkzeugs schaltet den Tab auf Malen/Terrain + # zurück — der Nutzer wollte aber Bauen. + (ui_right_panel as TabContainer).current_tab = bauen_mod.get_index() + + +func _on_terrain_btn_pressed() -> void: + if tool_mode != "terrain": + if terrain_mod.terrain_mode_paint.button_pressed: + terrain_mod.terrain_mode_raise.button_pressed = true + _set_tool_mode("terrain") + else: + _set_tool_mode("objects") + + +## Werkzeug-Modus wechseln: "objects" (Platzieren/Auswahl), "terrain" (Pinsel), +## "spawns" (Spawn-Zonen malen). +func _set_tool_mode(mode: String) -> void: + if mode == tool_mode: + _sync_terrain_button() + _sync_spawn_button() + _sync_test_start_button() + _update_tool_label() + return + if mode == "terrain": + _leave_spawn_mode() + _leave_test_start_mode() + _enter_terrain_mode() + elif mode == "spawns": + _leave_terrain_mode() + _leave_test_start_mode() + _enter_spawn_mode() + elif mode == "teststart": + _leave_terrain_mode() + _leave_spawn_mode() + _enter_test_start_mode() + else: + _leave_terrain_mode() + _leave_spawn_mode() + _leave_test_start_mode() + _enter_objects_mode() + _update_tool_label() + # Karte t_89c0ec95: Werkzeugwechsel = Höhenlinien-Umschalter — nach dem + # Terrain-Werkzeug (F1) wieder aus, bei Terrain-Eintritt wieder an + # (sofern nicht fixiert). Signal-getrieben an diesem EINEN Wechselpunkt, + # kein per-Frame-Poll. + _sync_contours_to_builder() + + +func _enter_terrain_mode() -> void: + interaction.call("cancel_placement") + interaction.call("clear_selection", true) + if not terrain_tool.call("activate"): + # Abgelehnt (z. B. Auflösungs-Limit) — Button zurück, Meldung steht. + _sync_terrain_button() + return + tool_mode = "terrain" + help_label.text = "Linksklick-Ziehen: formen bzw. malen · Strg+Klick: Gegenteil · [ ]: Pinselgröße · H: Hilfe" + terrain_tool.call("set_brush_mode", terrain_mod._selected_terrain_mode()) + terrain_mod.refresh_panels() + _set_status("Terrain-Modus an: LINKSKLICK-Ziehen formt den Boden, im Malen-Modus färbt er das Gelände (Steppe/Sand/Fels …). Regler rechts stellen Pinsel und Stärke ein.") + + +func _leave_terrain_mode() -> void: + if terrain_tool != null: + terrain_tool.call("deactivate") + _sync_terrain_button() + + +func _enter_spawn_mode() -> void: + interaction.call("cancel_placement") + interaction.call("clear_selection", true) + tool_mode = "spawns" + check_mod.spawn_panel.visible = true + help_label.text = "Linksklick: Zone setzen · Ziehen: verschieben · Entf: löschen · H: Hilfe" + check_mod.spawn_edit_btn.button_pressed = true + spawn_tool.call("activate") + spawn_tool.call("set_team", check_mod._selected_spawn_team()) + spawn_tool.call("set_radius", float(check_mod.spawn_radius_slider.value)) + _refresh_spawn_status() + _set_status("Spawn-Modus an: Klicke auf den Boden, um Zonen für %s zu malen. Die Kreis-Vorschau folgt der Maus." % SpawnOps.team_label(check_mod._selected_spawn_team())) + + +func _leave_spawn_mode() -> void: + if spawn_tool != null: + spawn_tool.call("deactivate") + if check_mod != null: + check_mod.spawn_panel.visible = false + _sync_spawn_button() + + +func _enter_objects_mode() -> void: + tool_mode = "objects" + help_label.text = "Linksklick: auswählen · Rahmen: mehrere · R: drehen · Entf: löschen · H: Hilfe" + _base_tool_status() + + +# ------------------------------------------------------------- Bauen ------ +# Bauteile sind normale Bibliotheks-Objekte (bau_*): das Bauen-Panel startet die +# Platzierung in interaction.gd (Ghost rastet über BuildSnap). Kein eigener +# Werkzeug-Modus mehr — Auswahl, Drehen, Kopieren, Löschen, Undo laufen über die +# Objekt-Werkzeuge. + + +## F3 / Menü: Bauen-Tab zeigen (wechselt aus Terrain-/Spawn-Modus zurück zu Objekten). +func _open_bauen_tab() -> void: + if tool_mode != "objects": + _set_tool_mode("objects") + if ui_right_panel is TabContainer and bauen_mod != null: + (ui_right_panel as TabContainer).current_tab = bauen_mod.get_index() + _set_status("Bauen: Bauteil rechts anklicken, dann in die Karte klicken — Wände rasten an Zellkanten, Böden in die Zellmitte, alles aufs 2-m-Raster.") + + +## Bauteil im Bauen-Tab angeklickt → Platzieren mit Snap starten. +func _on_bauteil_gewaehlt(oid: String) -> void: + if tool_mode != "objects": + _set_tool_mode("objects") + interaction.call("start_placement", oid) + + +func _sync_terrain_button() -> void: + terrain_mod.refresh_panels() + + +func _terrain_brush_size_step(up: bool) -> void: + terrain_mod.terrain_brush_size_step(up) + + +func _terrain_brush_mode_key(mode_idx: int) -> void: + var t: Control = terrain_mod + var btn: Button = null + match mode_idx: + 0: + btn = t.terrain_mode_raise + 1: + btn = t.terrain_mode_lower + 2: + btn = t.terrain_mode_smooth + 3: + btn = t.terrain_mode_paint + 4: + btn = t.terrain_mode_flatten + 5: + btn = t.terrain_mode_ramp + if btn == null or terrain_tool == null: + return + btn.set_pressed(true) + var labels := ["Heben", "Senken", "Glätten", "Malen", "Einebnen", "Rampe"] + var extra := "" + if mode_idx == 3: + var mats: Array = SvmapIO.MATERIAL_IDS + extra = " (Material: %s — mit der Palette rechts wechseln)" % str(mats[clampi(int(terrain_tool.get("paint_material")), 0, mats.size() - 1)]) + _set_status("Pinsel-Modus: %s%s — Linksklick-Ziehen formt/malt jetzt so (Ziffern 1–6 wechseln)." % [str(labels[mode_idx]), extra]) + + +## Ein Pinsel-Strich ist fertig (Doc + History schon aktualisiert vom Tool). +func _on_terrain_stroke_committed() -> void: + dirty = true + _refresh_file_label() + _refresh_stats() + _refresh_history_buttons() + view_builder.call("rebuild_terrain_from_doc", doc) + var desc: String = history.undo_desc() + _set_status("%s — Strg+Z macht den ganzen Strich rückgängig." % desc) + + +## Terrain-Modus nach Laden/Neu/Undo mit dem Doc synchron halten (Cache frisch, +## Auflösungs-Limit prüfen — sonst dezenter Rückschalter). +func _recheck_terrain_mode() -> void: + if tool_mode != "terrain" or terrain_tool == null: + return + if not terrain_tool.call("refresh_from_doc"): + terrain_tool.call("deactivate") + tool_mode = "objects" + _sync_terrain_button() + _update_tool_label() + # Karte t_89c0ec95: Dezenter Terrain-Rückschalter = ebenfalls ein + # Werkzeugwechsel — Linien wieder ausblenden (sofern nicht fixiert). + _sync_contours_to_builder() + help_label.text = "Linksklick: auswählen · Rahmen: mehrere · R: drehen · Entf: löschen · H: Hilfe" + _set_status("Terrain-Modus aus: Diese Map passt nicht ins Editor-MVP (Terrain-Auflösung über %d) — die Objekte bleiben normal bearbeitbar." % TerrainField.EDITOR_MAX_RESOLUTION, true) + + +# ---------------------------------------------------- Spawn-Zonen-Werkzeug --- + +func _on_spawn_btn_pressed() -> void: + check_mod._on_spawn_btn_pressed() + + +func _sync_spawn_button() -> void: + check_mod.spawn_edit_btn.button_pressed = tool_mode == "spawns" + + +## Panel-Zeile „Welche Zone ist angewählt?“ — WIRD vom Spawn-Tool gerufen. +func _sync_spawn_selection() -> void: + check_mod.sync_spawn_selection() + + +## LIVE-Validierung (Loader-Regeln) — immer sichtbar, nach JEDEM Struktur-Wechsel. +func _refresh_spawn_status() -> void: + check_mod.refresh_spawn_status() + + +## Map-Fertig-Check (Export-Button-Aktion): Klartext-Liste + Shake, wenn fehlt. +func _on_check_map() -> void: + var report := SpawnOps.ready_report(doc, manifest) + var lines := "" + for line in report["lines"]: + lines += "%s\n" % str(line["text"]) + var hint := str(line.get("hint", "")) + if hint != "": + lines += " → %s\n" % hint + check_text.text = lines + check_dialog.popup_centered() + if report["ok"]: + _set_status("✅ Karte prüfen: Alles bereit zum Export! Speichern erzeugt die signierte .svmap-Datei.") + else: + _shake_button(_registry_toolbar_button("karte_pruefen")) + _set_status("Map ist noch nicht bereit — siehe Warnungen im Map-Check-Fenster.", true) + + +## Kurzes Wackeln des Buttons — AUFMERKSAMKEIT statt Fehlerbombe (Chef-Grundsatz). +func _shake_button(btn: Control) -> void: + var orig: Vector2 = btn.position + var tween := create_tween() + for offset in [-9.0, 9.0, -6.0, 6.0, -3.0, 0.0]: + tween.tween_property(btn, "position", orig + Vector2(offset, 0.0), 0.045) + + +## Toolbar-Button per Registry-ID finden (karte_pruefen / karte_testen …). +func _registry_toolbar_button(id: String) -> Control: + var found: Array = toolbar_mod.find_children(id, "Button", true, false) + return found[0] if not found.is_empty() else null + + +## Baum-Rückrufe wohnen jetzt im Modul (core/ui/panels/scene_tree_panel.gd); +## die Tools rufen weiter main.call("_mark_tree_selection…") — dünne +## Weiterleitungen unten (Registry-Koordinator). +# ------------------------------------------------------- Struktur-Änderung -- + +## Nach undo/redo/platzieren/löschen: Struktur hat sich geändert → Ansicht neu. +func _after_structure_change() -> void: + dirty = true + _refresh_bau_info() + _refresh_file_label() + _refresh_stats() + _refresh_history_buttons() + _refresh_spawn_status() + if interaction != null: + interaction.call("clear_selection", true) + view_builder.call("rebuild", doc, manifest) + _sync_grid_to_builder() + # Karte t_89c0ec95: der Rebuild baut terrain_view NEU (Default-Pref „an“) + # — auch die Höhenlinien brauchen ihren Nacheich (Gitter-Muster). Sonst + # kehrte z. B. nach Strg+Z die Dauersicht zurück, die Sly störte. + _sync_contours_to_builder() + _refresh_tree() + if map_mod != null: + map_mod.refresh_values() # Undo/Redo: Wetter/Himmel-Auswahl nachziehen + _recheck_terrain_mode() + if interaction != null: + interaction.call("refresh_terrain_cache") # t_4092e3a0: Objekt-Cursor trifft das Gelände + if tool_mode == "spawns" and spawn_tool != null: + spawn_tool.call("refresh_from_doc") + +# --------------------------------------------------------------- Anzeige --- + +func _refresh_all() -> void: + _loading_ui_values = true + map_mod.refresh_values() + _loading_ui_values = false + + _refresh_tree() + _refresh_stats() + _refresh_file_label() + _refresh_history_buttons() + _refresh_viewport() + _refresh_spawn_status() + _recheck_terrain_mode() + + +func _refresh_viewport() -> void: + view_builder.call("rebuild", doc, manifest) + _sync_grid_to_builder() + _sync_contours_to_builder() + # Karte K1b (t_eca890c5): Laden/Neu = Ansicht auf den Karten-INHALT + # (Objekte + Bauteile + Spawnpunkte; Terrain zählt nicht) — ohne Inhalt + # wie bisher die ganze Karte. Pos1 (Home) bleibt ganze Karte. + var fb: Dictionary = doc.get("bounds", {}) + var fmn: Array = fb.get("min", []) + var fmx: Array = fb.get("max", []) + if fmn.size() == 3 and fmx.size() == 3: + camera.call("fit_to_document", doc, + Vector3(fmn[0], fmn[1], fmn[2]), Vector3(fmx[0], fmx[1], fmx[2])) + + +# --- Maßstabsgitter (Karte t_7ac70014) ------------------------------------- + +func _on_grid_toggled(pressed: bool) -> void: + _grid_on = pressed + _sync_grid_to_builder() + + +## Nach jedem Viewport-Rebuild: Nutzer-Wahl (bzw. Boden-Referenz ohne Terrain- +## Block) wieder anwenden — der Rebuild setzt das Gitter auf seinen Default. +func _sync_grid_to_builder() -> void: + if view_builder == null: + return + var no_terrain: bool = doc.get("terrain", {}).is_empty() + view_builder.call("set_grid_visible", _grid_on or no_terrain) + if grid_btn != null and grid_btn.button_pressed != _grid_on: + grid_btn.set_pressed_no_signal(_grid_on) + + +# --- Höhenlinien (Karte t_3ebe4086, Werkzeug-Kopplung t_89c0ec95) ------------ + +## Effektive Höhenlinien-Sichtbarkeit: FIX-Anforderung des Nutzers (L/Button) +## ODER das Terrain-Werkzeug (F1) ist aktiv. Bewusst KEIN _process-Poll — +## der Hook hängt am Werkzeug-Wechselpunkt (_set_tool_mode) und am Button/L. +func _contours_effective() -> bool: + return _contours_on or tool_mode == "terrain" + + +func _on_contours_toggled(pressed: bool) -> void: + _contours_on = pressed + _sync_contours_to_builder() + if status_label != null: + _set_status("Höhenlinien %s — %s" % [ + "FIXIERT AN" if _contours_on else "Fix aus", + "jetzt in jedem Werkzeug sichtbar (L oder Button schaltet zurück)." + if _contours_on else + "sichtbar nur noch im Terrain-Werkzeug (F1). L fixiert sie wieder."]) + + +## Nach jedem Viewport-Rebuild und nach JEDEM Werkzeug-Wechsel: die effektive +## Wahl anwenden — der Rebuild baut das Terrain-Mesh (und damit dessen +## Shader-Uniform) neu, der Werkzeugwechsel blendet die Linien ein/aus. +func _sync_contours_to_builder() -> void: + if view_builder == null: + return + var terrain_view = view_builder.get("terrain_view") + if terrain_view != null and is_instance_valid(terrain_view): + terrain_view.call("set_contours", _contours_effective()) + if contours_btn != null and contours_btn.button_pressed != _contours_on: + contours_btn.set_pressed_no_signal(_contours_on) + + +# --- Baum / Auswahl-Sync (U1: Module, dünne Weiterleitung) ------------------- + +func _refresh_tree() -> void: + tree_mod.refresh_tree() + + +## Baum-Klicks kommen im Modul an; die Viewport-Seite (interaction.gd) ruft +## weiter main.call("_mark_tree_selection…") — hier weiterleiten. +func _mark_tree_selection(idx: int) -> void: + tree_mod.mark_tree_selection(idx) + + +func _mark_tree_selection_multi(indices: Array) -> void: + tree_mod.mark_tree_selection_multi(indices) + + +## Rechte Panel-Sektion synchronisieren (wird von interaction.gd gerufen). +func _sync_selection_panel() -> void: + selection_mod.sync_selection() + + +## Hilfs-Meldung zur frischen Auswahl (wird von interaction.gd gerufen). +func _selection_status(idx: int) -> void: + selection_mod.selection_status(idx) + + +func _refresh_stats() -> void: + var count := SvmapDocument.object_count(doc) + var poly := SvmapDocument.poly_usage(doc, manifest) + var budget := int(SvmapIO.MAX_TOTAL_POLY) + var poly_pct := 100.0 * poly / budget + stats_objects.text = "%d Objekte · Poly %.0f %%" % [count, poly_pct] + stats_objects.tooltip_text = "Objekte: %d / 5000\nPoly-Budget: %s / %s (%.0f %%)\nBauteile: %d" % [ + count, _fmt_int(int(poly)), _fmt_int(budget), poly_pct, BauOps.teile_zahl(doc)] + # Nur bei knappem Budget auffällig färben (sonst ruhiger Nebentext). + stats_objects.add_theme_color_override("font_color", get_theme_color( + "warning" if poly_pct >= 90.0 or count >= 4500 else "field", "Editor")) + # GLB-Fehlende Objekte (Karte t_9b50b231): Platzhalter laufen, aber Sly soll + # sehen, WELCHE echten Modelle im Mesh-Lager nachgelegt werden müssen. + if view_builder != null and not (view_builder.get("fehlende_glbs") as Array).is_empty(): + var fehlend: Array = view_builder.get("fehlende_glbs") + var nennung := ", ".join(fehlend.slice(0, 3)) + if fehlend.size() > 3: + nennung += " … (+%d weitere)" % (fehlend.size() - 3) + stats_objects.text += " · ⚠ GLB fehlt: %s" % nennung + # Terrain-Anteil am Quest-3-Budget (res 513 = 524.288 Dreiecke, map_format.md §4) + var tris := 0 + var res := 0 + if view_builder != null and view_builder.get("terrain_view") != null: + tris = int(view_builder.terrain_view.call("triangle_count")) + res = int(view_builder.terrain_view.call("resolution")) + var quest_budget := 524288 + var pct := 100.0 * float(tris) / float(quest_budget) + var fps := Engine.get_frames_per_second() + stats_terrain.text = "Terrain %s² · %d FPS" % [str(res) if res > 0 else "—", fps] + stats_terrain.tooltip_text = "Terrain: %s² Grid, %s Dreiecke (%.0f %% Quest-Budget)" % [ + str(res) if res > 0 else "—", _fmt_int(tris), pct] + + +func _refresh_file_label() -> void: + var map_name := str(doc.get("info", {}).get("name", "Ohne Namen")) + if current_path.is_empty(): + file_label.text = "„%s“ (noch nicht gespeichert)%s" % [map_name, " •" if dirty else ""] + else: + file_label.text = "„%s“ — %s%s" % [map_name, current_path.get_file(), " •" if dirty else ""] + # Karte t_297221b5: bei egalisiertem Label (große Stufe, schmales Fenster) + # steht der volle Name im Tooltip. + file_label.tooltip_text = file_label.text + # U7 (Karte t_2336c58e): ungespeicherte Änderungen auch im FENSTERTITEL — + # so sieht man sie selbst dann, wenn die Datei-Leiste egalisiert ist. + get_window().title = "%s — %s%s" % [APP_TITLE, map_name, + " (ungespeichert)" if dirty else ""] + + +func _set_status(text: String, is_error := false) -> void: + if status_label == null or not is_instance_valid(status_label): + return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen + status_label.text = text + # U7: Statusfarben über das Theme (Editor/colors/status_error/_ok). + status_label.add_theme_color_override("font_color", + get_theme_color("status_error" if is_error else "status_ok", "Editor")) + + +func _fmt_int(value: int) -> String: + var s := str(value) + var out := "" + while s.length() > 3: + out = "." + s.substr(s.length() - 3) + out + s = s.substr(0, s.length() - 3) + return s + out + + +# ------------------------------------------------------- U1 Dock-Zustand ---- + +## Splitter-Lagen (Assets-Höhe) aus der Config lesen — NACH _build_ui. +func _load_dock_state() -> void: + var cfg := ConfigFile.new() + if cfg.load(UIScale.cfg_path()) == OK: + var l: Variant = cfg.get_value(UIScale.WIN_SECTION, "dock_links", null) + if l is float or l is int: + _dock_offsets["links"] = float(l) + var r: Variant = cfg.get_value(UIScale.WIN_SECTION, "dock_rechts", null) + if r is float or r is int: + _dock_offsets["rechts"] = float(r) + var u: Variant = cfg.get_value(UIScale.WIN_SECTION, "dock_unten", null) + if u is float or u is int: + _dock_offsets["unten"] = float(u) + _apply_dock_state() + + +## Gemerkte Lagen anwenden — deferred, weil das Layout erst nach dem ersten +## Layout-Durchlauf vermessene Größen hat. +func _apply_dock_state() -> void: + if DisplayServer.get_name() == "headless": + return + _apply_dock_state_deferred.call_deferred() + + +func _apply_dock_state_deferred() -> void: + if _dock_offsets.is_empty(): + return + var main_split := _find_split("MainSplit") + if main_split != null and _dock_offsets.has("links"): + main_split.split_offset = UIScale.f(float(_dock_offsets["links"])) + var bottom := _find_split("BottomSplit") + if bottom != null and _dock_offsets.has("unten"): + bottom.split_offset = -UIScale.f(float(_dock_offsets["unten"])) + + +func _find_split(sname: String) -> SplitContainer: + var found := find_children(sname, "SplitContainer", true, false) + return found[0] if not found.is_empty() else null + + +## Splitter-Lagen merken (beim Schließen, neben der Fensterlage). +func _save_dock_state() -> void: + if DisplayServer.get_name() == "headless": + return + var main_split := _find_split("MainSplit") + var bottom := _find_split("BottomSplit") + var cfg := ConfigFile.new() + cfg.load(UIScale.cfg_path()) + if main_split != null: + cfg.set_value(UIScale.WIN_SECTION, "dock_links", main_split.split_offset) + if bottom != null: + cfg.set_value(UIScale.WIN_SECTION, "dock_unten", float(-bottom.split_offset)) + cfg.save(UIScale.cfg_path()) + + +# ---------------------------------------------------- Screenshot-Demo ------- + +## Desktop-Smoke-Hilfe: `godot ... -- --ghost-demo [x z objekt-id]` parkt die +## Platzier-Vorschau sichtbar in der Map und zoomt die Kamera heran, damit ein +## Screenshot die Vorschau zeigen kann (nur für Smoke-Tests). +func _maybe_start_demo_mode() -> void: + var args := OS.get_cmdline_user_args() + if args.has("--terrain-demo"): + _start_terrain_demo(args) + return + if args.has("--library-demo"): + _start_library_demo(args) + return + if args.has("--spawns-demo"): + _start_spawns_demo(args) + return + if args.has("--groups-demo"): + _start_groups_demo(args) + return + if args.has("--bauen-demo"): + _start_bauen_demo(args) + return + if args.has("--ui-demo"): + _start_ui_demo(args) + return + if not args.has("--ghost-demo"): + return + var oid := "box_001" + var x := 0.0 + var z := 0.0 + for i in args.size(): + if args[i] == "--ghost-demo": + if i + 1 < args.size(): + x = float(args[i + 1]) + if i + 2 < args.size(): + z = float(args[i + 2]) + if i + 3 < args.size(): + oid = args[i + 3] + var pos := Vector3(x, SvmapDocument.terrain_height_at(doc, x, z), z) + interaction.demo_park_ghost(pos, oid) + # Demo-Aufbau: zwei Objekte setzen und eins orange auswählen — zeigt + # Ghost-Preview UND Auswahl-Markierung in einem Smoke-Screenshot. + if args.has("--ghost-demo-select"): + var before: Dictionary = history.snapshot(["objects"]) + (doc["objects"] as Array).append({"id": "water_tower_004", "pos": [6.0, 0.0, 0.0], "scale": 1.0, "rot_y_deg": 0.0}) + (doc["objects"] as Array).append({"id": "box_001", "pos": [0.0, 0.0, 0.0], "scale": 1.0, "rot_y_deg": 0.0}) + history.commit("Demo: Objekte gesetzt", before, history.snapshot(["objects"])) + interaction.call("clear_selection", true) + _refresh_all() + interaction.select_object(1, true) + else: + _refresh_history_buttons() + # Kamera nah heran, damit die Vorschau im Screenshot gut sichtbar ist. + camera.target = pos + Vector3(0.0, 0.6, 0.0) + camera.distance = 14.0 + camera.pitch_deg = 32.0 + camera.yaw_deg = 40.0 + camera.call("_apply") + + +# ----------------------------------------------------- UI-Größen-Demo-Screens -- + +## `godot ... -- --ui-demo [ordner] [--ui-demo-res BxH] [--ui-demo-quit]` +## (Karte t_297221b5): fährt ALLE UI-Größen-Stufen programmatisch durch — +## pro Stufe kompletter UI-Neuaufbau (derselbe Pfad wie der Menü-Klick) und +## ein Fenster-Screenshot. Beweist: umschaltbar im Lauf + nichts abgeschnitten. +func _start_ui_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_ui" + var idx := args.find("--ui-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + var res_idx := args.find("--ui-demo-res") + if res_idx >= 0 and res_idx + 1 < args.size(): + var parts := str(args[res_idx + 1]).split("x") + if parts.size() == 2: + # Fenstergröße NICHT zu früh setzen — direkt bei _ready wird der + # Wert von der Projekt-Einstellung überschrieben (xvfb-Befund); + # erst nach dem ersten Frame ist der Resize stabil. + await get_tree().process_frame + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + DisplayServer.window_set_size(Vector2i(int(parts[0]), int(parts[1]))) + get_window().size = Vector2i(int(parts[0]), int(parts[1])) # root-Window setzt durch (xvfb-Befund) + for i in UIScale.step_count(): + UIScale.set_step_index(i) + _rebuild_ui_for_scale() + await get_tree().process_frame + await get_tree().process_frame + _set_status("UI-Größe: %s (%.2f-fach) — Theme-Schrift %d px, Beweis-Screenshot." % [ + UIScale.step_label(i), UIScale.factor(), UIScale.font_size()]) + await _demo_shot(out_dir + "/ui_%s.png" % UIScale.step_key(i)) + print("UI_DEMO: Stufe %s Faktor %.2f Schrift %d" % [UIScale.step_key(i), UIScale.factor(), UIScale.font_size()]) + print("UI_DEMO_OK: %d Stufen, PNGs in %s" % [UIScale.step_count(), out_dir]) + if args.has("--ui-demo-quit"): + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden nach den Shots + + +# ------------------------------------------------ Terrain-Demo-Screenshots -- + +## `godot ... -- --terrain-demo [ordner]` — Desktop-Smoke (Karte t_9aba04f1): +## fährt die Terrain-Szenarien programmatisch durch und speichert PNGs: +## Hügel heben, Senke senken, glätten, Undo-Roundtrip. Die App bleibt offen +## (Sly sieht das Ergebnis live auf dem Desktop). +func _start_terrain_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_terrain" + var idx := args.find("--terrain-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + + _set_tool_mode("terrain") + terrain_mod.terrain_radius_slider.value = 45.0 + terrain_mod.terrain_strength_slider.value = 10.0 + terrain_mod.terrain_mode_raise.button_pressed = true + + # 1) Hügel heben — Pinsel-Ring bleibt als visueller Beweis im Bild. + terrain_tool.call("demo_stroke", -80.0, -60.0, TerrainField.Mode.RAISE, 1.4) + terrain_tool.call("demo_park_brush", -80.0, -60.0) + _demo_camera(Vector3(-80.0, 12.0, -60.0), 150.0, 32.0, 40.0) + _set_status("Demo 1/5: Hügel GEHOBEN (Radius 45 m, Stärke 10 m/s) — der grüne Ring ist der Pinsel.") + await _demo_shot(out_dir + "/terrain_raise.png") + + # 2) Senke senken (roter Ring). + terrain_mod.terrain_mode_lower.button_pressed = true + terrain_tool.call("demo_stroke", 90.0, 50.0, TerrainField.Mode.LOWER, 1.2) + terrain_tool.call("demo_park_brush", 90.0, 50.0) + _demo_camera(Vector3(90.0, 2.0, 50.0), 150.0, 32.0, -50.0) + _set_status("Demo 2/5: Senke GESSENKT (Strg+Linksklick senkt, ohne umzuschalten).") + await _demo_shot(out_dir + "/terrain_lower.png") + + # 3) Hügelkuppe glätten (blauer Ring). + terrain_mod.terrain_mode_smooth.button_pressed = true + terrain_tool.call("demo_stroke", -70.0, -50.0, TerrainField.Mode.SMOOTH, 0.9) + terrain_tool.call("demo_park_brush", -70.0, -50.0) + _demo_camera(Vector3(-70.0, 10.0, -50.0), 130.0, 35.0, 20.0) + _set_status("Demo 3/5: Kuppe GLÄTTET — rau wird weich, ohne die Form zu zerstören.") + await _demo_shot(out_dir + "/terrain_smooth.png") + + # 4) Undo-Roundtrip: kleiner Strich → Undo → Boden wieder flach. + terrain_mod.terrain_mode_raise.button_pressed = true + terrain_mod.terrain_radius_slider.value = 30.0 + terrain_tool.call("demo_stroke", 0.0, 150.0, TerrainField.Mode.RAISE, 0.9) + terrain_tool.call("demo_park_brush", 0.0, 150.0) + _demo_camera(Vector3(0.0, 8.0, 150.0), 120.0, 35.0, 0.0) + _set_status("Demo 4/5: Test-Hügel für den Undo-Vergleich geformt.") + await _demo_shot(out_dir + "/terrain_before_undo.png") + _do_undo() + _set_status("Demo 5/5: Strg+Z — der GANZE Strich ist zurück, der Boden ist wieder flach.") + await _demo_shot(out_dir + "/terrain_after_undo.png") + + print("TERRAIN_DEMO_OK: 5 Screenshots in %s" % out_dir) + + +func _demo_camera(target: Vector3, dist: float, pitch: float, yaw: float) -> void: + camera.target = target + camera.distance = dist + camera.pitch_deg = pitch + camera.yaw_deg = yaw + camera.call("_apply") + + +func _demo_shot(path: String) -> void: + for i in 5: + await get_tree().process_frame + await RenderingServer.frame_post_draw + var img := get_viewport().get_texture().get_image() + img.save_png(path) + print("DEMO_SHOT: %s (%d×%d)" % [path, img.get_width(), img.get_height()]) + var tv = view_builder.get("terrain_view") + if tv != null and is_instance_valid(tv): + var mi = tv.get("_mi") + if mi != null and is_instance_valid(mi) and mi.mesh != null: + var aabb: AABB = mi.mesh.get_aabb() + print("DEMO_AABB: pos=%s size=%s tris=%d" % [aabb.position, aabb.size, tv.triangle_count()]) + print("DEMO_CAM: cam=%s target=%s" % [camera.global_position, camera.target]) + + +## `godot ... -- --library-demo [ordner]` — Desktop-Smoke (Karte t_3a998ae4): +## Gruppen-Bibliothek + platzierte Objekte + Snap-to-Ground programmatisch +## durchspielen, PNGs speichern. Die App bleibt offen. +func _start_library_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_library" + var idx := args.find("--library-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + + # 1) Bibliothek-Browser: Gruppen sichtbar, Ghost-Preview aktiv. + _set_status("Demo 1/3: Bibliothek in Gruppen — ein Objekt anklicken setzt eine Vorschau in die Karte.") + interaction.demo_park_ghost(Vector3(10.0, SvmapDocument.terrain_height_at(doc, 10.0, 6.0), 6.0), "water_tower_004") + _demo_camera(Vector3(0.0, 2.0, 0.0), 90.0, 35.0, 40.0) + await _demo_shot(out_dir + "/library_browser.png") + + # 2) Objekte platziert (versch. Gruppen), eins orange ausgewählt. + var before: Dictionary = history.snapshot(["objects"]) + (doc["objects"] as Array).append({"id": "water_tower_004", "pos": [10.0, 0.0, 6.0], "scale": 1.0, "rot_y_deg": 30.0}) + (doc["objects"] as Array).append({"id": "box_001", "pos": [-8.0, 0.0, -4.0], "scale": 1.0, "rot_y_deg": 0.0}) + (doc["objects"] as Array).append({"id": "acacia_005", "pos": [-20.0, 0.0, 18.0], "scale": 1.0, "rot_y_deg": 75.0}) + (doc["objects"] as Array).append({"id": "rock_008", "pos": [24.0, 0.0, -18.0], "scale": 1.0, "rot_y_deg": 0.0}) + history.commit("Demo: Objekte gesetzt", before, history.snapshot(["objects"])) + interaction.call("cancel_placement") + _clear_edit_state() + _refresh_all() + interaction.select_object(0, true) + _demo_camera(Vector3(0.0, 2.0, 0.0), 110.0, 40.0, -35.0) + _set_status("Demo 2/3: Objekte platziert (Wachturm orange ausgewählt) — ziehen, drehen, löschen wie gehabt.") + await _demo_shot(out_dir + "/library_placed.png") + + # 3) Snap to Ground: Objekte künstlich in die Luft hängen, dann 1 Klick. + var float_before: Dictionary = history.snapshot(["objects"]) + for obj in (doc["objects"] as Array): + var pos: Array = obj["pos"] + obj["pos"] = [float(pos[0]), float(pos[1]) + 7.0, float(pos[2])] + history.commit("Demo: Objekte schweben", float_before, history.snapshot(["objects"])) + _refresh_all() + _demo_camera(Vector3(0.0, 6.0, 0.0), 110.0, 30.0, -35.0) + _set_status("Demo 3/3a: Objekte SCHWEBEN nach dem Terrain-Formen — ein Klick auf „Alles auf den Boden setzen\" …") + await _demo_shot(out_dir + "/library_floating.png") + _on_snap_to_ground() + _demo_camera(Vector3(0.0, 4.0, 0.0), 110.0, 38.0, -35.0) + _set_status("Demo 3/3b: … und schon stehen ALLE wieder auf dem Boden (Strg+Z nimmt den ganzen Klick zurück).") + await _demo_shot(out_dir + "/library_after_snap.png") + print("LIBRARY_DEMO_OK: 4 Screenshots in %s" % out_dir) + if args.has("--library-demo-quit"): + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind + + +## `godot ... -- --spawns-demo [ordner]` — Desktop-Smoke (Karte t_c4162c5d): +## Spawn-Zonen-Workflow programmatisch durchspielen, PNGs speichern: +## Zonen-Übersicht → Live-Check schlägt Alarm bei fehlenden Zonen → Undo rettet → +## Map-Check-Dialog meldet bereit. Die App bleibt offen (Sly sieht das live). +func _start_spawns_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_spawns" + var idx := args.find("--spawns-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + + # 1) Übersicht: Default-Zonen beider Teams (Kreise) + rote Ghost-Vorschau. + _set_tool_mode("spawns") + check_mod.spawn_team_red.button_pressed = true + spawn_tool.call("demo_park_ghost", 260.0, 120.0, 1, 12.0) + _demo_camera(Vector3(160.0, 140.0, 0.0), 700.0, 48.0, -90.0) + _set_status("Demo 1/4: Spawn-Zonen — blaue Kreise = Team Blau, rote = Team Rot, halbtransparent folgt der Ghost der Maus als Vorschau.") + await _demo_shot(out_dir + "/spawns_zones.png") + + # 2) ALLE roten Zonen löschen → Live-Check springt auf ❌ (BEVOR exportiert wird). + _clear_edit_state() + var before: Dictionary = SpawnOps.snapshot_blocks(history, doc) + for p in SpawnOps.points(doc, 1).duplicate(): + SpawnOps.remove_point(doc, 1, str(p["id"])) + history.commit("Demo: alle roten Zonen gelöscht", before, + SpawnOps.snapshot_blocks(history, doc)) + _after_structure_change() + _set_status("Demo 2/4: ALLE roten Zonen weg — der Spawn-Check (rechts) zeigt sofort ❌: Export wäre blockiert.") + await _demo_shot(out_dir + "/spawns_check.png") + + # 3) Strg+Z — alle vier roten Zonen sind zurück (EIN Undo-Schritt). + _do_undo() + _set_status("Demo 3/4: Strg+Z — alle 4 roten Zonen sind auf EINMAL zurück.") + await _demo_shot(out_dir + "/spawns_undo.png") + + # 4) Karte prüfen: Klartext-Liste im Dialog + Status „Alles bereit“. + _on_check_map() + _set_status("Demo 4/4: „Karte prüfen“ — alles ✅ … beim Speichern wird die .svmap automatisch signiert (sha256).") + await _demo_shot(out_dir + "/spawns_ready.png") + print("SPAWNS_DEMO_OK: 4 Screenshots in %s" % out_dir) + if args.has("--spawns-demo-quit"): + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind + + +## `godot ... -- --groups-demo [ordner]` — Desktop-Smoke (Karte t_f6f29d86): +## Auswahl-System programmatisch durchspielen, PNGs speichern: +## Mehrfachauswahl (Rahmen simuliert) → gruppieren → Gruppe gemeinsam ziehen → +## gemeinsames Drehen → Undo-Nachweis. Die App bleibt offen (Sly sieht live). +func _start_groups_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_groups" + var idx := args.find("--groups-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + + # Aufbau: 4 Objekte in zwei Wohnclustern (2 nahe beieinander = spätere Gruppe). + var before: Dictionary = history.snapshot(["objects"]) + (doc["objects"] as Array).append({"id": "water_tower_004", "pos": [10.0, 0.0, 6.0], "scale": 1.0, "rot_y_deg": 0.0}) + (doc["objects"] as Array).append({"id": "box_001", "pos": [14.0, 0.0, 9.0], "scale": 1.0, "rot_y_deg": 0.0}) + (doc["objects"] as Array).append({"id": "box_002", "pos": [14.0, 0.0, 13.0], "scale": 1.0, "rot_y_deg": 45.0}) + (doc["objects"] as Array).append({"id": "acacia_005", "pos": [-30.0, 0.0, 24.0], "scale": 1.0, "rot_y_deg": 0.0}) + history.commit("Demo: Objekte gesetzt", before, history.snapshot(["objects"])) + _refresh_all() + + # 1) Mehrfachauswahl: Objekte 0–2 markiert (wie nach Rahmen-Ausziehen). + interaction.call("select_many", [0, 1, 2], true) + _demo_camera(Vector3(0.0, 2.0, 0.0), 130.0, 40.0, -35.0) + _set_status("Demo 1/4: Mehrfachauswahl — 3 Objekte orange (Umschalt+Klick oder Rahmen aufziehen). Der Baum links bleibt außen vor.") + await _demo_shot(out_dir + "/groups_select.png") + + # 2) Gruppieren: EIN Klick, die 3 gehören ab jetzt zusammen. + interaction.call("group_selection") + _set_status("Demo 2/4: „Gruppieren“ — die 3 Objekte sind jetzt Gruppe 1. Der Baum links ist NICHT Teil der Gruppe.") + await _demo_shot(out_dir + "/groups_grouped.png") + + # 3) Gruppe gemeinsam ziehen (exakt der Drag-Release-Pfad) + gemeinsames Drehen. + interaction.call("demo_drag_selection", Vector3(24.0, 0.0, -18.0)) + interaction.call("rotate_selected", 30.0) + _demo_camera(Vector3(20.0, 2.0, 0.0), 130.0, 42.0, -20.0) + _set_status("Demo 3/4: Gruppe GEMEINSAM verschoben und um die eigene Mitte gedreht — die Anordnung bleibt erhalten, der Baum stand still.") + await _demo_shot(out_dir + "/groups_moved.png") + + # 4) Undo-Nachweis: Strg+Z nimmt die Drehung zurück (EIN Schritt), die + # zweite Stufe würde den Zug zurücknehmen — jede Aktion = EIN Undo-Schritt. + _do_undo() + _demo_camera(Vector3(20.0, 2.0, 0.0), 130.0, 42.0, -20.0) + _set_status("Demo 4/4: Strg+Z — die Drehung ist zurück (Zug & Drehen sind getrennte Schritte); die Gruppe bleibt gebunden.") + await _demo_shot(out_dir + "/groups_undo.png") + print("GROUPS_DEMO_OK: 4 Screenshots in %s" % out_dir) + if args.has("--groups-demo-quit"): + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind + + +## `godot ... -- --bauen-demo [ordner]` — Desktop-Smoke: baut programmatisch ein +## kleines Haus mit denselben Funktionen wie der Klick (BauSnap + BauOps), zeigt +## Ghost, Gebäude-Auswahl und Weiterbauen nach Speichern/Laden. Die App bleibt +## offen (Sly sieht das Ergebnis live). +func _start_bauen_demo(args: PackedStringArray) -> void: + var out_dir := "/tmp/scopedvr_bauen" + var idx := args.find("--bauen-demo") + if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"): + out_dir = args[idx + 1] + DirAccess.make_dir_recursive_absolute(out_dir) + _set_tool_mode("objects") + interaction.call("start_placement", "bau_wand") + interaction.call("demo_park_bau", Vector3(2.0, 0.0, 1.0)) + _demo_camera(Vector3(3.0, 2.0, 8.0), 14.0, 32.0, 25.0) + _set_status("Demo 1/3: Bauen — die Vorschau rastet auf die Zellkante ein.") + await _demo_shot(out_dir + "/bauen_ghost.png") + interaction.call("cancel_placement") + var gid := BauOps.haus_demo(doc, history, Vector3(4.0, 0.0, 4.0)) + _after_structure_change() + _demo_camera(Vector3(4.0, 1.5, 4.0), 13.0, 30.0, 35.0) + _set_status("Demo 2/3: Kleines Haus gebaut (Gruppe %d) — Fundament, Wände, Tür, Fenster, Giebel, Dach, Treppe." % (gid + 1)) + await _demo_shot(out_dir + "/bauen_haus.png") + _save_to(out_dir + "/bauen_demo.svmap") + var reload := SvmapIO.load_from_file(out_dir + "/bauen_demo.svmap") + if bool(reload["ok"]): + doc = reload["data"] + history.begin(doc) + _clear_edit_state() + _refresh_all() + _demo_camera(Vector3(4.0, 1.5, 4.0), 13.0, 30.0, 35.0) + _set_status("Demo 3/3: Gespeichert und wieder geladen — das Haus ist als Gruppe erhalten.") + await _demo_shot(out_dir + "/bauen_weiter.png") + print("BAUEN_DEMO_OK: 3 Screenshots in %s (%d Bauteile, Map OK)" % [out_dir, BauOps.teile_zahl(doc)]) + if args.has("--bauen-demo-quit"): + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind + + +## --- Test-Start: Haken (T1b-Vertrag, Operator-Wiederanbindung 20.10.) -------- +## Diese drei Namen erwartet tests/test_playtest_spawn.gd (56 checks) auf main. +## Sie waren beim U1-Umbau verschwunden (Suite brach mit „Nonexistent function +## '_test_start_reload'" ab und hing) — deshalb stehen sie hier bewusst fest. +## Der sichtbare Teil (Info + Löschen) lebt in core/ui/panels/test_start_panel.gd, +## der Zustand hier — genau wie vor dem Umbau. +func _test_start_apply(x: float, z: float, hinweis: String) -> void: + test_start_point = Vector3(x, 0.0, z) + TestStartStore.save_point(current_path, test_start_point) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + # Klartext-Hinweis des Werkzeugs (z.B. „auf dem Boden unter dem Objekt") + # mitnehmen — die Prüfsuite liest ihn aus der Statuszeile. + var ort := " (Höhe automatisch)" if hinweis.is_empty() else " (Höhe automatisch — %s)" % hinweis + _set_status("Test-Start: x=%.0f z=%.0f%s — F5 startet den Testlauf genau dort." % [x, z, ort]) + + +func _test_start_delete() -> void: + if test_start_point == null: + return + test_start_point = null + TestStartStore.clear_point(current_path) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + + +func _test_start_reload() -> void: + test_start_point = TestStartStore.load_point(current_path) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + + +func _refresh_test_start_panel() -> void: + if test_start_panel != null and is_instance_valid(test_start_panel): + test_start_panel.call("refresh_point", test_start_point) + + +## --- Test-Start: Werkzeug-Modus (wie Terrain/Spawns/Bauen) -------------------- +## Die Prüfsuite erwartet tool_mode == "teststart", einen leuchtenden Leisten- +## Knopf (`test_start_btn`) und das erst dann sichtbare Panel (`test_start_panel`). +func _enter_test_start_mode() -> void: + interaction.call("cancel_placement") + tool_mode = "teststart" + if test_start_tool != null: + test_start_tool.call("activate") + _sync_test_start_button() + _sync_test_start_panel_visibility() + + +func _leave_test_start_mode() -> void: + if test_start_tool != null: + test_start_tool.call("deactivate") + # Direkt aus-/einschalten statt über tool_mode: _set_tool_mode ruft die + # _leave_*-Funktionen BEVOR der Zielmodus gesetzt ist — ein Abgleich gegen + # tool_mode ließe das Panel offen (Befund aus test_playtest_spawn). + if test_start_panel != null: + test_start_panel.visible = false + if test_start_btn != null: + test_start_btn.button_pressed = false + + +func _sync_test_start_button() -> void: + if test_start_btn != null: + test_start_btn.button_pressed = (tool_mode == "teststart") + + +func _sync_test_start_panel_visibility() -> void: + if test_start_panel != null: + test_start_panel.visible = (tool_mode == "teststart") + + +## Leisten-Knopf „🎯 Test-Start" (registry.gd ACTIONS) — schaltet das Werkzeug um. +func _on_test_start_tool_pressed() -> void: + _set_tool_mode("objects" if tool_mode == "teststart" else "teststart") + + +# -------------------------------- Fremdformat-Dialog (t_9814f18f) ----------- + +## „Trotzdem laden (Prüfsumme erneuern)“ bestätigt: Datei NUR gegen die +## Kern-Validierung (V3–V16) prüfen, v2-Prüfsumme ins Dokument schreiben und +## normal öffnen. dirty=true: das Dokument weicht von der Datei ab — der +## nächste Speicher-Vorgang schreibt das v2-Schema in die Datei. +func _on_resign_confirmed() -> void: + var result := SvmapIO.load_from_file_resign(_resign_pending_path) + if not result["ok"]: + _show_errors("Map konnte NICHT geladen werden (auch ohne Prüfsummen-Prüfung):", result["errors"]) + _set_status("Laden fehlgeschlagen: %s" % _resign_pending_path.get_file(), true) + return + doc = result["data"] + SvmapIO.upgrade_material_layer(doc.get("terrain", {})) # feines Material-Raster + BauOps.migriere_gebaeude(doc) # alte Bau-Sektion → Bauteil-Objekte + current_path = _resign_pending_path + dirty = true + history.begin(doc) + _clear_edit_state() + _refresh_all() + _test_start_reload() + var map_name := str(doc.get("info", {}).get("name", "?")) + _set_status("Map „%s“ geladen — Prüfsumme erneuert (v2). Speichere die Map, um die neue Prüfsumme zu übernehmen." % map_name) + _refresh_file_label() diff --git a/ui/main.gd.uid b/ui/main.gd.uid new file mode 100644 index 0000000..115239e --- /dev/null +++ b/ui/main.gd.uid @@ -0,0 +1 @@ +uid://c5j3bplirtg6k diff --git a/ui/new_map_dialog.gd b/ui/new_map_dialog.gd new file mode 100644 index 0000000..d092869 --- /dev/null +++ b/ui/new_map_dialog.gd @@ -0,0 +1,94 @@ +extends ConfirmationDialog +## „Neue Map“-Dialog: Name, Größe, Wetter-Preset — bewusst nur 3 Fragen, +## alles Weitere setzt vernünftige Defaults (Chef-Grundsatz: easy). + +const UIScale := preload("res://core/ui/ui_scale.gd") # Karte t_297221b5 + +var name_edit: LineEdit +var size_option: OptionButton +var weather_option: OptionButton +var mode_option: OptionButton + +var _sizes: Array[float] = [512.0, 1024.0, 2048.0] +var _size_labels := ["Klein — 512 × 512 m", "Mittel — 1024 × 1024 m", "Groß — 2048 × 2048 m"] +var _weathers := ["mittag", "abend", "regen", "nacht"] +var _weather_labels := ["Mittag (sonnig, klar)", "Abend (warmes Licht)", "Regen (grau, kühl)", "Nacht (dunkel)"] + + +func _init() -> void: + title = "Neue Map" + ok_button_text = "Map erstellen" + cancel_button_text = "Abbrechen" + min_size = UIScale.v2i(460, 0) + + var vbox := VBoxContainer.new() + vbox.anchors_preset = Control.PRESET_FULL_RECT + vbox.set_anchors_preset(Control.PRESET_FULL_RECT) + vbox.add_theme_constant_override("separation", UIScale.px(12)) + add_child(vbox) + + vbox.add_child(_make_label("Wie soll die Map heißen?")) + name_edit = LineEdit.new() + name_edit.text = "Neue Map" + name_edit.max_length = 64 + name_edit.custom_minimum_size = UIScale.v2(0, 40) + vbox.add_child(name_edit) + + vbox.add_child(_make_label("Wie groß soll das Gelände sein?")) + size_option = OptionButton.new() + for label in _size_labels: + size_option.add_item(label) + size_option.select(1) # Mittel als Default + size_option.custom_minimum_size = UIScale.v2(0, 40) + # U7: Klartext-Tooltips (Karte t_2336c58e). + size_option.tooltip_text = "Kantenlänge des Geländes — größer = mehr Platz zum Bauen" + vbox.add_child(size_option) + + vbox.add_child(_make_label("Welches Wetter soll die Map haben?")) + weather_option = OptionButton.new() + for label in _weather_labels: + weather_option.add_item(label) + weather_option.custom_minimum_size = UIScale.v2(0, 40) + weather_option.tooltip_text = "Licht und Stimmung der Karte (Tag/Abend/Regen/Nacht)" + vbox.add_child(weather_option) + + vbox.add_child(_make_label("Welchen Spielmodus soll die Map haben?")) + mode_option = OptionButton.new() + for m in ModusKatalog.MODES: + mode_option.add_item(ModusKatalog.MODE_LABELS[m]) + mode_option.custom_minimum_size = UIScale.v2(0, 40) + mode_option.tooltip_text = "Team-Deathmatch: Kills zählen. Conquest: Teams nehmen mehrere Zonen ein. King of the Hill: ein Hill, der wandert. Deathmatch: alle gegen alle. Lässt sich später im Map-Tab ändern." + vbox.add_child(mode_option) + + var hint := _make_label("Die Map startet leer mit 4 Spawn-Punkten pro Team\n(Blau im Westen, Rot im Osten) — beides lässt sich später ändern.") + hint.modulate = Color(1, 1, 1, 0.7) + vbox.add_child(hint) + + +func _make_label(text: String) -> Label: + var label := Label.new() + label.text = text + label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + # FIX 20.10.: autowrap-Label ohne Mindestbreite meldet vor dem ersten + # Layout eine explosionsartige Mindesthöhe (Probe: Fenster 4781 px hoch + # statt ~390) — Popup-Größe wird daraus einmalig übernommen. Mindestbreite + # ~ Dialogbreite verankert den Umbruch an der echten Breite. + label.custom_minimum_size = Vector2(UIScale.px(440), 0) + return label + + +func get_map_name() -> String: + var n := name_edit.text.strip_edges() + return n if not n.is_empty() else "Neue Map" + + +func get_size_m() -> float: + return _sizes[size_option.selected] + + +func get_game_mode() -> String: + return ModusKatalog.MODES[mode_option.selected] + + +func get_weather() -> String: + return _weathers[weather_option.selected] diff --git a/ui/new_map_dialog.gd.uid b/ui/new_map_dialog.gd.uid new file mode 100644 index 0000000..4bc1260 --- /dev/null +++ b/ui/new_map_dialog.gd.uid @@ -0,0 +1 @@ +uid://d1tevsdsnf8ih diff --git a/ui/panels/assets_panel.gd b/ui/panels/assets_panel.gd new file mode 100644 index 0000000..62d223c --- /dev/null +++ b/ui/panels/assets_panel.gd @@ -0,0 +1,782 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Objekt-Bibliothek / Assets-Bereich (aus main.gd ausgezogen, Karte t_eaccbfed; +## Unity-artiger Ausbau Karte t_69687afc/U4): Kategorien-Leiste, Suche, Zuletzt +## benutzt, Vorschau-Kacheln mit Name + ID, Hover vergrößert die ID, fehlende +## Modelle werden markiert (nicht stillschweigend), Klick wählt aus, +## Doppelklick startet das Platzier-Werkzeug wie bisher. +## Der Bereich wohnt UNTEN unter der 3D-Ansicht (Unity-Projekt-Fenster-Platz). +## +## Kompatibilität (main.gd bindet diese Felder — NICHT umbenennen): +## library_search, library_buttons, _library_group_rows, +## library_open_all_btn, library_close_all_btn, +## _on_library_pressed(), _apply_library_filter(), +## _on_library_expand_all(), _on_library_collapse_all(), highlight_library(). +## Die Klapp-Gruppen bleiben als Körper der „Alle"-Ansicht erhalten; die +## Kategorien-Leiste filtert dieselben Zeilen (Mechanik wie die Suche). + +signal object_selected(oid: String) # Klick auf eine Kachel (nur bei Wechsel) +signal object_activated(oid: String) # Doppelklick — Werkzeug-Start (U5 koppelt) + +const LIBRARY_CFG_SECTION := "library" # Abschnitt in user://scopedvr_editor.cfg +const RECENT_CFG_KEY := "zuletzt_benutzt" # selbiger Abschnitt, Liste von IDs +const RECENT_MAX := 12 # gemerkte Objekte (neueste zuerst) +const CAT_ALLE := "alle" # Pseudo-Kategorie: alles +const CAT_ZULETZT := "zuletzt" # Pseudo-Kategorie: zuletzt benutzt + +var library_box: VBoxContainer +var library_buttons: Dictionary = {} # id → Button (Haupt-Grid, kanonisch) +## Bibliothek-Gruppen (t_59fc0f21): Klapp-Zustand + Zeilen für Suche/Alle auf/zu. +var library_search: LineEdit +var library_open_all_btn: Button +var library_close_all_btn: Button +var _library_group_open: Dictionary = {} # Gruppen-Key → auf/zu +var _library_group_rows: Array = [] # {key, header, box, ids} +var _library_bold_font: Font = null + +# --- U4 neu: Kategorien, Zähler, Zuletzt benutzt, Auswahl ------------------- +var library_counter: Label # „N von M sichtbar" +var _cat_chips: HFlowContainer # Kategorien-Leiste +var _cat_buttons: Dictionary = {} # Key → Chip-Button +var _cat_active: String = CAT_ALLE # aktive Kategorie (Chip) +var _recent_box: VBoxContainer # Zuletzt-benutzt-Ansicht (im Scroll) +var _recent_grid: GridContainer +var _recent_tiles: Dictionary = {} # id → Button (Zuletzt-Ansicht, frisch gebaut) +var _recent_ids: Array = [] # Reihenfolge: neueste zuerst +var _drag_oid := "" # Kachel, die gerade gezogen wird +var _drag_started_placement := false # Ziehen hat das Platzieren gestartet +var selected_oid: String = "" # per Klick gewähltes Objekt +var _id_font_normal: int = 0 # ID-Schriftgröße (skaliert), Normalzustand +var _empty_hint: Label # Leerzustand (U7): kein Treffer / leerer Bereich + + +static func spec() -> Dictionary: + return {"titel": "Objekt-Bibliothek", "gruppe": "unten", "order": 10} + + +func panel_title() -> String: + return "Objekt-Bibliothek" + + +## Bau: Überschrift → Suche → Kategorien-Leiste → Alle auf/zu → Zähler → +## Klapp-Gruppen-Körper („Alle") + Zuletzt-benutzt-Ansicht. +func build() -> void: + add_child(_header_label("Objekt-Bibliothek")) + + # Suche (t_59fc0f21): tippen filtert live nach Name/ID — Gruppen mit + # Treffern klappen auf, leere Gruppen verschwinden. Größe ist von + # test_ui_scale gepinnt (36 → 45 bei „Mittel") — nicht ändern. + library_search = LineEdit.new() + library_search.placeholder_text = "Suchen (Name oder ID)…" + library_search.clear_button_enabled = true + library_search.custom_minimum_size = UIScale.v2(0, 36) + library_search.text_changed.connect(_apply_library_filter) + library_search.text_submitted.connect(func(_t: String) -> void: library_search.release_focus()) + add_child(library_search) + + _build_category_chips() + + # „Alle auf" / „Alle zu" (t_59fc0f21): Klapp-Zustand aller Gruppen auf einmal. + var group_btns := HBoxContainer.new() + group_btns.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster + library_open_all_btn = Button.new() + library_open_all_btn.text = "Alle auf" + library_open_all_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + library_open_all_btn.tooltip_text = "Klappt ALLE Gruppen auf" + library_open_all_btn.pressed.connect(_on_library_expand_all) + group_btns.add_child(library_open_all_btn) + library_close_all_btn = Button.new() + library_close_all_btn.text = "Alle zu" + library_close_all_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + library_close_all_btn.tooltip_text = "Klappt ALLE Gruppen zu" + library_close_all_btn.pressed.connect(_on_library_collapse_all) + group_btns.add_child(library_close_all_btn) + add_child(group_btns) + + # Zähler „N von M sichtbar" (U4): M = Objekte im aktiven Bereich, N = davon + # durch Suche/Kategorie sichtbar. + library_counter = Label.new() + # U7: Nebentext-Größe + Theme-Farbe (Editor/colors/field). + library_counter.add_theme_font_size_override("font_size", UIScale.small_font_size()) + library_counter.add_theme_color_override("font_color", theme_color("field")) + library_counter.tooltip_text = "Sichtbare Kacheln im aktiven Bereich" + add_child(library_counter) + + var scroll := ScrollContainer.new() + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.custom_minimum_size = UIScale.v2(0, 320) + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + add_child(scroll) + library_box = VBoxContainer.new() + library_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL + library_box.add_theme_constant_override("separation", UIScale.px(4)) + scroll.add_child(library_box) + # Leerzustand (U7, „fertig aussehen"): freundlicher Klartext statt + # stummer leerer Fläche — Suchbegriff ohne Treffer oder leerer Bereich. + _empty_hint = Label.new() + _empty_hint.text = "Nichts gefunden — anderen Suchbegriff oder „Alle“ versuchen." + _empty_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _empty_hint.add_theme_color_override("font_color", theme_color("muted")) + _empty_hint.visible = false + scroll.add_child(_empty_hint) + _recent_box = VBoxContainer.new() + _recent_box.visible = false + _recent_box.add_theme_constant_override("separation", UIScale.px(4)) + library_box.add_child(_recent_box) + _fill_library() + _recent_ids = _recent_load() + _update_recent_chip() + _apply_library_filter(library_search.text) + + +# ------------------------------------------------------------ Kategorien ---- + +## Kategorien-Leiste (U4): „Alle" + eine Chip je Bibliotheks-Gruppe +## (LibraryBrowser.groups_in_order — KEINE eigene Kategorieliste) + +## „Zuletzt benutzt". Chips zeigen Name + Anzahl; Summe der Gruppen-Chips = +## Gesamtzahl (test_assets_panel rechnet nach). +func _build_category_chips() -> void: + _cat_chips = HFlowContainer.new() + _cat_chips.add_theme_constant_override("h_separation", UIScale.px(4)) + _cat_chips.add_theme_constant_override("v_separation", UIScale.px(4)) + add_child(_cat_chips) + var bg := ButtonGroup.new() + var groups := LibraryBrowser.groups_in_order(main.manifest) + _cat_add_chip(CAT_ALLE, "Alle", _manifest_count(), bg) + for group in groups: + var g_key: String = group["key"] + var g_ids: Array = group["ids"] + if g_ids.is_empty(): + continue + _cat_add_chip(g_key, str(group["label"]), g_ids.size(), bg) + _cat_add_chip(CAT_ZULETZT, "Zuletzt benutzt", 0, bg) + (_cat_buttons[CAT_ZULETZT] as Button).visible = false # taucht auf, sobald benutzt + + +func _manifest_count() -> int: + return (main.manifest.get("objects", {}) as Dictionary).size() + + +func _cat_add_chip(key: String, label: String, count: int, bg: ButtonGroup) -> void: + var chip := Button.new() + chip.toggle_mode = true + chip.button_group = bg + chip.button_pressed = key == _cat_active + chip.text = "%s (%d)" % [label, count] if key != CAT_ZULETZT else label + chip.custom_minimum_size = UIScale.v2(0, 28) + chip.tooltip_text = "Zeigt nur die Gruppe „%s“" % label if key != CAT_ALLE and key != CAT_ZULETZT else "Zeigt alle Objekte" if key == CAT_ALLE else "Die zuletzt ausgewählten/benutzten Objekte" + chip.set_meta("cat_key", key) + chip.set_meta("cat_count", count) + chip.toggled.connect(_on_cat_chip_toggled.bind(key)) + _cat_chips.add_child(chip) + _cat_buttons[key] = chip + + +func _on_cat_chip_toggled(on: bool, key: String) -> void: + if not on: + return # Radio-Gruppe: der neue Chip drückt sich selbst an + _select_category(key) + + +## Kategorie wechseln: Zustand merken (kein Config-Zwang — Chips sind +## Ansichtsfilter, der Klapp-Zustand je Gruppe bleibt in der cfg). +func _select_category(key: String) -> void: + if key != CAT_ZULETZT and not _cat_buttons.has(key): + return + _cat_active = key + for chip_key in _cat_buttons: + var chip := _cat_buttons[chip_key] as Button + chip.set_pressed_no_signal(str(chip_key) == _cat_active) + _apply_library_filter(library_search.text) + + +func _update_recent_chip() -> void: + var chip := _cat_buttons.get(CAT_ZULETZT) as Button + if chip == null: + return + chip.visible = not _recent_ids.is_empty() + chip.text = "Zuletzt benutzt (%d)" % _recent_ids.size() + + +## Objekte des aktiven Bereichs (M im Zähler): Alle → Gesamtkatalog, +## Kategorie → deren IDs, Zuletzt → gemerkte Liste. +func _scope_ids() -> Array: + var lib: Dictionary = main.manifest.get("objects", {}) + if _cat_active == CAT_ALLE: + return lib.keys() + if _cat_active == CAT_ZULETZT: + return _recent_ids.duplicate() + for row in _library_group_rows: + if str(row["key"]) == _cat_active: + return (row["ids"] as Array).duplicate() + return [] + + +# -------------------------------------------------------- Klapp-Gruppen ---- + +func _fill_library() -> void: + library_buttons.clear() + _library_group_rows.clear() + _library_group_open = _library_load_open_states() + # Gruppen-Browser (Karte t_3a998ae4, Überarbeitung t_59fc0f21): + # - Klapp-Überschriften je Gruppe (fett, 18 px, Name + Anzahl), + # - STANDARD EINGEKLAPPT — nur die erste Gruppe ist offen (Übersicht!), + # - Klapp-Zustand je Gruppe wird in user://scopedvr_editor.cfg gemerkt, + # - Objekte als Kacheln mit 96-px-Vorschaubild (2 Spalten je Gruppe). + # U4: der Körper bleibt („Alle“-Ansicht), die Kategorien-Leiste filtert ihn. + var first_open: String = "" + for group in LibraryBrowser.groups_in_order(main.manifest): + var g_key: String = group["key"] + var g_label: String = group["label"] + var g_ids: Array = group["ids"] + if g_ids.is_empty(): + continue + if first_open.is_empty(): + first_open = g_key + var is_open: bool = _library_group_open.get(g_key, g_key == first_open) + _library_group_open[g_key] = is_open + var header := Button.new() + header.text = _library_header_text(is_open, g_label, g_ids.size()) + header.toggle_mode = true + header.button_pressed = is_open + header.alignment = HORIZONTAL_ALIGNMENT_LEFT + header.custom_minimum_size = UIScale.v2(0, 34) + header.tooltip_text = "Gruppe „%s“ auf-/zuklappen" % g_label + header.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + header.add_theme_font_override("font", _library_header_font()) + library_box.add_child(header) + var group_box := VBoxContainer.new() + group_box.add_theme_constant_override("separation", UIScale.px(4)) + group_box.visible = is_open + var grid := GridContainer.new() + grid.columns = 2 + grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL + grid.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + grid.add_theme_constant_override("v_separation", UIScale.px(8)) # U7-Raster + group_box.add_child(grid) + library_box.add_child(group_box) + header.toggled.connect(_on_library_group_toggled.bind(g_key, g_label, g_ids.size(), header, group_box)) + for oid in g_ids: + _add_library_button(str(oid), grid) + _library_group_rows.append({"key": g_key, "header": header, "box": group_box, "ids": g_ids, "label": g_label, "count": g_ids.size(), "grid": grid}) + if not resized.is_connected(_on_panel_resized): + resized.connect(_on_panel_resized) + _on_panel_resized() + + +## Unity-Projekt-Fenster-Feeling (U4): die Spaltenzahl passt sich der BREITE +## des unteren Bereichs an (schmaler Dock = wenige Spalten, breiter = viele). +## Kachel-Zielbreite ≈ 180 px (skaliert über ui_scale). +func _on_panel_resized() -> void: + _apply_grid_columns(size.x) + + +func _apply_grid_columns(width: float) -> void: + var cols := clampi(int(width / float(UIScale.px(200))), 1, 12) + for row in _library_group_rows: + ((row["grid"] as GridContainer)).columns = cols + if _recent_grid != null: + _recent_grid.columns = cols + + +## Kompletter Neubau des Bibliotheks-Körpers (Probe/Umbau): alte Zeilen frei +## geben, dann frisch füllen und den Filter anwenden. Die Zuletzt-Ansicht +## (_recent_box) überlebt den Neubau — sie wird nur aus dem Baum genommen und +## wieder eingehängt, damit die Referenz NICHT auf ein freigegebenes Objekt +## zeigt (sonst „previously freed" im nächsten Filterlauf). +func _rebuild_library() -> void: + if _recent_box != null and _recent_box.get_parent() == library_box: + library_box.remove_child(_recent_box) + for c in library_box.get_children(): + library_box.remove_child(c) + (c as Node).free() + if _recent_box != null and _recent_box.get_parent() == null: + library_box.add_child(_recent_box) + _fill_library() + _apply_library_filter(library_search.text) + + +func _library_header_text(on: bool, label: String, count: int) -> String: + return "%s %s (%d)" % ["▾" if on else "▸", label, count] + + +func _on_library_group_toggled(on: bool, g_key: String, g_label: String, count: int, header: Button, group_box: VBoxContainer) -> void: + group_box.visible = on + header.text = _library_header_text(on, g_label, count) + _library_group_open[g_key] = on + _library_save_open_state(g_key, on) + + +func _on_library_expand_all() -> void: + for row in _library_group_rows: + var header := row["header"] as Button + header.button_pressed = true + # toggled feuert → box sichtbar; Filter danach neu anwenden, damit + # ein aktiver Kategorie-Filter/Suche nicht weggeblasen wird. + _apply_library_filter(library_search.text) + + +func _on_library_collapse_all() -> void: + for row in _library_group_rows: + var header := row["header"] as Button + header.button_pressed = false + _apply_library_filter(library_search.text) + + +func _library_header_font() -> Font: + if _library_bold_font == null: + var sf := SystemFont.new() + sf.font_names = PackedStringArray(["Noto Sans", "DejaVu Sans", "Ubuntu", "sans-serif"]) + sf.font_weight = 700 + _library_bold_font = sf + return _library_bold_font + + +func _library_load_open_states() -> Dictionary: + var cfg := ConfigFile.new() + cfg.load("user://scopedvr_editor.cfg") + var out := {} + if cfg.has_section(LIBRARY_CFG_SECTION): + for key in cfg.get_section_keys(LIBRARY_CFG_SECTION): + if str(key) == RECENT_CFG_KEY: + continue + # Robustheit (t_efb3d022): cfg-Einträge dieser Sektion müssen Bool- + # Zustände sein. Ein kaputter Eintrag (String/Array/sonstwas) darf + # KEINEN SCRIPT ERROR werfen (bool() ist kein gültiger + # Konstruktor) — sonst brechen die Klapp-Zustände komplett weg. + var val = cfg.get_value(LIBRARY_CFG_SECTION, str(key), true) + if typeof(val) == TYPE_BOOL: + out[str(key)] = val + else: + out[str(key)] = true + return out + + +func _library_save_open_state(g_key: String, on: bool) -> void: + var cfg := ConfigFile.new() + cfg.load("user://scopedvr_editor.cfg") + cfg.set_value(LIBRARY_CFG_SECTION, g_key, on) + cfg.save("user://scopedvr_editor.cfg") + + +# ----------------------------------------------------------------- Suche ---- + +## Live-Filter (t_59fc0f21, U4 erweitert): Suche (Name de/en, ID — case- +## insensitive) geht VOR der Kategorie (sucht im ganzen Katalog); ohne Suche +## zeigt die aktive Kategorie nur ihre Gruppe, „Alle" den Klapp-Zustand wie +## gemerkt, „Zuletzt" die Gemerkten. Danach Zähler „N von M sichtbar". +func _apply_library_filter(query: String) -> void: + var q := query.strip_edges().to_lower() + var searching := not q.is_empty() + var visible_count := 0 + # Suche gilt immer für den GANZEN Katalog; ohne Suche zählt der aktive Bereich. + var scope: int = _manifest_count() if not q.is_empty() else _scope_ids().size() + _recent_box.visible = false + if searching: + for row in _library_group_rows: + var ids: Array = row["ids"] + var any := false + for oid in ids: + var btn := library_buttons.get(str(oid)) as Button + if btn == null: + continue + var vis := _library_oid_matches(str(oid), q) + btn.visible = vis + any = any or vis + if vis: + visible_count += 1 + var header := row["header"] as Button + var box := row["box"] as VBoxContainer + header.visible = any + # Treffer-Gruppen bleiben auf — auch wenn sie zugeklappt waren + # (Pfeil zeigt dann offen, kosmetisch konsistent). + box.visible = any + if any: + header.text = _library_header_text(true, str(row["label"]), int(row["count"])) + elif _cat_active == CAT_ZULETZT: + for row in _library_group_rows: + (row["header"] as Button).visible = false + (row["box"] as VBoxContainer).visible = false + for oid in row["ids"]: + var btn := library_buttons.get(str(oid)) as Button + if btn != null: + btn.visible = false + _fill_recent_box() + _recent_box.visible = true + for oid in _recent_ids: + if _recent_tiles.has(str(oid)): + visible_count += 1 + else: + for row in _library_group_rows: + var in_cat: bool = _cat_active == CAT_ALLE or str(row["key"]) == _cat_active + var ids: Array = row["ids"] + var header := row["header"] as Button + var box := row["box"] as VBoxContainer + header.visible = in_cat + if not in_cat: + box.visible = false + for oid in ids: + var btn := library_buttons.get(str(oid)) as Button + if btn != null: + btn.visible = false + continue + var is_open := bool(_library_group_open.get(str(row["key"]), false)) + if _cat_active != CAT_ALLE: + # Kategorie-Ansicht zeigt DIESE Gruppe geöffnet (wie die + # Suche ihre Treffer-Gruppen öffnet) — ohne den gemerkten + # Klapp-Zustand zu überschreiben. + is_open = true + box.visible = is_open + header.text = _library_header_text(is_open, str(row["label"]), int(row["count"])) + for oid in ids: + var btn := library_buttons.get(str(oid)) as Button + if btn != null: + btn.visible = is_open + if is_open: + visible_count += 1 + if library_counter != null: + library_counter.text = "%d von %d sichtbar" % [visible_count, scope] + # Leerzustand (U7): 0 Treffer → Klartext-Hinweis zeigen statt stummer Fläche. + if _empty_hint != null: + _empty_hint.visible = visible_count == 0 + + +func _library_oid_matches(oid: String, q: String) -> bool: + if oid.to_lower().contains(q): + return true + var entry: Dictionary = (main.manifest.get("objects", {}) as Dictionary).get(oid, {}) + if LibraryBrowser.entry_label(oid, entry).to_lower().contains(q): + return true + if LibraryBrowser.object_label(oid).to_lower().contains(q): + return true + return false + + +# ---------------------------------------------------------------- Kacheln ---- + +## Kachel in ein Grid hängen. canonical = Haupt-Grid (landet in +## library_buttons, wird von interaction/_highlight_library gefunden); +## Zuletzt-benutzt-Kacheln sind eigene frische Buttons (kein Dict-Konflikt). +func _add_library_button(oid: String, parent: Container) -> void: + var btn := _build_tile(oid) + parent.add_child(btn) + library_buttons[oid] = btn + + +func _build_tile(oid: String) -> Button: + var lib: Dictionary = main.manifest.get("objects", {}) + var entry: Dictionary = lib.get(oid, {}) + var poly := int(entry.get("poly", 0)) + var name_txt := LibraryBrowser.entry_label(oid, entry) + var missing := _tile_is_missing(oid, entry) + var btn := Button.new() + btn.toggle_mode = true + btn.button_pressed = false + btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + btn.custom_minimum_size = UIScale.v2(0, 188) + btn.tooltip_text = "%s · %d Polys · %s" % [name_txt, poly, oid] + if missing: + btn.tooltip_text += " · MODELL FEHLT (Platzhalter-Kiste)" + btn.pressed.connect(_on_tile_pressed.bind(oid)) + btn.gui_input.connect(_on_tile_gui_input.bind(oid)) + # Ziehen & Loslassen (25.10.): Kachel anklicken, gedrückt halten, in die Szene + # ziehen — beim Loslassen wird das Objekt dort gesetzt (Doppelklick geht weiter). + btn.set_drag_forwarding(_tile_drag_data.bind(oid, btn), Callable(), Callable()) + btn.mouse_entered.connect(_on_tile_hover.bind(oid, true)) + btn.mouse_exited.connect(_on_tile_hover.bind(oid, false)) + var tex := LibraryBrowser.load_thumb(oid) + if tex == null: + # Kein Thumbnail → Text-Zeile wie früher (Fehlertoleranz, t_3a998ae4). + btn.text = "%s · %d Polys" % [name_txt, poly] + btn.alignment = HORIZONTAL_ALIGNMENT_LEFT + btn.custom_minimum_size = UIScale.v2(0, 44) + else: + # Kachel (t_59fc0f21 + U4): 96-px-Vorschaubild oben, ID + Name darunter. + # Kinder ignorieren die Maus, damit die Klicks am Button ankommen. + # Reihenfolge Bild → ID → Name: der Name bleibt das LETZTE Label + # (test_library_browser_ui liest das letzte Label als Namen). + var tile := VBoxContainer.new() + tile.mouse_filter = Control.MOUSE_FILTER_IGNORE + tile.set_anchors_preset(Control.PRESET_FULL_RECT) + tile.add_theme_constant_override("separation", UIScale.px(4)) # U7-Raster + btn.add_child(tile) + # Vorschaubild: bleibt DIREKTES Kind der Kachel-VBox (test_library_ + # browser_ui sucht es dort) mit gepinnter Min-Größe (test_ui_scale + # sucht THUMB_DISPLAY_PX × Stufenfaktor). Die „Modell fehlt“-Marke + # hängt als Badge-KIND am TextureRect — der Button ist kein Container, + # eine Extra-Zeile unter dem Bild würde unter die Kachelkante rutschen + # und von der nächsten Reihe überdeckt. + var tr := TextureRect.new() + tr.texture = tex + tr.custom_minimum_size = UIScale.v2(LibraryBrowser.THUMB_DISPLAY_PX, LibraryBrowser.THUMB_DISPLAY_PX) + tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + tr.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + tr.mouse_filter = Control.MOUSE_FILTER_IGNORE + tile.add_child(tr) + var id_lbl := Label.new() + id_lbl.text = oid + # U7: Tile-ID als Nebentext-Größe, Farbe über Theme (Editor/colors/title). + _id_font_normal = UIScale.small_font_size() + id_lbl.add_theme_font_size_override("font_size", _id_font_normal) + id_lbl.add_theme_color_override("font_color", theme_color("title")) + id_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + id_lbl.clip_text = true + id_lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + id_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE + id_lbl.set_meta("tile_id_label", true) + tile.add_child(id_lbl) + var lbl := Label.new() + lbl.text = name_txt + lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + lbl.clip_text = true + lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE + tile.add_child(lbl) + if missing: + # Klar sichtbar markieren (U4): Bild abgedunkelt + Warn-Badge + # als Overlay am unteren Bildrand (immer innerhalb der Kachel). + # U7: Abdunkeln nur über Alpha (Farbe bleibt weiß), Badge-Farbe + # über Theme (Editor/colors/warning + badge_outline), Nebentext- + # Größe statt Mikroschrift (U7-Raster: zwei Größen + Überschrift). + tr.modulate.a = 0.45 + tr.clip_contents = true + var warn := Label.new() + warn.text = "Modell fehlt" + warn.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) + warn.offset_top = -UIScale.px(18) + warn.offset_bottom = -UIScale.px(1) + warn.add_theme_font_size_override("font_size", UIScale.small_font_size()) + warn.add_theme_color_override("font_color", theme_color("warning")) + warn.add_theme_color_override("font_outline_color", theme_color("badge_outline")) + warn.add_theme_constant_override("outline_size", UIScale.px(3)) + warn.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + warn.mouse_filter = Control.MOUSE_FILTER_IGNORE + tr.add_child(warn) + return btn + + +## Fehlendes Modell (U4): nur Katalog-Objekte MIT glb-Eintrag können fehlen; +## kuratierte Primitive (ohne glb) bauen ihren Platzhalter lokal — nie „fehlt". +func _tile_is_missing(oid: String, entry: Dictionary) -> bool: + if str(entry.get("glb", "")).is_empty(): + return false + return GlbMeshStore.fehlt(oid) + + +## Hover (U4): die ID wird GRÖSSER, beim Verlassen wieder normal. +func _on_tile_hover(oid: String, entered: bool) -> void: + _set_tile_hover(oid, entered) + + +func _set_tile_hover(oid: String, on: bool) -> void: + var btn := _tile_button(oid) + if btn == null: + return + var id_lbl := _find_id_label(btn) + if id_lbl == null: + return + # U7-Raster: Hover-Stufe ist die Body-Schrift (klein → body), kein Wert ins Freie. + id_lbl.add_theme_font_size_override("font_size", + UIScale.font_size() if on else _id_font_normal) + + +func _tile_button(oid: String) -> Button: + if library_buttons.has(oid): + return library_buttons[oid] as Button + if _recent_tiles.has(oid): + return _recent_tiles[oid] as Button + return null + + +func _find_id_label(btn: Button) -> Label: + var stack: Array = [btn] + while not stack.is_empty(): + var node: Node = stack.pop_back() + if node is Label and (node as Label).has_meta("tile_id_label"): + return node as Label + for c in node.get_children(): + stack.push_back(c) + return null + + +# --------------------------------------------------- Klick / Doppelklick ---- + +## Klick = auswählen (U4). Das Platzieren selbst startet NICHT mehr hier — +## das übernimmt der Doppelklick (bzw. U5 das Einmal-Platzieren). +func _on_tile_pressed(oid: String) -> void: + _select_tile(oid) + + +## Doppelklick = „gesetzt" haken: Werkzeug startet wie bisher (U4-Vertrag; +## das Einmal-Platzieren kommt in U5). +func _on_tile_gui_input(event: InputEvent, oid: String) -> void: + if event is InputEventMouseButton and event.pressed \ + and event.button_index == MOUSE_BUTTON_LEFT and event.double_click: + _activate_tile(oid) + + +## Ziehen-Start einer Kachel: Vorschau-Etikett an der Maus, Daten = Objekt-ID. +func _tile_drag_data(_at_position: Vector2, oid: String, btn: Button) -> Variant: + if main.doc.is_empty(): + return null + _select_tile(oid) + _drag_oid = oid + var prev := PanelContainer.new() + var box := HBoxContainer.new() + prev.add_child(box) + var tex := LibraryBrowser.load_thumb(oid) + if tex != null: + var tr := TextureRect.new() + tr.texture = tex + tr.custom_minimum_size = UIScale.v2(64, 64) + tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + box.add_child(tr) + var lbl := Label.new() + lbl.text = LibraryBrowser.entry_label(oid, (main.manifest.get("objects", {}) as Dictionary).get(oid, {})) + box.add_child(lbl) + prev.modulate.a = 0.85 + btn.set_drag_preview(prev) # gehört in den Drag-Start: gui_is_dragging() ist hier noch false + return {"svr_oid": oid} + + +## Maus mit gezogener Kachel ÜBER der 3D-Ansicht: Platzier-Vorschau folgt der Maus. +func drag_hover(oid: String, viewport_pos: Vector2) -> void: + if str(main.get("tool_mode")) != "objects": + main.call("_set_tool_mode", "objects") + if not (main.interaction.placing and main.interaction.place_oid == oid): + main.interaction.start_placement(oid) + _drag_started_placement = true + main.interaction._ghost_update(viewport_pos) + + +## Loslassen über der 3D-Ansicht: genau wie ein Setz-Klick. +func drag_drop(oid: String, viewport_pos: Vector2) -> void: + drag_hover(oid, viewport_pos) + main.interaction._place_object(viewport_pos, false) + if main.interaction.placing and not BauKatalog.ist_bau(oid): + main.interaction.cancel_placement() # daneben gezielt: Vorschau nicht hängen lassen + _drag_started_placement = false + _drag_oid = "" + + +func _notification(what: int) -> void: + if what != NOTIFICATION_DRAG_END or _drag_oid.is_empty(): + return + # Losgelassen ÜBER der Szene → drag_drop hat aufgeräumt. Sonst (abgebrochen, + # anderswo losgelassen) die Vorschau wieder wegnehmen. + if _drag_started_placement and main != null and main.interaction.placing: + main.interaction.cancel_placement() + _drag_started_placement = false + _drag_oid = "" + + +func _select_tile(oid: String) -> void: + var changed := selected_oid != oid + selected_oid = oid + _apply_tile_selection() + _record_recent(oid) + if changed: + object_selected.emit(oid) + + +## Doppelklick-Aktion: Werkzeug-Start 1:1 wie der alte Bibliotheks-Klick +## (interaction.start_placement, Auto-Rückwechsel auf Objekt-Werkzeug). +func _activate_tile(oid: String) -> void: + _select_tile(oid) + if str(main.get("tool_mode")) != "objects": + main.call("_set_tool_mode", "objects") # Bibliothek-Klick wechselt automatisch zurück + if main.interaction.placing and main.interaction.place_oid == oid: + # Nochmal (Doppel-)geklickt → Modus beenden (An/Aus ohne Umwege). + main.interaction.cancel_placement() + main.call("_set_status", "Platzieren abgebrochen.") + return + main.interaction.start_placement(oid) + object_activated.emit(oid) + + +## Auswahll-Marke je Kachel: ausgewählt = voll hell + Rahmen-Akzent. +func _apply_tile_selection() -> void: + for other_oid in library_buttons: + var btn := library_buttons[other_oid] as Button + btn.button_pressed = str(other_oid) == selected_oid + # U7: Auswahl-Markierung nur über Alpha (Farbe bleibt weiß). + btn.modulate.a = 1.0 if str(other_oid) == selected_oid else 0.85 + for recent_oid in _recent_tiles: + var rbtn := _recent_tiles[recent_oid] as Button + rbtn.button_pressed = str(recent_oid) == selected_oid + rbtn.modulate.a = 1.0 if str(recent_oid) == selected_oid else 0.85 + + +## Der Bibliotheks-Button des aktiven Platzier-Objekts leuchtet auf +## (Weitersenden von interaction.gd — nur Anzeige, KEIN Signal). +func highlight_library(oid: String) -> void: + selected_oid = oid + _apply_tile_selection() + if not oid.is_empty(): + _record_recent(oid) + + +# -------------------------------------------------------- Zuletzt benutzt ---- + +func _recent_load() -> Array: + var cfg := ConfigFile.new() + cfg.load("user://scopedvr_editor.cfg") + var raw: Variant = cfg.get_value(LIBRARY_CFG_SECTION, RECENT_CFG_KEY, []) + var lib: Dictionary = main.manifest.get("objects", {}) + var out: Array = [] + for v in (raw as Array): + var oid := str(v) + if lib.has(oid): # nur IDs, die es wirklich gibt + out.append(oid) + return out + + +func _recent_save() -> void: + var cfg := ConfigFile.new() + cfg.load("user://scopedvr_editor.cfg") + cfg.set_value(LIBRARY_CFG_SECTION, RECENT_CFG_KEY, _recent_ids) + cfg.save("user://scopedvr_editor.cfg") + + +## Auswahl/Benutzung merken (neueste zuerst, Ring mit RECENT_MAX Einträgen). +func _record_recent(oid: String) -> void: + if oid.is_empty(): + return + _recent_ids.erase(oid) + _recent_ids.insert(0, oid) + while _recent_ids.size() > RECENT_MAX: + _recent_ids.pop_back() + _recent_save() + _update_recent_chip() + if _cat_active == CAT_ZULETZT: + _fill_recent_box() + + +func _fill_recent_box() -> void: + if _recent_box == null: + return + for c in _recent_box.get_children(): + (c as Node).queue_free() + _recent_tiles.clear() + var head := Label.new() + head.text = "Zuletzt benutzt" + head.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + head.add_theme_font_override("font", _library_header_font()) + _recent_box.add_child(head) + _recent_grid = GridContainer.new() + _recent_grid.columns = 2 + _recent_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _recent_grid.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + _recent_grid.add_theme_constant_override("v_separation", UIScale.px(8)) # U7-Raster + _recent_box.add_child(_recent_grid) + var lib: Dictionary = main.manifest.get("objects", {}) + for oid in _recent_ids: + if not lib.has(str(oid)): + continue + var btn := _build_tile(str(oid)) + _recent_grid.add_child(btn) + _recent_tiles[str(oid)] = btn + if str(oid) == selected_oid: + btn.button_pressed = true + btn.modulate.a = 1.0 diff --git a/ui/panels/assets_panel.gd.uid b/ui/panels/assets_panel.gd.uid new file mode 100644 index 0000000..7153a42 --- /dev/null +++ b/ui/panels/assets_panel.gd.uid @@ -0,0 +1 @@ +uid://dxoh6gjklwjna diff --git a/ui/panels/bauen_panel.gd b/ui/panels/bauen_panel.gd new file mode 100644 index 0000000..91f3a68 --- /dev/null +++ b/ui/panels/bauen_panel.gd @@ -0,0 +1,118 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Bauen-Tab: Bauteile für eigene Gebäude (Fundament, Wände, Dach, Treppe). Fertige +## Pack-Objekte (Container, Hangar-Tore, Sperren …) gehören in die Objekt-Bibliothek +## unten, NICHT hierher. Ein Klick auf ein Teil startet +## das Platzieren (Vorschau rastet an Zelle/Kante/Ebene, siehe build_snap.gd); jedes +## gesetzte Teil ist ein normales Objekt und gehört zum laufenden Gebäude (Gruppe). +## Gebäude kopieren/löschen/auswählen läuft über die Knöpfe unten — alles Undo-fähig. + +var bau_buttons: Dictionary = {} # oid → Button +var info_label: Label +var geschoss_label: Label +var _group: ButtonGroup + + +static func spec() -> Dictionary: + return {"titel": "Bauen", "gruppe": "rechts", "order": 47} + + +func panel_title() -> String: + return "Bauen" + + +func build() -> void: + _group = ButtonGroup.new() + _group.allow_unpress = true + add_child(_muted_label("Wähle ein Teil und klicke in die Karte. Wände rasten an die nächste Zellkante, Böden und Dächer in die Zellmitte (Raster 2 m). Klick auf die Oberkante einer Wand stapelt das nächste Geschoss (oder Bild↑/Bild↓: festes Geschoss, dann sitzen Decken mitten im Raum), Klick auf die Seite ersetzt das Teil (z. B. Wand → Tür). R dreht, Esc beendet.")) + for gruppe in BauKatalog.GRUPPEN: + add_child(_field_label(str(gruppe["label"]))) + add_child(_knopf_raster(gruppe["oids"] as Array)) + add_child(_separator()) + add_child(_header_label("Höhe / Geschoss")) + geschoss_label = _muted_label(main.interaction.call("bau_geschoss_text")) + add_child(geschoss_label) + var hoehe := HBoxContainer.new() + hoehe.add_theme_constant_override("separation", UIScale.px(8)) + for spec in [["▲ Höher (Bild↑)", 1], ["▼ Tiefer (Bild↓)", -1]]: + var hb := _aktion(str(spec[0]), "Festes Geschoss wählen: Böden und Decken sitzen dann überall im Raum auf dieser Höhe (je 3 m). Ganz unten = automatisch.", + func() -> void: + main.interaction.call("bau_geschoss_aendern", int(spec[1])) + geschoss_label.text = main.interaction.call("bau_geschoss_text")) + hb.size_flags_horizontal = Control.SIZE_EXPAND_FILL + hoehe.add_child(hb) + add_child(hoehe) + add_child(_separator()) + add_child(_header_label("Gebäude")) + info_label = _muted_label("") + add_child(info_label) + add_child(_aktion("🏠 Neues Gebäude beginnen", "Das nächste Teil gehört zu einem neuen Gebäude (neue Gruppe).", + func() -> void: main.call("_bau_neues_gebaeude"))) + add_child(_aktion("🔲 Ganzes Gebäude auswählen", "Wählt alle Teile des Gebäudes, zu dem das markierte Teil gehört — zum Verschieben, Drehen, Kopieren.", + func() -> void: main.call("_bau_gebaeude_waehlen"))) + add_child(_aktion("📄 Gebäude kopieren", "Kopiert das ganze Gebäude als neues Gebäude (Strg+Z nimmt es zurück).", + func() -> void: main.call("_bau_gebaeude_kopieren"))) + add_child(_aktion("🗑 Gebäude löschen", "Löscht alle Teile des Gebäudes (Strg+Z holt es zurück).", + func() -> void: main.call("_bau_gebaeude_loeschen"))) + refresh_info() + wrap_in_scroll() + + +func _knopf_raster(oids: Array) -> GridContainer: + var grid := GridContainer.new() + grid.columns = 2 + grid.add_theme_constant_override("h_separation", UIScale.px(8)) + grid.add_theme_constant_override("v_separation", UIScale.px(8)) + var lib: Dictionary = main.manifest.get("objects", {}) + for oid in oids: + var btn := Button.new() + btn.toggle_mode = true + btn.button_group = _group + btn.custom_minimum_size = UIScale.v2(148, 44) + btn.clip_text = true + var poly := int((lib.get(oid, {}) as Dictionary).get("poly", 0)) + btn.text = BauKatalog.label(oid) + btn.tooltip_text = "%s — %s\n%d Dreiecke" % [BauKatalog.label(oid), + str((BauKatalog.KINDS[oid] as Dictionary).get("info", "")), poly] + btn.pressed.connect(_on_teil_gedrueckt.bind(str(oid), btn)) + bau_buttons[str(oid)] = btn + grid.add_child(btn) + return grid + + +func _aktion(text: String, tip: String, cb: Callable) -> Button: + var b := Button.new() + b.text = text + b.tooltip_text = tip + b.custom_minimum_size = UIScale.v2(0, 44) + b.pressed.connect(cb) + return b + + +func _on_teil_gedrueckt(oid: String, btn: Button) -> void: + if not btn.button_pressed: + main.interaction.call("cancel_placement") + return + main.call("_on_bauteil_gewaehlt", oid) + + +## Teil-Markierung an den laufenden Platzier-Zustand koppeln (Esc/Abbruch löst sie). +func sync_platzierung() -> void: + var aktiv := str(main.interaction.get("place_oid")) if bool(main.interaction.get("placing")) else "" + for oid in bau_buttons: + (bau_buttons[oid] as Button).set_pressed_no_signal(oid == aktiv) + + +## Info-Zeile: aktuelles Gebäude (Name, Teile, Dreiecke) bzw. Gesamtzahl. +func refresh_info() -> void: + if info_label == null or main == null or main.doc.is_empty(): + return + var gebaeude := BauOps.gebaeude_gruppen(main.doc) + if geschoss_label != null: + geschoss_label.text = main.interaction.call("bau_geschoss_text") + var text := "%d Gebäude, %d Bauteile in der Map." % [gebaeude.size(), BauOps.teile_zahl(main.doc)] + var gid := int(main.interaction.get("build_group_id")) + for g in gebaeude: + if int(g["gid"]) == gid: + text = "Aktuell: %s — %d Teile, %d Dreiecke.\n%s" % [g["name"], (g["indizes"] as Array).size(), int(g["dreiecke"]), text] + break + info_label.text = text diff --git a/ui/panels/bauen_panel.gd.uid b/ui/panels/bauen_panel.gd.uid new file mode 100644 index 0000000..60f27df --- /dev/null +++ b/ui/panels/bauen_panel.gd.uid @@ -0,0 +1 @@ +uid://ctxc87eisoda6 diff --git a/ui/panels/check_panel.gd b/ui/panels/check_panel.gd new file mode 100644 index 0000000..4450275 --- /dev/null +++ b/ui/panels/check_panel.gd @@ -0,0 +1,176 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Spawn-Check + Spawn-Zonen-Werkzeug-Panel (aus main.gd ausgezogen, Karte +## t_eaccbfed U1 — Logik unverändert, Karte t_c4162c5d): Live-Validierung +## (Loader-Regeln, IMMER sichtbar) + großer Werkzeug-Button + Team/Radius/ +## Löschen. Werkzeug-Ein/Aus bleibt im Koordinator (main.gd). + +var spawn_edit_btn: Button +var spawn_panel: VBoxContainer +var spawn_team_blue: Button +var spawn_team_red: Button +var spawn_radius_slider: HSlider +var spawn_radius_label: Label +var spawn_status: Label # Live-Validierung (IMMER sichtbar, Loader-Regeln) +var spawn_sel_label: Label # Welche Zone ist an? +var spawn_delete_btn: Button +var spawn_yaw_row: HBoxContainer +var spawn_yaw_spin: SpinBox +var spawn_yaw_reset: Button + + +static func spec() -> Dictionary: + return {"titel": "Spawn-Zonen", "gruppe": "map", "order": 10} + + +func panel_title() -> String: + return "Prüfen" + + +## Bau (aus der alten main.gd/_build_right_panel übernommen, Logik identisch): +## Live-Status IMMER sichtbar + Werkzeug-Button + Bedien-Panel (Team/Radius/ +## Auswahl/Löschen). Ein/Aus des Werkzeugs bleibt beim Koordinator (main.gd). +func build() -> void: + add_child(_header_label("Spawn-Zonen")) + # --- Spawn-Zonen (Karte t_c4162c5d): Live-Status IMMER sichtbar --- + spawn_status = Label.new() + spawn_status.text = "" + spawn_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + spawn_status.custom_minimum_size = UIScale.v2(0, 44) + add_child(spawn_status) + + # --- Spawn-Zonen-Werkzeug: großer Button + Bedien-Panel --- + spawn_edit_btn = Button.new() + spawn_edit_btn.text = "🚩 Spawn-Zonen malen" + spawn_edit_btn.custom_minimum_size = UIScale.v2(0, 48) + spawn_edit_btn.toggle_mode = true + spawn_edit_btn.tooltip_text = "Startzonen für Team Blau und Rot auf den Boden malen (TDM)" + spawn_edit_btn.pressed.connect(_on_spawn_btn_pressed) + add_child(spawn_edit_btn) + + spawn_panel = VBoxContainer.new() + spawn_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster + spawn_panel.visible = false + add_child(spawn_panel) + + spawn_panel.add_child(_field_label("Team:")) + var team_row := HBoxContainer.new() + team_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster + spawn_panel.add_child(team_row) + var team_group := ButtonGroup.new() + spawn_team_blue = _spawn_team_button("🔵 Team Blau", team_group, 0) + spawn_team_red = _spawn_team_button("🔴 Team Rot", team_group, 1) + spawn_team_blue.button_pressed = true + team_row.add_child(spawn_team_blue) + team_row.add_child(spawn_team_red) + + spawn_radius_label = _field_label("Zonen-Radius: 6 m") + spawn_panel.add_child(spawn_radius_label) + spawn_radius_slider = HSlider.new() + spawn_radius_slider.min_value = SpawnOps.MIN_ZONE_RADIUS + spawn_radius_slider.max_value = SpawnOps.MAX_ZONE_RADIUS + spawn_radius_slider.step = 1.0 + spawn_radius_slider.value = SpawnOps.DEFAULT_ZONE_RADIUS + spawn_radius_slider.custom_minimum_size = UIScale.v2(0, 32) + spawn_radius_slider.value_changed.connect(_on_spawn_radius_changed) + spawn_panel.add_child(spawn_radius_slider) + + spawn_sel_label = Label.new() + spawn_sel_label.text = "Keine Zone ausgewählt." + spawn_sel_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + spawn_panel.add_child(spawn_sel_label) + spawn_yaw_row = HBoxContainer.new() + spawn_yaw_row.add_theme_constant_override("separation", UIScale.px(8)) + spawn_yaw_row.add_child(_field_label("Blickrichtung °")) + spawn_yaw_spin = SpinBox.new() + spawn_yaw_spin.min_value = 0.0 + spawn_yaw_spin.max_value = 359.0 + spawn_yaw_spin.step = 1.0 + spawn_yaw_spin.tooltip_text = "Wohin Spieler und Bots hier beim Spawnen schauen. Geht auch im Bild: Pfeilspitze ziehen." + spawn_yaw_spin.size_flags_horizontal = Control.SIZE_EXPAND_FILL + spawn_yaw_spin.value_changed.connect(_on_spawn_yaw_changed) + spawn_yaw_row.add_child(spawn_yaw_spin) + spawn_yaw_reset = Button.new() + spawn_yaw_reset.text = "Standard" + spawn_yaw_reset.tooltip_text = "Keine feste Richtung: Spieler schauen zur Kartenmitte, Bots zum Gegner (grauer Pfeil)" + spawn_yaw_reset.pressed.connect(func() -> void: main.spawn_tool.call("set_selected_yaw", null)) + spawn_yaw_row.add_child(spawn_yaw_reset) + spawn_panel.add_child(spawn_yaw_row) + spawn_delete_btn = Button.new() + spawn_delete_btn.text = "Zone löschen (Entf)" + spawn_delete_btn.custom_minimum_size = UIScale.v2(0, 40) + spawn_delete_btn.tooltip_text = "Die ausgewählte Spawn-Zone löschen (Strg+Z holt sie zurück)" + spawn_delete_btn.pressed.connect(_on_spawn_delete_pressed) + spawn_panel.add_child(spawn_delete_btn) + + + +func _spawn_team_button(text: String, group: ButtonGroup, team: int) -> Button: + var btn := Button.new() + btn.text = text + btn.toggle_mode = true + btn.button_group = group + btn.custom_minimum_size = UIScale.v2(0, 40) + # U7: Werkzeugname im Tooltip (Team, in das neue Zonen gemalt werden). + btn.tooltip_text = "Zonen für %s malen" % ("Team Blau" if team == 0 else "Team Rot") + btn.pressed.connect(_on_spawn_team_changed.bind(team)) + return btn + + +func _on_spawn_btn_pressed() -> void: + if spawn_edit_btn.button_pressed: + main.call("_set_tool_mode", "spawns") + else: + main.call("_set_tool_mode", "objects") + + +func _selected_spawn_team() -> int: + return 1 if spawn_team_red.button_pressed else 0 + + +func _on_spawn_team_changed(team: int) -> void: + if main.spawn_tool != null and str(main.get("tool_mode")) == "spawns": + main.spawn_tool.call("set_team", team) + + +func _on_spawn_radius_changed(value: float) -> void: + spawn_radius_label.text = "Zonen-Radius: %d m" % int(value) + if main.spawn_tool != null and str(main.get("tool_mode")) == "spawns": + main.spawn_tool.call("set_radius", value) + + +func _on_spawn_yaw_changed(value: float) -> void: + if str(main.get("tool_mode")) == "spawns": + main.spawn_tool.call("set_selected_yaw", value) + + +func _on_spawn_delete_pressed() -> void: + if str(main.get("tool_mode")) == "spawns": + main.spawn_tool.call("delete_selected") + + +## Panel-Zeile „Welche Zone ist angewählt?“ — WIRD vom Spawn-Tool gerufen. +func sync_spawn_selection() -> void: + if main.spawn_tool == null: + return + var team: int = main.spawn_tool.get("selected_team") + var pid: String = str(main.spawn_tool.get("selected_id")) + if team < 0 or pid == "": + spawn_sel_label.text = "Keine Zone ausgewählt.\nKlicke im Viewport auf einen Kreis." + spawn_delete_btn.disabled = true + spawn_yaw_row.visible = false + return + spawn_yaw_row.visible = true + for p in SpawnOps.points(main.doc, team): + if p is Dictionary and str(p.get("id", "")) == pid: + spawn_yaw_spin.set_value_no_signal(SpawnOps.effective_yaw(main.doc, p)) + spawn_yaw_reset.disabled = not SpawnOps.has_yaw(p) + spawn_sel_label.text = "Ausgewählt: %s (%s)\nVerschieben: klicken+ziehen · Radius: Regler oben" % [ + pid, SpawnOps.team_label(team)] + spawn_delete_btn.disabled = false + + +## LIVE-Validierung (Loader-Regeln) — immer sichtbar, nach JEDEM Struktur-Wechsel. +func refresh_spawn_status() -> void: + if main.doc.is_empty() or spawn_status == null: + return + spawn_status.text = SpawnOps.live_status(main.doc, main.manifest) diff --git a/ui/panels/check_panel.gd.uid b/ui/panels/check_panel.gd.uid new file mode 100644 index 0000000..6e2389a --- /dev/null +++ b/ui/panels/check_panel.gd.uid @@ -0,0 +1 @@ +uid://b84fxjtuhejui diff --git a/ui/panels/editor_panel.gd b/ui/panels/editor_panel.gd new file mode 100644 index 0000000..1f10761 --- /dev/null +++ b/ui/panels/editor_panel.gd @@ -0,0 +1,89 @@ +extends VBoxContainer +class_name EditorPanel + +## Basis-Klasse aller Registry-Panels (Karte t_eaccbfed). Jedes Modul baut +## seinen eigenen Inhalt in setup(main) und wird von main.gd NUR über die +## Registry (core/ui/panels/registry.gd) in den Baum gehängt — main.gd kennt +## keine Panel-Interna mehr. +## +## Datenfluss (bewusst 1:1 wie vorher): Module greifen über `main` auf das +## Dokument/History/Werkzeuge zu und rufen die Koordinator-Helfer über +## main.call(...) — der Aufruf-Vertrag zu den Tools (interaction.gd etc.) +## bleibt unverändert, die Methoden wohnen jetzt nur in den Modulen. + +var main # core/ui/main.gd (script-erweitert → untypisiert) + + +func setup(p_main) -> void: + main = p_main + # U7-Abstands-Raster (4/8 px, über ui_scale skaliert): Panel-Grundabstand. + add_theme_constant_override("separation", UIScale.px(8)) + + +## Titel der Sektions-Überschrift (Registry-Titel als Default). +func panel_title() -> String: + return "Panel" + + +## Lange Tabs (Map, Bauen): den ganzen Inhalt in einen senkrechten Scroll-Bereich +## legen, damit die rechte Spalte nicht mit dem Inhalt wächst. Rückgabe = die +## innere Box (dort hängen spätere Abschnitte an). +func wrap_in_scroll() -> VBoxContainer: + var scroll := ScrollContainer.new() + scroll.name = "%sScroll" % name + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + var inner := VBoxContainer.new() + inner.name = "%sInhalt" % name + inner.size_flags_horizontal = Control.SIZE_EXPAND_FILL + inner.add_theme_constant_override("separation", UIScale.px(8)) + for child in get_children(): + remove_child(child) + inner.add_child(child) + scroll.add_child(inner) + add_child(scroll) + return inner + + +# --- Farb-Zugriff NUR über das Theme (Karte t_2336c58e/U7) ------------------ +## Farben kommen ausschließlich aus theme/editor_theme.tres (Abschnitt +## Editor/colors/…) — keine Farbwerte mehr im Panel-Code. Der Wert liest +## sich vom angeschlossenen Theme-Baum (main hängt theme auf das Fenster). + +func theme_color(cname: String) -> Color: + # Befund 25.10. („Texte kaum lesbar“): Panels werden gebaut, BEVOR sie im + # Baum hängen — get_theme_color liefert dort Schwarz statt der Theme-Farbe. + # Darum direkt aus dem Theme des Hauptfensters lesen. + if main != null and main.theme != null and main.theme.has_color(cname, "Editor"): + return main.theme.get_color(cname, "Editor") + return get_theme_color(cname, "Editor") + + +# --- Gemeinsame Bau-Helfer (aus main.gd übernommen, Logik identisch) -------- + +func _header_label(text: String) -> Label: + var label := Label.new() + label.text = text + label.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + label.add_theme_color_override("font_color", theme_color("accent")) + return label + + +func _field_label(text: String) -> Label: + var label := Label.new() + label.text = text + label.add_theme_color_override("font_color", theme_color("field")) + return label + + +func _muted_label(text: String) -> Label: + var label := Label.new() + label.text = text + label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + label.add_theme_color_override("font_color", theme_color("muted")) + return label + + +func _separator() -> HSeparator: + return HSeparator.new() diff --git a/ui/panels/editor_panel.gd.uid b/ui/panels/editor_panel.gd.uid new file mode 100644 index 0000000..902fabd --- /dev/null +++ b/ui/panels/editor_panel.gd.uid @@ -0,0 +1 @@ +uid://cu0oq4cx0oy60 diff --git a/ui/panels/loadout_panel.gd b/ui/panels/loadout_panel.gd new file mode 100644 index 0000000..cd0de39 --- /dev/null +++ b/ui/panels/loadout_panel.gd @@ -0,0 +1,329 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Loadout-Bereich im Map-Tab (Karte „Loadout pro Map"): pro Map wählbar, ob die +## Spieler ihr eigenes Lobby-Loadout mitbringen oder die Map ein festes Loadout +## (Primär-/Sekundärwaffe + Aufsätze) vorschreibt. Gespeichert als optionaler +## Block doc["loadout"] (Regeln: core/svmap/loadout_katalog.gd, V18). Der Server +## setzt ein festes Loadout durch, der Client zeigt es an. In beiden Modi lässt +## sich zusätzlich der Granaten-/Minen-Vorrat pro Leben festlegen (auch für Bots). +## +## Jede Änderung ist ein Undo-Schritt (History-Block "loadout"; fehlt der Block, +## ist das Spieler-Loadout gemeint). + +const KEINE := "" + +var mode_option: OptionButton +var primary_option: OptionButton +var secondary_option: OptionButton +var fixed_box: VBoxContainer +var attach_box: VBoxContainer +var hint_label: Label +var vorrat_check: CheckBox +var vorrat_box: VBoxContainer +var granaten_spins: Dictionary = {} # Granaten-ID -> SpinBox +var minen_spin: SpinBox + +var _loading := false +var _ids: Array[String] = [] + + +static func spec() -> Dictionary: + return {"titel": "Loadout", "gruppe": "map", "order": 25} + + +func panel_title() -> String: + return "Loadout" + + +func build() -> void: + add_child(_header_label("Loadout")) + add_child(_muted_label("Legt fest, mit welchen Waffen die Spieler diese Karte spielen.")) + + mode_option = OptionButton.new() + mode_option.tooltip_text = "Eigenes Loadout: Spieler nehmen ihre Auswahl aus der Lobby mit. Festes Loadout: alle bekommen genau diese Waffen, der Server setzt das durch." + mode_option.add_item("Eigenes Loadout der Spieler (Lobby)") + mode_option.add_item("Festes Map-Loadout") + add_child(mode_option) + + fixed_box = VBoxContainer.new() + fixed_box.add_theme_constant_override("separation", UIScale.px(8)) + add_child(fixed_box) + + fixed_box.add_child(_field_label("Primärwaffe (Rücken)")) + primary_option = OptionButton.new() + fixed_box.add_child(primary_option) + fixed_box.add_child(_field_label("Sekundärwaffe (Hüfte)")) + secondary_option = OptionButton.new() + fixed_box.add_child(secondary_option) + + _ids = LoadoutKatalog.waffen_ids() + for id in _ids: + primary_option.add_item(LoadoutKatalog.anzeigename(id)) + secondary_option.add_item(LoadoutKatalog.anzeigename(id)) + secondary_option.add_item("— keine —") + + attach_box = VBoxContainer.new() + attach_box.add_theme_constant_override("separation", UIScale.px(6)) + fixed_box.add_child(attach_box) + + hint_label = _muted_label("Messer bleiben unverändert.") + fixed_box.add_child(hint_label) + + add_child(_header_label("Granaten und Minen")) + vorrat_check = CheckBox.new() + vorrat_check.text = "Anzahl für diese Karte festlegen" + vorrat_check.tooltip_text = "Aus: es gilt der Standard des Servers. An: Spieler und Bots bekommen pro Leben genau diese Anzahl." + vorrat_check.toggled.connect(func(an: bool) -> void: + if not _loading: + set_vorrat_aktiv(an)) + add_child(vorrat_check) + vorrat_box = VBoxContainer.new() + vorrat_box.add_theme_constant_override("separation", UIScale.px(6)) + add_child(vorrat_box) + for gid in LoadoutKatalog.GRANATEN_PANEL: + var spin := _anzahl_zeile(str(LoadoutKatalog.GRANATEN[gid]), LoadoutKatalog.MAX_GRANATEN_JE_ART) + spin.value_changed.connect(func(v: float) -> void: + if not _loading: + set_granaten(gid, int(v))) + granaten_spins[gid] = spin + minen_spin = _anzahl_zeile("Minen", LoadoutKatalog.MAX_MINEN) + minen_spin.value_changed.connect(func(v: float) -> void: + if not _loading: + set_minen(int(v))) + + mode_option.item_selected.connect(func(i: int) -> void: set_fixed(i == 1)) + primary_option.item_selected.connect(func(i: int) -> void: + set_weapons(_ids[i], _secondary_id())) + secondary_option.item_selected.connect(func(_i: int) -> void: + set_weapons(_primary_id(), _secondary_id())) + refresh_values() + + +func _anzahl_zeile(titel: String, max_n: int) -> SpinBox: + var row := HBoxContainer.new() + var lab := _field_label(titel) + lab.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(lab) + var spin := SpinBox.new() + spin.min_value = 0 + spin.max_value = max_n + spin.step = 1 + row.add_child(spin) + vorrat_box.add_child(row) + return spin + + +# ------------------------------------------------------------- Lesen ------- + +func _primary_id() -> String: + return _ids[primary_option.selected] if primary_option.selected >= 0 else "" + + +func _secondary_id() -> String: + var i := secondary_option.selected + return _ids[i] if i >= 0 and i < _ids.size() else KEINE + + +func current() -> Dictionary: + return main.doc.get("loadout", {}) if main != null and not main.doc.is_empty() else {} + + +func is_fixed() -> bool: + return str(current().get("mode", LoadoutKatalog.MODE_PLAYER)) == LoadoutKatalog.MODE_FIXED + + +# ------------------------------------------------------------- Ändern ------ + +## Modus wechseln. Auf „fest" ohne vorherige Wahl: M40 + Pistole 1 als Start. +func set_fixed(fixed: bool) -> void: + if main.doc.is_empty(): + return + if not fixed: + _commit(_nur_vorrat(current()), "Loadout: Spieler-Loadout") + return + var cur := current() + if cur.get("mode", "") == LoadoutKatalog.MODE_FIXED: + return + var prim := "m40" if LoadoutKatalog.hat_waffe("m40") else (_ids[0] if not _ids.is_empty() else "") + var sec := "pistol1" if LoadoutKatalog.hat_waffe("pistol1") else KEINE + var neu := {"mode": LoadoutKatalog.MODE_FIXED, "primary": prim, "secondary": sec, "attachments": {}} + var vorrat: Variant = _nur_vorrat(cur) + if vorrat != null: + neu.merge(vorrat as Dictionary, false) + _commit(neu, "Loadout: festes Map-Loadout") + + +## Spieler-Loadout mit Vorrat bleibt als Block erhalten; ohne Vorrat kein Block. +func _nur_vorrat(lo: Dictionary) -> Variant: + if not lo.has("grenades") and not lo.has("mines"): + return null + var out := {"mode": LoadoutKatalog.MODE_PLAYER} + for key in ["grenades", "mines"]: + if lo.has(key): + out[key] = lo[key] + return out + + +func hat_vorrat() -> bool: + return current().has("grenades") or current().has("mines") + + +## Vorrat festlegen (Start = Server-Standard) oder wieder dem Server überlassen. +func set_vorrat_aktiv(an: bool) -> void: + if main.doc.is_empty() or an == hat_vorrat(): + return + var lo := current().duplicate(true) + if an: + if lo.is_empty(): + lo["mode"] = LoadoutKatalog.MODE_PLAYER + lo["grenades"] = {"he1": 1, "blend1": 1, "smoke1": 1} + lo["mines"] = LoadoutKatalog.STANDARD_MINEN + _commit(lo, "Loadout: Granaten und Minen festgelegt") + return + lo.erase("grenades") + lo.erase("mines") + var leer := str(lo.get("mode", LoadoutKatalog.MODE_PLAYER)) == LoadoutKatalog.MODE_PLAYER + _commit(null if leer else lo, "Loadout: Granaten und Minen wie Server") + + +func set_granaten(gid: String, anzahl: int) -> void: + if main.doc.is_empty() or not hat_vorrat() or not LoadoutKatalog.GRANATEN.has(gid): + return + var lo := current().duplicate(true) + var g: Dictionary = lo.get("grenades", {}) + if anzahl > 0: + g[gid] = clampi(anzahl, 0, LoadoutKatalog.MAX_GRANATEN_JE_ART) + else: + g.erase(gid) + lo["grenades"] = g + _commit(lo, "Loadout: %s" % str(LoadoutKatalog.GRANATEN[gid])) + + +func set_minen(anzahl: int) -> void: + if main.doc.is_empty() or not hat_vorrat(): + return + var lo := current().duplicate(true) + lo["mines"] = clampi(anzahl, 0, LoadoutKatalog.MAX_MINEN) + _commit(lo, "Loadout: Minen") + + +## Waffen setzen. Aufsätze von Waffen, die nicht mehr im Loadout sind, fallen weg. +func set_weapons(primary: String, secondary: String) -> void: + if main.doc.is_empty() or not is_fixed(): + return + if not LoadoutKatalog.hat_waffe(primary) or (secondary != KEINE and not LoadoutKatalog.hat_waffe(secondary)): + return + var lo := current().duplicate(true) + lo["primary"] = primary + lo["secondary"] = secondary + var att: Dictionary = lo.get("attachments", {}) + for w in att.keys(): + if w != primary and w != secondary: + att.erase(w) + lo["attachments"] = att + _commit(lo, "Loadout: %s / %s" % [LoadoutKatalog.anzeigename(primary), + LoadoutKatalog.anzeigename(secondary) if secondary != KEINE else "keine"]) + + +## Ein Aufsatz-Slot einer Loadout-Waffe setzen (part = Teil-ID, "" = nichts). +func set_part(weapon: String, slot: String, part: String) -> void: + if main.doc.is_empty() or not is_fixed(): + return + var lo := current().duplicate(true) + if weapon != str(lo.get("primary", "")) and weapon != str(lo.get("secondary", "")): + return + var att: Dictionary = lo.get("attachments", {}) + var sel := LoadoutKatalog.auswahl(weapon, str(att.get(weapon, ""))) + if not sel.has(slot): + return + sel[slot] = part + var wire := LoadoutKatalog.encode(weapon, sel) + if LoadoutKatalog.pruefe_aufsaetze(weapon, wire) != "": + return + if wire.is_empty(): + att.erase(weapon) + else: + att[weapon] = wire + lo["attachments"] = att + _commit(lo, "Loadout: Aufsatz %s" % slot) + + +func _commit(neu: Variant, text: String) -> void: + var alt: Variant = main.doc.get("loadout", null) + if alt != null: + alt = (alt as Dictionary).duplicate(true) + if neu == null: + main.doc.erase("loadout") + else: + main.doc["loadout"] = (neu as Dictionary).duplicate(true) + var danach: Variant = main.doc.get("loadout", null) + if danach != null: + danach = (danach as Dictionary).duplicate(true) + if alt == danach: + return + main.history.commit(text, {"loadout": alt}, {"loadout": danach}) + main.dirty = true + main.call("_refresh_file_label") + main.call("_refresh_history_buttons") + refresh_values() + main.call("_set_status", "%s." % text) + + +# ------------------------------------------------------------ Anzeige ------ + +## UI aus dem doc auffüllen (Laden, Undo/Redo) — löst keinen Schreibzugriff aus. +func refresh_values() -> void: + if mode_option == null or main == null: + return + _loading = true + var lo := current() + var fixed := is_fixed() + mode_option.select(1 if fixed else 0) + fixed_box.visible = fixed + if fixed: + var prim := str(lo.get("primary", "")) + var sec := str(lo.get("secondary", "")) + primary_option.select(maxi(0, _ids.find(prim))) + secondary_option.select(_ids.find(sec) if sec != KEINE and _ids.has(sec) else _ids.size()) + _rebuild_attachments(prim, sec, lo.get("attachments", {})) + var vorrat := hat_vorrat() + vorrat_check.button_pressed = vorrat + vorrat_box.visible = vorrat + for gid in granaten_spins: + (granaten_spins[gid] as SpinBox).value = int((lo.get("grenades", {}) as Dictionary).get(gid, 0)) + minen_spin.value = int(lo.get("mines", LoadoutKatalog.STANDARD_MINEN)) + _loading = false + + +func _rebuild_attachments(prim: String, sec: String, att: Dictionary) -> void: + for c in attach_box.get_children(): + attach_box.remove_child(c) + c.queue_free() + for w in [prim, sec]: + if w == "" or LoadoutKatalog.slots(w).is_empty(): + continue + attach_box.add_child(_header_label("Aufsätze: %s" % LoadoutKatalog.anzeigename(w))) + var sel := LoadoutKatalog.auswahl(w, str(att.get(w, ""))) + var sl := LoadoutKatalog.slots(w) + var slot_names: Array = sl.keys() + slot_names.sort() + for s in slot_names: + var sd: Dictionary = sl[s] + var row := HBoxContainer.new() + var lab := _field_label(str(s).replace("_", " ")) + lab.custom_minimum_size = UIScale.v2(90, 0) + row.add_child(lab) + var ob := OptionButton.new() + ob.name = "Teil_%s_%s" % [w, s] + ob.size_flags_horizontal = Control.SIZE_EXPAND_FILL + var parts: Array = [] + if bool(sd.get("optional", false)): + parts.append("") + parts.append_array(sd.get("options", [])) + for p in parts: + ob.add_item(LoadoutKatalog.teil_name(str(p))) + ob.select(maxi(0, parts.find(sel.get(s, "")))) + ob.item_selected.connect(func(i: int) -> void: + if not _loading: + set_part(w, str(s), str(parts[i]))) + row.add_child(ob) + attach_box.add_child(row) diff --git a/ui/panels/loadout_panel.gd.uid b/ui/panels/loadout_panel.gd.uid new file mode 100644 index 0000000..dfc7fac --- /dev/null +++ b/ui/panels/loadout_panel.gd.uid @@ -0,0 +1 @@ +uid://t00n1cb8be2w diff --git a/ui/panels/malen_panel.gd b/ui/panels/malen_panel.gd new file mode 100644 index 0000000..36772df --- /dev/null +++ b/ui/panels/malen_panel.gd @@ -0,0 +1,170 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Malen-Tab (25.10., UI-Aufräumen): Material-Palette mit Vorschau-Kacheln, +## Pinselgröße und Kante. Malen ist technisch weiter der Pinsel-Modus PAINT des +## Terrain-Werkzeugs (terrain_tool.gd) — die Mode-Buttons und die Pinsel-Logik +## wohnen in terrain_panel.gd, dieses Panel hält nur die Mal-Widgets. main.gd +## reicht die Felder unter den alten Namen (terrain_palette …) weiter. + +const ARRAYS_PATH := "res://assets/textures/terrain/terrain_arrays.res" +const MATERIAL_NAMES: Array[String] = [ + "Steppe", "Sand", "Fels", "Sattgras", "Vertrocknet", "Moos", "Erde", + "Pfad", "Umgegraben", "Schotter", "Lehm", "Sand hell", "Sand rost", + "Basalt", "Geröll", "Schlamm", "Schnee", "Matschschnee", "Asche", "Stoppeln", +] + +var malen_edit_btn: Button +var malen_panel: VBoxContainer +var palette: VBoxContainer +var mat_buttons: Array = [] +var radius_label: Label +var radius_slider: HSlider +var weich_label: Label +var weich_slider: HSlider +var paint_hint: Label +var _syncing := false + + +static func spec() -> Dictionary: + return {"titel": "Malen", "gruppe": "rechts", "order": 45} + + +func panel_title() -> String: + return "Malen" + + +func build() -> void: + malen_edit_btn = Button.new() + malen_edit_btn.text = "🖌 Malen" + malen_edit_btn.custom_minimum_size = UIScale.v2(0, 48) + malen_edit_btn.toggle_mode = true + malen_edit_btn.tooltip_text = "Material auf den Boden malen (Taste 4)" + malen_edit_btn.pressed.connect(_on_malen_btn_pressed) + add_child(malen_edit_btn) + + malen_panel = VBoxContainer.new() + malen_panel.add_theme_constant_override("separation", UIScale.px(8)) + add_child(malen_panel) + + palette = VBoxContainer.new() + palette.add_theme_constant_override("separation", UIScale.px(4)) + palette.visible = false + malen_panel.add_child(palette) + palette.add_child(_field_label("Material:")) + var grid := GridContainer.new() + grid.columns = 3 + grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL + grid.add_theme_constant_override("h_separation", UIScale.px(6)) + grid.add_theme_constant_override("v_separation", UIScale.px(6)) + var scroll := ScrollContainer.new() + scroll.custom_minimum_size = UIScale.v2(0, 340) + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll.add_child(grid) + palette.add_child(scroll) + var previews := _material_previews() + var group := ButtonGroup.new() + for mat_id in SvmapIO.MATERIAL_IDS.size(): + var btn := Button.new() + btn.text = MATERIAL_NAMES[mat_id] if mat_id < MATERIAL_NAMES.size() else str(SvmapIO.MATERIAL_IDS[mat_id]) + btn.custom_minimum_size = UIScale.v2(92, 104) + btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + btn.toggle_mode = true + btn.button_group = group + btn.clip_text = true + btn.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER + btn.vertical_icon_alignment = VERTICAL_ALIGNMENT_TOP + btn.expand_icon = true + btn.add_theme_constant_override("icon_max_width", UIScale.px(76)) + if mat_id < previews.size() and previews[mat_id] != null: + btn.icon = previews[mat_id] + btn.tooltip_text = "Malen: %s" % btn.text + if mat_id == 0: + btn.button_pressed = true + btn.toggled.connect(_on_material_changed.bind(mat_id)) + mat_buttons.append(btn) + grid.add_child(btn) + + radius_label = _field_label("Pinsel-Größe: 12 m") + malen_panel.add_child(radius_label) + radius_slider = HSlider.new() + radius_slider.min_value = 1.0 + radius_slider.max_value = 64.0 + radius_slider.step = 1.0 + radius_slider.value = 12.0 + radius_slider.custom_minimum_size = UIScale.v2(0, 32) + radius_slider.value_changed.connect(_on_radius_changed) + malen_panel.add_child(radius_slider) + + weich_label = _field_label("Kante: 40 % weich") + malen_panel.add_child(weich_label) + weich_slider = HSlider.new() + weich_slider.min_value = 0.0 + weich_slider.max_value = 100.0 + weich_slider.step = 5.0 + weich_slider.value = 40.0 + weich_slider.custom_minimum_size = UIScale.v2(0, 32) + weich_slider.tooltip_text = "Links = harte Kante, rechts = weicher Übergang" + weich_slider.value_changed.connect(_on_weich_changed) + malen_panel.add_child(weich_slider) + + paint_hint = _muted_label("") + malen_panel.add_child(paint_hint) + + +## Vorschaubilder der 20 Materialien aus dem Textur-Array (Albedo-Layer). +## Fehlt das Array oder ein Layer: Kachel bleibt ohne Bild (nie ein Absturz). +func _material_previews() -> Array: + var out: Array = [] + if not ResourceLoader.exists(ARRAYS_PATH): + return out + var arr = load(ARRAYS_PATH) + if arr == null or not ("albedo_images" in arr): + return out + for img in arr.albedo_images: + var tex: Texture2D = null + if img is Image and not (img as Image).is_empty(): + var copy: Image = (img as Image).duplicate() + if copy.is_compressed(): + copy.decompress() + copy.resize(96, 96, Image.INTERPOLATE_BILINEAR) + tex = ImageTexture.create_from_image(copy) + out.append(tex) + return out + + +func _on_malen_btn_pressed() -> void: + var t = main.terrain_mod + if malen_edit_btn.button_pressed: + t.terrain_mode_paint.button_pressed = true + main.call("_set_tool_mode", "terrain") + t.refresh_panels() + else: + main.call("_set_tool_mode", "objects") + + +func _on_material_changed(pressed: bool, mat_id: int) -> void: + if pressed and main.terrain_tool != null: + main.terrain_tool.call("set_paint_material", mat_id) + + +func _on_radius_changed(value: float) -> void: + radius_label.text = "Pinsel-Größe: %d m" % int(value) + if _syncing: + return + var t = main.terrain_mod + if t != null and t.terrain_radius_slider != null: + t.terrain_radius_slider.value = value # → set_radius über dessen Handler + + +## Terrain-Tab → Malen: Radius spiegeln, ohne Rückkopplung. +func set_radius_silent(value: float) -> void: + _syncing = true + radius_slider.value = value + _syncing = false + + +func _on_weich_changed(value: float) -> void: + weich_label.text = "Kante: %d %% weich" % int(value) if value > 0.0 else "Kante: hart" + if main.terrain_tool != null: + main.terrain_tool.call("set_weichheit", value / 100.0) diff --git a/ui/panels/malen_panel.gd.uid b/ui/panels/malen_panel.gd.uid new file mode 100644 index 0000000..12230fd --- /dev/null +++ b/ui/panels/malen_panel.gd.uid @@ -0,0 +1 @@ +uid://1cuxg4r1bmls diff --git a/ui/panels/map_panel.gd b/ui/panels/map_panel.gd new file mode 100644 index 0000000..bce6af5 --- /dev/null +++ b/ui/panels/map_panel.gd @@ -0,0 +1,264 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Map-Eigenschaften (aus main.gd ausgezogen, Karte t_eaccbfed U1 — Logik +## unverändert): Name/Autor/Beschreibung/Version/Wetter + Terrain-Info. +## Eingaben schreiben LIVE ins doc (Live-Bindung wie vorher) und markieren +## dirty — die Signale wurden vorher in _build_right_panel verdrahtet, sie +## hängen jetzt hier. + +var prop_name: LineEdit +var prop_author: LineEdit +var prop_desc: LineEdit +var prop_version: SpinBox +var prop_weather: OptionButton +var prop_sky: OptionButton +var sky_preview: TextureRect # Vorschau der gewählten Skybox (obere Panorama-Hälfte) +var prop_water: HSlider +var terrain_info: Label + +var _sections: VBoxContainer # Inhalt des Map-Tabs (im Scroll-Bereich) +var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder + +## Karte t_28fdef03: Slider-Stellung für „Kein Wasser“ (Meta-Vertrag für +## Tests/Verdrahtung). Slider unterhalb davon (−10 m) = Wasser AUS — das doc- +## Feld wird dann ENTFERNT (Legacy-sauber, kein Wert 0 versehentlich gespeichert). +const KEIN_WASSER_WERT := -10.0 + + +static func spec() -> Dictionary: + return {"titel": "Map", "gruppe": "rechts", "order": 30} + + +func panel_title() -> String: + return "Map-Eigenschaften" + + +## Bau (aus der alten main.gd/_build_right_panel übernommen, Logik identisch): +## Name/Autor/Beschreibung/Version/Wetter + Terrain-Info. Die Signale hängen +## jetzt hier (statt in main.gd) — der doc-Datenfluss ist derselbe. +func build() -> void: + add_child(_field_label("Name")) + prop_name = LineEdit.new() + prop_name.max_length = 64 + add_child(prop_name) + + add_child(_field_label("Autor")) + prop_author = LineEdit.new() + add_child(prop_author) + + add_child(_field_label("Beschreibung")) + prop_desc = LineEdit.new() + add_child(prop_desc) + + add_child(_field_label("Version")) + prop_version = SpinBox.new() + prop_version.min_value = 1 + prop_version.max_value = 99999 + prop_version.step = 1 + add_child(prop_version) + + add_child(_field_label("Wetter")) + prop_weather = OptionButton.new() + # U7: Klartext-Tooltip am Wetter-Auswahlfeld. + prop_weather.tooltip_text = "Licht und Stimmung der Karte (Tag/Abend/Regen/Nacht)" + prop_weather.add_item("Mittag (sonnig, klar)") + prop_weather.add_item("Abend (warmes Licht)") + prop_weather.add_item("Regen (grau, kühl)") + prop_weather.add_item("Nacht (dunkel)") + add_child(prop_weather) + + add_child(_field_label("Himmel")) + prop_sky = OptionButton.new() + # Karte Atmosphäre/Skybox: Panorama-Skybox aus dem Pack als Map-Option. + prop_sky.tooltip_text = "Himmel der Karte: Prozedural (Standard, lässt die Karte unverändert) oder eine Skybox aus dem Katalog. Der Viewport zeigt sie sofort; Strg+Z macht die Wahl rückgängig." + prop_sky.add_item("Prozedural (Standard)") + for e in SkyboxKatalog.EINTRAEGE: + prop_sky.add_item(str(e["name"])) + add_child(prop_sky) + sky_preview = TextureRect.new() + sky_preview.custom_minimum_size = Vector2(0, 80) + sky_preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + sky_preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + sky_preview.tooltip_text = "Vorschau: oberer Teil der gewählten Skybox" + add_child(sky_preview) + + # Wasser (Karte t_28fdef03): Wasserhöhe der Map — Senken unter der Höhe + # füllen sich automatisch. Ganz links = „Kein Wasser“ (Feld entfernen, + # alte Maps unverändert). Die Live-Vorschau nutzt dasselbe Material wie + # der Client (SvmapWasser.make_material — Identitäts-Vertrag). + add_child(_field_label("Wasserhöhe")) + prop_water = HSlider.new() + prop_water.min_value = -10.0 + prop_water.max_value = 100.0 + prop_water.step = 0.25 + prop_water.value = KEIN_WASSER_WERT + prop_water.custom_minimum_size = UIScale.v2(0, 32) + prop_water.tooltip_text = "Wasser: ganz links = kein Wasser, sonst Höhe der Wasseroberfläche über dem Terrain. Senken unter dieser Höhe füllen sich mit Wasser." + prop_water.set_meta("kein_wasser_value", KEIN_WASSER_WERT) + add_child(prop_water) + + add_child(_separator()) + add_child(_header_label("Terrain")) + terrain_info = Label.new() + terrain_info.text = "" + terrain_info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(terrain_info) + + # Signale (Änderungen → Dokument + Ansicht) — wie vorher in main.gd. + prop_name.text_changed.connect(_on_prop_changed.bind("name")) + prop_author.text_changed.connect(_on_prop_changed.bind("author")) + prop_desc.text_changed.connect(_on_prop_changed.bind("description")) + prop_version.value_changed.connect(_on_version_changed) + prop_weather.item_selected.connect(_on_weather_changed) + prop_sky.item_selected.connect(_on_sky_changed) + prop_water.value_changed.connect(_on_water_changed) + _sections = wrap_in_scroll() + + +## Hier hängen die Abschnitte der Registry-Gruppe "map" (Spawn-Zonen, Test-Start, +## Symmetrie) unter den Map-Eigenschaften. +func sections_host() -> Control: + return _sections + + +func _on_prop_changed(text: String, field: String) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + var info: Dictionary = main.doc["info"] + match field: + "name": + info["name"] = text + "author": + info["author"] = text + "description": + info["description"] = text + main.dirty = true + main.call("_refresh_file_label") + + +func _on_version_changed(value: float) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + main.doc["info"]["version"] = int(value) + main.dirty = true + main.call("_refresh_file_label") + + +func _on_weather_changed(index: int) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + var weathers := ["mittag", "abend", "regen", "nacht"] + main.doc["weather"] = weathers[index] + main.dirty = true + main.call("_refresh_file_label") + # Licht/Stimmung im Viewport sofort anpassen + main.view_builder.call("rebuild", main.doc, main.manifest) + main.call("_sync_grid_to_builder") + main.call("_set_status", "Wetter geändert auf „%s“." % weathers[index]) + + +## Skybox-Auswahl: Eintrag → doc["weather_sky"] = Katalog-ID (ein Speicherfeld, +## checksum-neutral wie tint/gebaeude). „Prozedural (Standard)“ = Index 0 → Feld +## ENTFERNEN (alte Maps unverändert). Mit Undo/Redo (History-Block weather_sky). +## Sofort-Neuaufbau wie Wetter — der Viewport zeigt exakt, was der Client zeigt. +func _on_sky_changed(index: int) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + var neu := "" if index <= 0 else str(SkyboxKatalog.EINTRAEGE[index - 1]["id"]) + var alt := str(main.doc.get("weather_sky", "")) + if neu == alt: + return + var before := {"weather_sky": (alt if main.doc.has("weather_sky") else null)} + if neu == "": + main.doc.erase("weather_sky") + else: + main.doc["weather_sky"] = neu + var after := {"weather_sky": (neu if neu != "" else null)} + var name := "Prozedural (Standard)" if neu == "" else str(SkyboxKatalog.eintrag(neu)["name"]) + main.history.commit("Himmel: %s" % name, before, after) + main.dirty = true + main.call("_refresh_file_label") + main.call("_refresh_history_buttons") + main.view_builder.call("rebuild", main.doc, main.manifest) + main.call("_sync_grid_to_builder") + _update_sky_preview(neu) + var hinweis := "" + if neu != "" and SkyboxKatalog.panorama_textur(neu) == null: + hinweis = " Achtung: Textur nicht gefunden (Client-Ordner neben dem Editor fehlt) — Vorschau zeigt den Standard-Himmel." + main.call("_set_status", "Himmel geändert auf „%s“.%s" % [name, hinweis]) + + +func _update_sky_preview(id: String) -> void: + sky_preview.texture = SkyboxKatalog.vorschau(id) if id != "" else null + sky_preview.visible = sky_preview.texture != null + + +## Wasser (Karte t_28fdef03): Slider → doc["water_level"]. Ganz links +## („Kein Wasser“, KEIN_WASSER_WERT) = Feld ENTFERNEN (Legacy-sauber). +## Sofort-Neuaufbau wie Wetter/Himmel — die Vorschau zeigt die Fläche live +## (dasselbe Material wie der Client). +func _on_water_changed(value: float) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + if value <= KEIN_WASSER_WERT + 0.0001: + main.doc.erase("water_level") + main.call("_set_status", "Wasser entfernt (kein Wasser in dieser Karte).") + else: + var terrain: Dictionary = main.doc.get("terrain", {}) + var ok := true + if not terrain.is_empty(): + # Range-Bewacher (V20-Band): sinnlose Werte gar nicht erst + # ins doc schreiben — der Slider klemmt am Höhenbereich. + var hmin := float(terrain.get("min_height_m", -5.0)) + var hmax := float(terrain.get("max_height_m", 500.0)) + var geklemmt := clampf(value, hmin - 10.0, hmax + 10.0) + if geklemmt != value: + ok = false + value = geklemmt + prop_water.set_value_no_signal(geklemmt) + main.call("_set_status", "Wasserhöhe an den Terrain-Höhenbereich geklemmt (%.1f m)." % geklemmt) + if ok or value != KEIN_WASSER_WERT: + main.doc["water_level"] = value + main.dirty = true + main.call("_refresh_file_label") + main.view_builder.call("rebuild", main.doc, main.manifest) + main.call("_sync_grid_to_builder") + + +## Felder aus dem doc auffüllen (aus main.gd _refresh_all-Anfang ausgezogen). +func refresh_values() -> void: + _loading_ui_values = true + var info: Dictionary = main.doc.get("info", {}) + prop_name.text = str(info.get("name", "")) + prop_author.text = str(info.get("author", "")) + prop_desc.text = str(info.get("description", "")) + prop_version.value = float(info.get("version", 1)) + var weathers := ["mittag", "abend", "regen", "nacht"] + var weather := str(main.doc.get("weather", "mittag")) + prop_weather.select(maxi(0, weathers.find(weather))) + # Himmel: Auswahl ↔ doc["weather_sky"] (Katalog-ID; unbekannt/leer → Standard). + var sky_id := str(main.doc.get("weather_sky", "")) + var sky_idx := 0 + for i in SkyboxKatalog.EINTRAEGE.size(): + if SkyboxKatalog.EINTRAEGE[i]["id"] == sky_id: + sky_idx = i + 1 + prop_sky.select(sky_idx) + _update_sky_preview(sky_id if sky_idx > 0 else "") + + var terrain: Dictionary = main.doc.get("terrain", {}) + terrain_info.text = "Größe: %.0f × %.0f m\nAuflösung: %d × %d Höhenpunkte\nHöhenbereich: %.0f bis %.0f m" % [ + float(terrain.get("size_x_m", 0.0)), float(terrain.get("size_z_m", 0.0)), + int(terrain.get("resolution", 0)), + int(terrain.get("resolution", 0)), + float(terrain.get("min_height_m", 0.0)), float(terrain.get("max_height_m", 0.0))] + # Wasser (Karte t_28fdef03): Regler aus dem doc füllen (ohne dirty). + var wl: Variant = main.doc.get("water_level", null) + prop_water.set_value_no_signal( + float(wl) if (wl is float or wl is int) else KEIN_WASSER_WERT) + # Loadout-Bereich (Registry-Gruppe "map") aus dem doc nachziehen. + var modus_panel: Node = _sections.get_node_or_null("modus") if _sections != null else null + if modus_panel != null: + modus_panel.call("refresh_values") + var loadout_panel: Node = _sections.get_node_or_null("loadout") if _sections != null else null + if loadout_panel != null: + loadout_panel.call("refresh_values") + _loading_ui_values = false diff --git a/ui/panels/map_panel.gd.uid b/ui/panels/map_panel.gd.uid new file mode 100644 index 0000000..41e082d --- /dev/null +++ b/ui/panels/map_panel.gd.uid @@ -0,0 +1 @@ +uid://dihu04ppa406d diff --git a/ui/panels/modus_panel.gd b/ui/panels/modus_panel.gd new file mode 100644 index 0000000..886e79c --- /dev/null +++ b/ui/panels/modus_panel.gd @@ -0,0 +1,413 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Spielmodus-Bereich im Map-Tab (28.10.): Modus der Map wählen (Team-Deathmatch / +## Conquest / King of the Hill / Deathmatch), Zonen bzw. Hills setzen und eigene +## Spawns je Modus anlegen. Zonen erscheinen als farbige Kreise im 3D-Bild. +## Gespeichert als optionale Felder doc["game_mode"], doc["conquest_zones"], +## doc["hills"], doc["mode_spawns"] (Regeln: core/svmap/modus_katalog.gd). +## +## Jede Änderung ist ein Undo-Schritt (History-Blöcke; fehlt ein Block, gilt der +## Standard: TDM, keine Zonen, keine eigenen Spawns). + +var mode_option: OptionButton +var cp_box: VBoxContainer # Zonen-/Hill-Bereich (nur Conquest und KotH) +var zones_title: Label +var rows_box: VBoxContainer +var add_button: Button +var hint_label: Label +var spawn_option: OptionButton # Spawn-Satz, der mit dem Spawn-Werkzeug bearbeitet wird +var spawn_create: Button +var spawn_remove: Button +var spawn_hint: Label + +var _loading := false + + +static func spec() -> Dictionary: + return {"titel": "Spielmodus", "gruppe": "map", "order": 22} + + +func panel_title() -> String: + return "Spielmodus" + + +func build() -> void: + add_child(_header_label("Spielmodus")) + add_child(_muted_label("Legt fest, wie auf dieser Karte gewonnen wird.")) + + mode_option = OptionButton.new() + mode_option.name = "ModusAuswahl" + mode_option.tooltip_text = "Team-Deathmatch: Kills zählen. Conquest: Teams nehmen mehrere Zonen ein. King of the Hill: ein Hill, der wandert. Deathmatch: alle gegen alle." + for m in ModusKatalog.MODES: + mode_option.add_item(ModusKatalog.MODE_LABELS[m]) + add_child(mode_option) + + cp_box = VBoxContainer.new() + cp_box.add_theme_constant_override("separation", UIScale.px(8)) + add_child(cp_box) + + zones_title = _field_label("Zonen") + cp_box.add_child(zones_title) + rows_box = VBoxContainer.new() + rows_box.add_theme_constant_override("separation", UIScale.px(6)) + cp_box.add_child(rows_box) + + add_button = Button.new() + add_button.name = "ZoneNeu" + add_button.text = "+ Zone in Bildmitte setzen" + add_button.tooltip_text = "Setzt eine neue Zone dorthin, wo die Kamera hinschaut. Position und Größe lassen sich darunter ändern." + cp_box.add_child(add_button) + + hint_label = _muted_label("Mindestens eine Zone nötig.") + cp_box.add_child(hint_label) + + # Spawns je Modus + add_child(_field_label("Spawns bearbeiten für")) + spawn_option = OptionButton.new() + spawn_option.name = "SpawnSatz" + spawn_option.tooltip_text = "Welche Spawn-Punkte das Spawn-Werkzeug gerade zeigt und ändert. Ohne eigene Spawns nutzt ein Modus die Basis-Spawns." + add_child(spawn_option) + spawn_create = Button.new() + spawn_create.name = "SpawnSatzNeu" + spawn_create.tooltip_text = "Legt eigene Spawn-Punkte für diesen Modus an (Kopie der Basis-Spawns) — danach mit dem Spawn-Werkzeug anpassen." + add_child(spawn_create) + spawn_remove = Button.new() + spawn_remove.name = "SpawnSatzWeg" + spawn_remove.text = "Eigene Spawns löschen" + spawn_remove.tooltip_text = "Entfernt die eigenen Spawns dieses Modus — er nutzt danach wieder die Basis-Spawns." + add_child(spawn_remove) + spawn_hint = _muted_label("") + add_child(spawn_hint) + + mode_option.item_selected.connect(func(i: int) -> void: + if not _loading: + set_mode(ModusKatalog.MODES[i])) + add_button.pressed.connect(func() -> void: add_zone_at_view()) + spawn_option.item_selected.connect(func(i: int) -> void: + if not _loading: + select_spawn_set(_spawn_set_ids()[i])) + spawn_create.pressed.connect(func() -> void: create_spawns(SpawnOps.edit_set)) + spawn_remove.pressed.connect(func() -> void: remove_spawns(SpawnOps.edit_set)) + refresh_values() + + +# ------------------------------------------------------------- Ändern ------ + +func set_mode(mode: String) -> void: + if main.doc.is_empty() or not ModusKatalog.MODES.has(mode) or ModusKatalog.mode_of(main.doc) == mode: + return + var before := _snap() + if mode == ModusKatalog.MODE_TDM: + main.doc.erase("game_mode") + else: + main.doc["game_mode"] = mode + if ModusKatalog.has_zones(mode) and ModusKatalog.zones(main.doc, mode).is_empty(): + main.doc[ModusKatalog.ZONE_KEYS[mode]] = ModusKatalog.default_zones(main.doc, mode) + _commit(before, "Spielmodus: %s" % ModusKatalog.MODE_SHORT[mode]) + + +func _zone_key() -> String: + return str(ModusKatalog.ZONE_KEYS.get(ModusKatalog.mode_of(main.doc), "")) + + +func add_zone_at_view() -> void: + if main.doc.is_empty() or _zone_key() == "": + return + var at := Vector3.ZERO + if main.camera != null and "target" in main.camera: + at = main.camera.target + var half := _half() + add_zone(clampf(at.x, -half.x, half.x), clampf(at.z, -half.y, half.y)) + + +func add_zone(x: float, z: float) -> String: + var mode := ModusKatalog.mode_of(main.doc) + var key := _zone_key() + if key == "": + return "" + var before := _snap() + var zone := ModusKatalog.make_zone(main.doc, mode, x, z) + var list: Array = ModusKatalog.zones(main.doc, mode).duplicate(true) + list.append(zone) + main.doc[key] = list + _commit(before, "%s %s gesetzt" % [ModusKatalog.ZONE_NAMES[mode], zone["id"]]) + return str(zone["id"]) + + +func remove_zone(id: String) -> void: + var mode := ModusKatalog.mode_of(main.doc) + var key := _zone_key() + if key == "": + return + var before := _snap() + var list: Array = [] + for c in ModusKatalog.zones(main.doc, mode): + if str(c.get("id", "")) != id: + list.append((c as Dictionary).duplicate(true)) + if list.size() == ModusKatalog.zones(main.doc, mode).size(): + return + if list.is_empty(): + main.doc.erase(key) + else: + main.doc[key] = list + _commit(before, "%s %s gelöscht" % [ModusKatalog.ZONE_NAMES[mode], id]) + + +## King of the Hill: Reihenfolge der Hills ändern (dir = -1 hoch, +1 runter). +func move_zone(id: String, dir: int) -> void: + var mode := ModusKatalog.mode_of(main.doc) + var key := _zone_key() + if key == "": + return + var list: Array = ModusKatalog.zones(main.doc, mode).duplicate(true) + var idx := -1 + for i in list.size(): + if str(list[i].get("id", "")) == id: + idx = i + var to := idx + dir + if idx < 0 or to < 0 or to >= list.size(): + return + var before := _snap() + var tmp: Variant = list[idx] + list[idx] = list[to] + list[to] = tmp + main.doc[key] = list + _commit(before, "%s %s verschoben (Reihenfolge)" % [ModusKatalog.ZONE_NAMES[mode], id]) + + +## Name / Position / Radius einer Zone ändern (nur übergebene Felder). +func edit_zone(id: String, fields: Dictionary) -> void: + var mode := ModusKatalog.mode_of(main.doc) + var key := _zone_key() + if key == "": + return + var before := _snap() + var list: Array = ModusKatalog.zones(main.doc, mode).duplicate(true) + for c in list: + if str(c.get("id", "")) != id: + continue + if fields.has("label"): + c["label"] = str(fields["label"]).strip_edges().substr(0, 32) + if fields.has("radius_m"): + c["radius_m"] = clampf(float(fields["radius_m"]), ModusKatalog.MIN_RADIUS, ModusKatalog.MAX_RADIUS) + if fields.has("x") or fields.has("z"): + var half := _half() + var p: Array = c["pos"] + var nx := clampf(float(fields.get("x", p[0])), -half.x, half.x) + var nz := clampf(float(fields.get("z", p[2])), -half.y, half.y) + c["pos"] = [nx, SvmapDocument.terrain_height_at(main.doc, nx, nz), nz] + if fields.has(SpawnOps.YAW_KEY): + SpawnOps.write_yaw(c, fields[SpawnOps.YAW_KEY]) + main.doc[key] = list + _commit(before, "%s %s geändert" % [ModusKatalog.ZONE_NAMES[mode], id], "cp_%s" % id if fields.has("x") or fields.has("z") or fields.has("radius_m") or fields.has(SpawnOps.YAW_KEY) else "") + + +# ---- Spawn-Sätze ---- + +func _spawn_set_ids() -> Array: + var out: Array = [ModusKatalog.SPAWN_SET_BASE] + out.append_array(ModusKatalog.MODES) + return out + + +func select_spawn_set(set_id: String) -> void: + if not _spawn_set_ids().has(set_id): + return + SpawnOps.edit_set = set_id + _refresh_spawn_view() + refresh_values() + main.call("_set_status", "Spawn-Satz: %s." % _set_label(set_id)) + + +func create_spawns(mode: String) -> void: + if main.doc.is_empty() or mode == ModusKatalog.SPAWN_SET_BASE or ModusKatalog.has_own_spawns(main.doc, mode): + return + var before := _snap() + ModusKatalog.create_spawn_set(main.doc, mode) + _commit(before, "Eigene Spawns für %s angelegt" % ModusKatalog.MODE_SHORT[mode]) + _refresh_spawn_view() + + +func remove_spawns(mode: String) -> void: + if main.doc.is_empty() or mode == ModusKatalog.SPAWN_SET_BASE or not ModusKatalog.has_own_spawns(main.doc, mode): + return + var before := _snap() + ModusKatalog.remove_spawn_set(main.doc, mode) + SpawnOps.edit_set = ModusKatalog.SPAWN_SET_BASE + _commit(before, "Eigene Spawns für %s gelöscht" % ModusKatalog.MODE_SHORT[mode]) + _refresh_spawn_view() + + +func _refresh_spawn_view() -> void: + if main.view_builder != null: + main.view_builder.call("_rebuild_spawn_markers", main.doc) + if "spawn_tool" in main and main.spawn_tool != null: + main.spawn_tool.call("refresh_from_doc") + + +func _set_label(set_id: String) -> String: + if set_id == ModusKatalog.SPAWN_SET_BASE: + return "Basis (für alle Modi ohne eigene Spawns)" + return "%s%s" % [ModusKatalog.MODE_SHORT[set_id], " ✔ eigene" if ModusKatalog.has_own_spawns(main.doc, set_id) else ""] + + +func _half() -> Vector2: + var t: Dictionary = main.doc.get("terrain", {}) + return Vector2(float(t.get("size_x_m", 1024.0)) * 0.5, float(t.get("size_z_m", 1024.0)) * 0.5) + + +func _snap() -> Dictionary: + var out := {} + for key in ["game_mode", "conquest_zones", "hills", "mode_spawns"]: + var v: Variant = main.doc.get(key, null) + out[key] = v.duplicate(true) if v is Array or v is Dictionary else v + return out + + +func _commit(before: Dictionary, text: String, merge_tag := "") -> void: + var after := _snap() + if before == after: + return + main.history.commit(text, before, after, merge_tag) + main.dirty = true + main.call("_refresh_file_label") + main.call("_refresh_history_buttons") + if main.view_builder != null: + main.view_builder.call("rebuild_zone_markers", main.doc) + refresh_values() + main.call("_set_status", "%s." % text) + + +# ------------------------------------------------------------ Anzeige ------ + +## UI aus dem doc auffüllen (Laden, Undo/Redo) — löst keinen Schreibzugriff aus. +func refresh_values() -> void: + if mode_option == null or main == null or main.doc.is_empty(): + return + _loading = true + var mode := ModusKatalog.mode_of(main.doc) + mode_option.select(ModusKatalog.MODES.find(mode)) + cp_box.visible = ModusKatalog.has_zones(mode) + if cp_box.visible: + var nm: String = ModusKatalog.ZONE_NAMES[mode] + zones_title.text = "Hills (Reihenfolge = Wanderreihenfolge)" if mode == ModusKatalog.MODE_KOTH else "Zonen" + add_button.text = "+ %s in Bildmitte setzen" % nm + hint_label.text = "Mindestens %s nötig. %s" % [ + "einen Hill" if mode == ModusKatalog.MODE_KOTH else "eine Zone", + "Der Server wechselt nach der eingestellten Zeit zum nächsten Hill (oder zufällig)." if mode == ModusKatalog.MODE_KOTH + else "Spieler spawnen zusätzlich an Zonen ihres Teams."] + for c in rows_box.get_children(): + rows_box.remove_child(c) + c.queue_free() + var zl := ModusKatalog.zones(main.doc, mode) + for i in zl.size(): + if zl[i] is Dictionary: + rows_box.add_child(_row(zl[i], i, zl.size(), mode)) + hint_label.visible = zl.is_empty() + # Spawn-Sätze + if not _spawn_set_ids().has(SpawnOps.edit_set): + SpawnOps.edit_set = ModusKatalog.SPAWN_SET_BASE + spawn_option.clear() + for sid in _spawn_set_ids(): + spawn_option.add_item(_set_label(sid)) + spawn_option.select(_spawn_set_ids().find(SpawnOps.edit_set)) + var base := SpawnOps.edit_set == ModusKatalog.SPAWN_SET_BASE + var own := not base and ModusKatalog.has_own_spawns(main.doc, SpawnOps.edit_set) + spawn_create.visible = not base and not own + spawn_create.text = "Eigene Spawns für %s anlegen" % (ModusKatalog.MODE_SHORT[SpawnOps.edit_set] if not base else "") + spawn_remove.visible = own + if base: + spawn_hint.text = "Basis-Spawns gelten für jeden Modus ohne eigene Spawns." + elif own: + spawn_hint.text = "Das Spawn-Werkzeug ändert jetzt die eigenen Spawns dieses Modus." + else: + spawn_hint.text = "%s nutzt noch die Basis-Spawns. Mit dem Knopf legst du eigene an." % ModusKatalog.MODE_SHORT[SpawnOps.edit_set] + if SpawnOps.edit_set == ModusKatalog.MODE_DM and own: + spawn_hint.text += " Deathmatch hat keine Teams — alle Punkte gelten für alle." + _loading = false + + +func _row(cp: Dictionary, index := 0, count := 1, mode := "conquest") -> Control: + var id := str(cp.get("id", "")) + var pos: Array = cp.get("pos", [0, 0, 0]) + var half := _half() + var box := VBoxContainer.new() + box.name = "Z_%s" % id + box.add_theme_constant_override("separation", UIScale.px(4)) + + var top := HBoxContainer.new() + var idl := _field_label("%d·%s" % [index + 1, id] if mode == ModusKatalog.MODE_KOTH else id) + idl.custom_minimum_size = UIScale.v2(32, 0) + top.add_child(idl) + var name_edit := LineEdit.new() + name_edit.name = "Name" + name_edit.text = str(cp.get("label", id)) + name_edit.max_length = 32 + name_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL + name_edit.text_submitted.connect(func(t: String) -> void: + if not _loading: + edit_zone(id, {"label": t})) + name_edit.focus_exited.connect(func() -> void: + if not _loading and name_edit.text != str(cp.get("label", id)): + edit_zone(id, {"label": name_edit.text})) + top.add_child(name_edit) + if mode == ModusKatalog.MODE_KOTH: + var up := Button.new() + up.name = "Hoch" + up.text = "▲" + up.tooltip_text = "Früher drankommen" + up.disabled = index == 0 + up.pressed.connect(func() -> void: move_zone(id, -1)) + top.add_child(up) + var dn := Button.new() + dn.name = "Runter" + dn.text = "▼" + dn.tooltip_text = "Später drankommen" + dn.disabled = index >= count - 1 + dn.pressed.connect(func() -> void: move_zone(id, 1)) + top.add_child(dn) + var del := Button.new() + del.name = "Loeschen" + del.text = "✕" + del.tooltip_text = "Löschen" + del.pressed.connect(func() -> void: remove_zone(id)) + top.add_child(del) + box.add_child(top) + + var nums := HBoxContainer.new() + nums.add_theme_constant_override("separation", UIScale.px(4)) + nums.add_child(_spin("X", "x", id, float(pos[0]), -half.x, half.x, 1.0)) + nums.add_child(_spin("Z", "z", id, float(pos[2]), -half.y, half.y, 1.0)) + nums.add_child(_spin("Radius", "radius_m", id, float(cp.get("radius_m", ModusKatalog.DEFAULT_RADIUS)), + ModusKatalog.MIN_RADIUS, ModusKatalog.MAX_RADIUS, 1.0)) + box.add_child(nums) + if mode == ModusKatalog.MODE_CONQUEST: + var facing := HBoxContainer.new() + facing.add_theme_constant_override("separation", UIScale.px(4)) + facing.add_child(_spin("Blickrichtung °", SpawnOps.YAW_KEY, id, SpawnOps.effective_yaw(main.doc, cp), 0.0, 359.0, 1.0)) + var reset := Button.new() + reset.name = "BlickStandard" + reset.text = "Standard" + reset.tooltip_text = "Keine feste Richtung: Spieler schauen zur Kartenmitte, Bots zum Gegner. Im Spawn-Werkzeug geht's auch per Pfeilspitze." + reset.disabled = not SpawnOps.has_yaw(cp) + reset.pressed.connect(func() -> void: edit_zone(id, {SpawnOps.YAW_KEY: null})) + facing.add_child(reset) + box.add_child(facing) + return box + + +func _spin(label: String, field: String, id: String, value: float, mn: float, mx: float, step: float) -> Control: + var h := HBoxContainer.new() + h.size_flags_horizontal = Control.SIZE_EXPAND_FILL + h.add_child(_field_label(label)) + var sb := SpinBox.new() + sb.name = field + sb.min_value = mn + sb.max_value = mx + sb.step = step + sb.set_value_no_signal(value) + sb.size_flags_horizontal = Control.SIZE_EXPAND_FILL + sb.value_changed.connect(func(v: float) -> void: + if not _loading: + edit_zone(id, {field: v})) + h.add_child(sb) + return h diff --git a/ui/panels/modus_panel.gd.uid b/ui/panels/modus_panel.gd.uid new file mode 100644 index 0000000..ad8aa37 --- /dev/null +++ b/ui/panels/modus_panel.gd.uid @@ -0,0 +1 @@ +uid://dd6aphlxfvcha diff --git a/ui/panels/registry.gd b/ui/panels/registry.gd new file mode 100644 index 0000000..dce3e73 --- /dev/null +++ b/ui/panels/registry.gd @@ -0,0 +1,109 @@ +class_name PanelRegistry +extends RefCounted + +## Zentrale, DATENGETRIEBENE Anmeldung aller Editor-Bereiche (Karte t_eaccbfed, +## Operator-Vorgabe 20.10.). main.gd baut das Layout AUSSCHLIESSLICH aus dieser +## Registry — keine Panel-Erzeugung mehr fest verdrahtet. +## +## Warum: Die Umbau-Karten U2 (Szenen-Baum), U3 (Gruppen), U4 (Assets), +## U5 (Platzieren nur einmal), U6 (dezente Auswahl) und U7 (Feinschliff) ändern +## danach NUR ihr eigenes Modul und melden sich HIER an (einzeiliger Eintrag) +## — ohne main.gd anzufassen. Dadurch kollidieren parallele Karten nicht mehr +## in derselben Datei (gleiche Falle wie früher bei match_hud.gd). +## +## Struktur je Eintrag: titel (Tab-/Überschrift), gruppe ("rechts" = rechtes +## TabControl, "links" = linke Spalte, "unten" = Assets-Bereich, "map" = Abschnitte im Map-Tab, "dialog" = Einstellungen-Fenster, "toolbar" / +## "status" = Leisten-Aktionen), order (Sortier-Schlüssel), skript (preload-Pfad +## des Panel-Moduls). Ein Bereich erscheint GENAU EINMAL im Baum (test_ui_layout +## prüft das gegen Doppel-Namen). + +const TITLE_TOOLBAR := "Werkzeuge" +const TITLE_STATUS := "Statuszeile" + +## Die Bereiche in fester Reihenfolge. skript = preload-Pfad; jeder +## Panel-Eintrag muss in seinem Skript `static func spec()` haben, das +## mindestens {"titel", "gruppe", "order"} liefert (die Werte hier sind die +## Authorität — spec() wird von test_ui_layout GEGEN diese Tabelle geprüft). +const PANELS: Array = [ + # --- rechts: Tab-Gruppe (Unity-Inspector-Spalte) --- + {"id": "inspektor", "titel": "Inspektor", "gruppe": "rechts", "order": 10, + "skript": "res://core/ui/inspector_panel.gd"}, + {"id": "map", "titel": "Map", "gruppe": "rechts", "order": 30, + "skript": "res://core/ui/panels/map_panel.gd"}, + {"id": "terrain", "titel": "Terrain", "gruppe": "rechts", "order": 40, + "skript": "res://core/ui/panels/terrain_panel.gd"}, + # 25.10. UI-Aufräumen: Malen und Bauen hängen nicht mehr im Terrain-Tab. + {"id": "malen", "titel": "Malen", "gruppe": "rechts", "order": 45, + "skript": "res://core/ui/panels/malen_panel.gd"}, + {"id": "bauen", "titel": "Bauen", "gruppe": "rechts", "order": 47, + "skript": "res://core/ui/panels/bauen_panel.gd"}, + # Etappe 3: Server-Verwaltung (Admin-API des Match-Servers). + {"id": "server", "titel": "Server", "gruppe": "rechts", "order": 50, + "skript": "res://core/ui/panels/server_panel.gd"}, + # --- map: Abschnitte UNTER den Map-Eigenschaften (Tab „Map“) --- + # 25.10. (Test-Befund): Prüfen/Test-Start/Symmetrie sind keine eigenen Tabs + # mehr. Spawn-Zonen, Test-Start-Info und Symmetrie wohnen im Map-Tab. + {"id": "check", "titel": "Spawn-Zonen", "gruppe": "map", "order": 10, + "skript": "res://core/ui/panels/check_panel.gd"}, + {"id": "teststart", "titel": "Test-Start", "gruppe": "map", "order": 20, + "skript": "res://core/ui/panels/test_start_panel.gd"}, + {"id": "loadout", "titel": "Loadout", "gruppe": "map", "order": 25, + "skript": "res://core/ui/panels/loadout_panel.gd"}, + {"id": "modus", "titel": "Spielmodus", "gruppe": "map", "order": 22, + "skript": "res://core/ui/panels/modus_panel.gd"}, + {"id": "symmetrie", "titel": "Symmetrie", "gruppe": "map", "order": 30, + "skript": "res://core/ui/panels/symmetry_panel.gd"}, + # --- dialog: Inhalt des Einstellungen-Fensters (Knopf in der Kopfleiste) --- + # T3 Komfort (Karte t_aaff5dcc) + 25.10.: Gitter, Höhenlinien, Symmetrie- + # Modus, UI-Größe, Raster-Snap, Gizmo-Schritt, Karte prüfen. + {"id": "einstellungen", "titel": "Einstellungen", "gruppe": "dialog", "order": 10, + "skript": "res://core/ui/panels/settings_panel.gd"}, + # --- links: Vorbereitet für U2 (Szenen-Baum); Bibliothek ist UNTEN --- + {"id": "szene", "titel": "Map-Inhalt", "gruppe": "links", "order": 10, + "skript": "res://core/ui/panels/scene_tree_panel.gd"}, + # --- unten: Objekt-Bibliothek / Assets (Unity-Projekt-Fenster-Platz) --- + {"id": "assets", "titel": "Objekt-Bibliothek", "gruppe": "unten", "order": 10, + "skript": "res://core/ui/panels/assets_panel.gd"}, +] + +## Leisten-Aktionen (Toolbar-Buttons/Menüs — KEINE Panels, melden sich ditto +## datengetrieben an, damit T1s Lauf-Modus und künftige Aktionen main.gd +## nicht mehr anfassen). action = Methodenname an main (Callable wird gebunden). +const ACTIONS: Array = [ + {"id": "karte_pruefen", "titel": "✅ Karte prüfen", "gruppe": "toolbar", + "order": 10, "action": "_on_check_map"}, + {"id": "karte_testen", "titel": "▶ Karte testen", "gruppe": "toolbar", + "order": 20, "action": "_on_test_run"}, + # Wiederanbindung 20.10. (T1b, Karte t_96e3c4ee): der Test-Start-Knopf war + # beim U1-Umbau aus der Leiste verschwunden. + {"id": "teststart", "titel": "🎯 Test-Start", "gruppe": "toolbar", + "order": 30, "action": "_on_test_start_tool_pressed"}, +] + + +## Panel-Einträge einer Gruppe, nach order sortiert (Reihenfolge = Tab-Reihenfolge). +static func entries(gruppe: String) -> Array: + var out: Array = [] + for e in PANELS: + if str(e["gruppe"]) == gruppe: + out.append(e) + out.sort_custom(func(a, b) -> bool: return int(a["order"]) < int(b["order"])) + return out + + +## Toolbar-Aktionen, nach order sortiert. +static func toolbar_actions() -> Array: + var out: Array = [] + for e in ACTIONS: + if str(e["gruppe"]) == "toolbar": + out.append(e) + out.sort_custom(func(a, b) -> bool: return int(a["order"]) < int(b["order"])) + return out + + +## Eintrag per ID (für Tests/Probes). +static func entry(id: String) -> Dictionary: + for e in PANELS: + if str(e["id"]) == id: + return e + return {} diff --git a/ui/panels/registry.gd.uid b/ui/panels/registry.gd.uid new file mode 100644 index 0000000..78557b8 --- /dev/null +++ b/ui/panels/registry.gd.uid @@ -0,0 +1 @@ +uid://cmom62lxsqgef diff --git a/ui/panels/scene_tree_panel.gd b/ui/panels/scene_tree_panel.gd new file mode 100644 index 0000000..dccc376 --- /dev/null +++ b/ui/panels/scene_tree_panel.gd @@ -0,0 +1,590 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Map-Inhalt-Baum (aus main.gd ausgezogen, Karte t_eaccbfed U1 — Logik +## unverändert). U2 baut hier den hierarchischen Szenen-Baum aus; diese Karte +## zieht nur um: Der Baum lebt jetzt im LINKEN TabContainer (Registry-Gruppe +## "links"), Zustand/Signale wie vorher. + +var content_tree: Tree +var group_btn: Button +var ungroup_btn: Button +var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum +var _tree_select_pending := false # multi_selected gesammelt, Auswertung steht aus +var _obj_index_map: Dictionary = {} # obj_index -> TreeItem (schnelle Zwei-Wege-Kopplung) + +# U3 (Karte t_a4bf9b8c): Kontextmenü, Umbenennen, Lösch-Rückfrage, Drag&Drop. +var _context_menu: PopupMenu +var _rename_dialog: AcceptDialog +var _rename_field: LineEdit +var _rename_gid := -1 # Gruppe, die das Umbenennen-Dialogfeld betrifft +var _confirm_delete: ConfirmationDialog +var _confirm_gid := -1 # Gruppe, die das Lösch-Rückfragen betrifft + + +static func spec() -> Dictionary: + return {"titel": "Map-Inhalt", "gruppe": "links", "order": 10} + + +func build() -> void: + add_child(_header_label("Map-Inhalt")) + + content_tree = Tree.new() + content_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL + content_tree.custom_minimum_size = UIScale.v2(0, 160) + content_tree.allow_reselect = true + content_tree.select_mode = Tree.SELECT_MULTI # Strg+Klick im Baum = Mehrfachauswahl (t_c46d31c2) + content_tree.hide_root = true + # FIX 25.10. (Test-Befund „Liste anklicken wählt in der Szene nichts aus“): + # Im Modus SELECT_MULTI feuert Godot NIE item_selected, sondern nur + # multi_selected (pro Zeile). Beide anschließen; das Sammeln übernimmt der + # Aufrufer-Merker, damit ein Klick mit mehreren Signalen nur EINMAL wirkt. + content_tree.item_selected.connect(_on_tree_selected) + content_tree.multi_selected.connect(_on_tree_multi_selected) + add_child(content_tree) + + # Gruppen verwalten (25.10.: der Tab „Auswahl“ ist weg): mehrere Objekte in der + # Liste (Strg+Klick) oder in der Szene markieren, dann hier gruppieren/lösen. + # Rechtsklick auf eine Zeile bietet dieselben Aktionen. + var group_row := HBoxContainer.new() + group_row.add_theme_constant_override("separation", UIScale.px(8)) + add_child(group_row) + group_btn = Button.new() + group_btn.text = "🔗 Gruppieren" + group_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + group_btn.custom_minimum_size = UIScale.v2(0, 40) + group_btn.tooltip_text = "Markierte Objekte zu einer Gruppe verbinden (Strg+G) — sie ziehen und drehen dann gemeinsam. Mehrere markieren: Strg+Klick in der Liste oder Rahmen in der Szene." + group_btn.pressed.connect(func() -> void: main.interaction.group_selection()) + group_row.add_child(group_btn) + ungroup_btn = Button.new() + ungroup_btn.text = "⛓ Gruppe lösen" + ungroup_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL + ungroup_btn.custom_minimum_size = UIScale.v2(0, 40) + ungroup_btn.tooltip_text = "Die Gruppe der Auswahl auflösen (Strg+Umschalt+G) — die Objekte bleiben, nur die Bindung löst sich" + ungroup_btn.pressed.connect(func() -> void: main.interaction.ungroup_selection()) + group_row.add_child(ungroup_btn) + + # U3 (Karte t_a4bf9b8c): Rechtsklick-Kontextmenü + Drag&Drop für Gruppen. + content_tree.gui_input.connect(_on_tree_gui_input) + # Tree.set_drag_forwarding: die drei Virtuals (_get_drag_data/_can_drop_data/ + # _drop_data) wohnen am Tree selbst — hier per Callables weitergeleitet. + content_tree.set_drag_forwarding(_tree_drag_data, _tree_can_drop, _tree_drop) + _context_menu = PopupMenu.new() + _context_menu.hide_on_item_selection = true + _context_menu.id_pressed.connect(_on_menu_action) + add_child(_context_menu) + _rename_dialog = AcceptDialog.new() + _rename_dialog.title = "Gruppe umbenennen" + _rename_dialog.ok_button_text = "Übernehmen" + # Hinweis: AcceptDialog kennt in Godot 4.7 weder cancel_button_text noch + # get_cancel_button() — der Default-Abbrechen-Button bleibt, wie er ist. + _rename_field = LineEdit.new() + _rename_field.placeholder_text = "Gruppenname" + _rename_dialog.add_child(_rename_field) + _rename_dialog.confirmed.connect(_on_rename_confirmed) + add_child(_rename_dialog) + _confirm_delete = ConfirmationDialog.new() + _confirm_delete.title = "Gruppe löschen" + _confirm_delete.ok_button_text = "Löschen" + _confirm_delete.cancel_button_text = "Abbrechen" + _confirm_delete.confirmed.connect(_on_delete_confirmed) + add_child(_confirm_delete) + add_child(_separator()) + + +## multi_selected kommt je Zeile (auch beim Abwählen): die Auswahl EINMAL pro +## Frame auswerten, nicht pro Signal. +func _on_tree_multi_selected(_item: TreeItem, _column: int, _selected: bool) -> void: + if _tree_select_lock or _tree_select_pending: + return + _tree_select_pending = true + _apply_tree_selection.call_deferred() + + +func _apply_tree_selection() -> void: + _tree_select_pending = false + _on_tree_selected() + + +## Der Baum ruft zurück, wenn der Nutzer dort ein Objekt auswählt. +## SELECT_MULTI (Karte t_c46d31c2): einfacher Klick = Ersatz-Auswahl, +## Strg+Klick addiert/entfernt nativ — ALLE markierten Objekt-Zeilen spiegeln +## sich in die Viewport-Auswahl (Baum und Szene sind immer synchron). +func _on_tree_selected() -> void: + if _tree_select_lock: + return + var item := content_tree.get_selected() + if item == null: + return + # Gruppe geklickt (Karte t_e3c0310a U2): alle Mitglieder auswählen. + if item.has_meta("group_id"): + var g_indices: Array = [] + var walk_g := item.get_first_child() + while walk_g != null: + if walk_g.has_meta("obj_index"): + g_indices.append(int(walk_g.get_meta("obj_index"))) + walk_g = walk_g.get_next() + if not g_indices.is_empty(): + _tree_select_lock = true + main.interaction.select_many(g_indices) + _tree_select_lock = false + main.call("_set_status", "Gruppe %d ausgewählt (%d Objekte) — gemeinsam verschieben/drehen/duplizieren." % [int(item.get_meta("group_id")), g_indices.size()]) + return + var indices: Array = [] + var walk := content_tree.get_next_selected(null) + while walk != null: + if walk.has_meta("obj_index"): + indices.append(int(walk.get_meta("obj_index"))) + walk = content_tree.get_next_selected(walk) + if indices.is_empty(): + return + var objects: Array = main.doc.get("objects", []) + var valid: Array = [] + for idx in indices: + if idx >= 0 and idx < objects.size(): + valid.append(idx) + if valid.is_empty(): + return + if valid.size() == 1: + main.interaction.select_object(valid[0], true) + main.call("_set_status", "%s ausgewählt — ziehen zum Verschieben, R/Mausrad zum Drehen, Entf zum Löschen. Strg+Klick markiert weitere dazu." % main.object_label(str(objects[valid[0]].get("id", "")))) + else: + main.interaction.select_many(valid) + main.call("_set_status", "%d Objekte im Baum markiert — Entf löscht ALLE, Strg+D dupliziert ALLE (jeweils EIN Undo-Schritt)." % valid.size()) + + +func mark_tree_selection(idx: int) -> void: + mark_tree_selection_multi([idx]) + + +func mark_tree_selection_multi(indices: Array) -> void: + _tree_select_lock = true + var wanted := {} + for i in indices: + wanted[int(i)] = true + # Zwei-Wege-Kopplung (Karte t_e3c0310a U2): die Auswahl in 3D spiegelt + # sich im Baum — über die Index-Karte O(1) statt Voll-Suche. + for idx in wanted.keys(): + if _obj_index_map.has(idx): + var item: TreeItem = _obj_index_map[idx] + item.select(0) + # Abwählen: alle übrigen markierten Objekt-Zeilen deselektieren. + var walk := content_tree.get_next_selected(null) + while walk != null: + if walk.has_meta("obj_index") and not wanted.has(int(walk.get_meta("obj_index"))): + walk.deselect(0) + walk = content_tree.get_next_selected(walk) + var primary: Variant = main.interaction.get("selected_index") if main.interaction != null else -1 + if primary is int and primary >= 0 and _obj_index_map.has(primary): + _ensure_tree_visible(content_tree.get_root(), primary) + _tree_select_lock = false + + +func _ensure_tree_visible(item: TreeItem, idx: int) -> void: + if item == null: + return + if item.has_meta("obj_index") and int(item.get_meta("obj_index")) == idx: + # Fix (beim Inspektor-Einbau gefunden, Vorbestand t_f6f29d86): Tree + # und TreeItem haben KEIN ensure_visible — die Godot-4-API heißt + # Tree.scroll_to_item. Der alte Ruf warf bei JEDER Auswahl einen + # SCRIPT ERROR und scrollte den Baum nie zur Auswahl. + content_tree.scroll_to_item(item) + return + for child in item.get_children(): + _ensure_tree_visible(child, idx) + + +func refresh_tree() -> void: + content_tree.clear() + _obj_index_map.clear() + var root := content_tree.create_item() + var objects: Array = main.doc.get("objects", []) + var zones: Dictionary = main.doc.get("spawn_zones", {}) + var group_ids: Array = GroupOps.active_group_ids(main.doc) + + # Leerer Zustand (Karte t_e3c0310a U2): Klartext-Hinweis statt leere Gruppen. + if objects.is_empty() and zones.is_empty() and group_ids.is_empty(): + var empty_item := content_tree.create_item(root) + empty_item.set_text(0, "Noch keine Objekte — links in der Objekt-Bibliothek wählen") + empty_item.set_selectable(0, false) + return + + # Objekte: Kategorie → Objekte (Karte t_e3c0310a U2 — z. B. Mauern, Barrieren). + var obj_item := content_tree.create_item(root) + obj_item.set_text(0, "Objekte (%d)" % objects.size()) + obj_item.set_meta("objekte_kopf", true) # Drag-Ziel „aus Gruppe herauslösen“ (U3) + var kat_map: Dictionary = {} + for i in objects.size(): + var obj = objects[i] + if obj is Dictionary: + var oid := str(obj.get("id", "?")) + var kat := LibraryBrowser.group_of(oid) + if not kat_map.has(kat): + kat_map[kat] = [] + kat_map[kat].append({"idx": i, "obj": obj}) + for kat in kat_map.keys(): + var kat_item := content_tree.create_item(obj_item) + kat_item.set_text(0, "%s (%d)" % [_kat_label(kat), (kat_map[kat] as Array).size()]) + for data in kat_map[kat]: + var idx: int = int(data["idx"]) + var obj: Dictionary = data["obj"] + var is_visible: bool = bool(obj.get("visible", true)) + var item := content_tree.create_item(kat_item) + item.set_text(0, ("👁 " if is_visible else "🚫 ") + "%s #%d" % [main.object_label(str(obj.get("id", "?"))), idx + 1]) + item.set_meta("obj_index", idx) + item.set_meta("visible", is_visible) + _obj_index_map[idx] = item + + # Spawn-Zonen: Team A/B (Karte t_e3c0310a U2 — aus der alten Struktur). + if not zones.is_empty(): + var spawn_item := content_tree.create_item(root) + spawn_item.set_text(0, "Spawn-Zonen") + for team_key in ["0", "1"]: + if not zones.has(team_key): + continue + var zone: Dictionary = zones[team_key] + var team_item := content_tree.create_item(spawn_item) + var team_name := "Team Blau" if team_key == "0" else "Team Rot" + team_item.set_text(0, "%s (%d Punkte)" % [team_name, (zone.get("points", []) as Array).size()]) + for p in (zone.get("points", []) as Array): + if p is Dictionary: + var point_item := content_tree.create_item(team_item) + point_item.set_text(0, "%s (%.0f, %.0f)" % [ + str(p.get("id", "?")), float(p.get("pos", [0, 0, 0])[0]), float(p.get("pos", [0, 0, 0])[2])]) + + # Gruppen (Karte t_e3c0310a U2): aktive Gruppen als eigene Ebene im Baum. + # Klick auf eine Gruppe wählt ALLE Mitglieder in der 3D-Szene (Zwei-Wege). + if not group_ids.is_empty(): + var grp_item := content_tree.create_item(root) + grp_item.set_text(0, "Gruppen (%d)" % group_ids.size()) + grp_item.set_meta("objekte_kopf", true) # Drag-Ziel „herauslösen“ (U3) + for gid in group_ids: + var member_indices: Array = GroupOps.members_of(main.doc, gid) + # U3 (Karte t_a4bf9b8c): gespeicherter Name voran, Fallback "Gruppe N". + var g_node := content_tree.create_item(grp_item) + g_node.set_text(0, "%s (%d Objekte)" % [GroupState.display_name(main.doc, int(gid)), member_indices.size()]) + g_node.set_meta("group_id", int(gid)) + for mi in member_indices: + var obj: Dictionary = main.doc["objects"][int(mi)] + var m_item := content_tree.create_item(g_node) + m_item.set_text(0, " %s" % main.object_label(str(obj.get("id", "?")))) + m_item.set_meta("obj_index", int(mi)) + + +## Kategorie-Schlüssel (z. B. "mauern") in Anzeige-Label (z. B. "Mauern") — +## Karte t_e3c0310a U2. Nutzt KAT_GROUPS aus LibraryBrowser. +static func _kat_label(key: String) -> String: + for kat_name in LibraryBrowser.KAT_GROUPS: + var g: Dictionary = LibraryBrowser.KAT_GROUPS[kat_name] + if str(g["key"]) == key: + return str(g["label"]) + return key.capitalize() + + +# ================= U3: Gruppen im Baum (Karte t_a4bf9b8c) ==================== +# Gruppen direkt im Szenen-Baum bilden: Rechtsklick-Menü (Gruppieren/Auflösen/ +# Umbenennen/Löschen), Drag&Drop für die Mitgliedschaft, Gruppennamen im +# Dokument (GroupState, additiv). Jede Aktion = EIN Undo-Schritt über +# main.history — die Mehrfachauswahl-Logik (interaction.gd) wird benutzt, +# nicht kopiert; object_ops.gd nur LESEND. + + +## Rechtsklick im Baum: Zeile unter der Maus finden → Kontextmenü aufmachen. +func _on_tree_gui_input(event: InputEvent) -> void: + if not (event is InputEventMouseButton): + return + var mb := event as InputEventMouseButton + if not mb.pressed or mb.button_index != MOUSE_BUTTON_RIGHT: + return + var item: TreeItem = content_tree.get_item_at_position(mb.position) + if item == null: + return + # Rechtsklick auf eine (noch nicht markierte) Zeile wählt sie an — wie in + # Unity/Godot: das Menü gehört zur angeklickten Zeile. Direkter Ruf von + # _on_tree_selected (item_selected feuert bei .select() nicht selbst). + if item.has_meta("obj_index") and not item.is_selected(0): + content_tree.deselect_all() + item.select(0) + _on_tree_selected() + _open_context_menu(item, Vector2i(content_tree.get_screen_transform() * mb.position)) + + +## Kontextmenü je Zeilen-Typ aufbauen (Klartext-Einträge, je Aktion eine ID). +func _open_context_menu(item: TreeItem, at_screen: Vector2i) -> void: + _context_menu.clear() + var has_entries := false + # Gruppen-Knoten: Auflösen / Umbenennen / Löschen (Rückfrage bei > 1 Objekt). + if item.has_meta("group_id"): + var gid := int(item.get_meta("group_id")) + _context_menu.add_item("⛓ Gruppe auflösen", 1) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "loesen_gruppe", "gid": gid}) + _context_menu.add_item("✂ Umbenennen…", 2) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "umbenennen", "gid": gid}) + _context_menu.add_item("🗑 Gruppe löschen…", 3) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "loeschen_gruppe", "gid": gid}) + has_entries = true + # Objekt-Zeile: Gruppieren (wenn ≥ 2 markiert), Aus Gruppe lösen, Löschen. + if item.has_meta("obj_index"): + var sel: Array = main.interaction.selected_indices + if sel.size() >= 2: + _context_menu.add_item("🔗 Gruppieren (%d Objekte)" % sel.size(), 10) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "gruppieren"}) + has_entries = true + var objects: Array = main.doc.get("objects", []) + var idx := int(item.get_meta("obj_index")) + if idx >= 0 and idx < objects.size() and GroupOps.group_id(objects[idx]) != GroupOps.UNGROUPED: + _context_menu.add_item("⛓ Aus Gruppe lösen", 1) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "loesen_einzeln", "idx": idx}) + has_entries = true + _context_menu.add_item("🗑 Löschen", 3) + _context_menu.set_item_metadata(_context_menu.item_count - 1, {"aktion": "loeschen_einzeln", "idx": idx}) + has_entries = true + if not has_entries: + return + _context_menu.position = at_screen + _context_menu.popup() + + +## Menü-Auswahl → Aktion (Metadaten tragen Ziel; ID → Zeile über get_item_index, +## robust auch nachdem das Menü sich bei hide_on_item_selection schon versteckt). +func _on_menu_action(id: int) -> void: + var menu_index := _context_menu.get_item_index(id) + if menu_index < 0: + return + var meta: Variant = _context_menu.get_item_metadata(menu_index) + if not (meta is Dictionary): + return + var m := meta as Dictionary + match str(m.get("aktion", "")): + "gruppieren": + group_selected() + "loesen_gruppe": + ungroup_group(int(m.get("gid", -1))) + "loesen_einzeln": + ungroup_indices([int(m.get("idx", -1))]) + "umbenennen": + open_rename_dialog(int(m.get("gid", -1))) + "loeschen_gruppe": + delete_group(int(m.get("gid", -1))) + "loeschen_einzeln": + _select_for_action([int(m.get("idx", -1))]) + main.interaction.delete_selected() + + +## Auswahl für eine Aktion setzen (Baum-Markierung + 3D synchron). +func _select_for_action(indices: Array) -> void: + _tree_select_lock = true + main.interaction.select_many(indices) + _tree_select_lock = false + + +## Mehrfachauswahl → Gruppe (nutzt die bestehende interaction-Logik, EIN Undo). +func group_selected() -> void: + var sel: Array = main.interaction.selected_indices + if sel.size() < 2: + main.call("_set_status", "Gruppieren braucht mindestens 2 markierte Objekte — Strg+Klick im Baum oder im Viewport.", true) + return + main.interaction.group_selection() + _refresh_after_group_change() + _select_for_action(sel) # Gruppe bleibt markiert (Mitglieder hängen jetzt unter dem Gruppen-Knoten) + + +## GANZE Gruppe auflösen: Mitglieder anwählen → bestehende Löse-Logik. +func ungroup_group(gid: int) -> void: + var members: Array = GroupOps.members_of(main.doc, gid) + if members.is_empty(): + main.call("_set_status", "Gruppe %d existiert nicht mehr." % (gid + 1), true) + return + _select_for_action(members) + main.interaction.ungroup_selection() + _refresh_after_group_change() + + +## Objekte aus ihrer Gruppe herauslösen (NUR diese Indizes — Drag-out-Pfad). +## EIN Undo-Schritt: snapshot ["objects", "group_names"] vor/nach (verwaiste +## Namen-Einträge werden im selben Schritt mitgeräumt). +func ungroup_indices(indices: Array) -> void: + var before := _snapshot_group_blocks() + var freed := GroupState.clear_group_fields(main.doc, indices) + if freed == 0: + main.call("_set_status", "Die markierten Objekte stecken in keiner Gruppe.") + return + GroupState.prune_orphans(main.doc) + main.history.commit("Objekt aus Gruppe gelöst (%d)" % freed, + before, _snapshot_group_blocks()) + main.call("_refresh_history_buttons") + _refresh_after_group_change() + + +## Snapshot für Gruppen-Undo-Schritte: ["objects", "group_names"]. Fehlt der +## Namen-Block im Dokument (alte Maps), wird er als null SEEDED — ActionHistory +## wendet nur Schlüssel an, die im Snapshot stehen; ohne Seed bliebe der Name +## nach Undo als Leiche stehen (Restore von null löscht den Block sauber). +func _snapshot_group_blocks() -> Dictionary: + var out: Dictionary = main.history.snapshot(["objects", "group_names"]) + if not out.has(GroupState.NAMES_KEY): + out[GroupState.NAMES_KEY] = null + return out + + +## Umbenennen-Kern (testbar ohne Dialog): Name im Dokument setzen, EIN Undo. +func rename_group(gid: int, new_name: String) -> bool: + if gid < 0: + return false + var clean := new_name.strip_edges() + if GroupState.group_name(main.doc, gid) == clean: + return false # Name unverändert — kein leerer Undo-Eintrag + var before := _snapshot_group_blocks() + GroupState.set_group_name(main.doc, gid, new_name) + GroupState.prune_orphans(main.doc) + var after := _snapshot_group_blocks() + var label := ("Gruppe umbenannt in „%s“" % clean) if not clean.is_empty() \ + else ("Gruppenname entfernt (Gruppe %d)" % (gid + 1)) + main.history.commit(label, before, after) + main.call("_refresh_history_buttons") + _refresh_after_group_change() + return true + + +## Umbenennen-Dialog (Rechtsklick am Gruppen-Knoten). +func open_rename_dialog(gid: int) -> void: + var members: Array = GroupOps.members_of(main.doc, gid) + if members.is_empty(): + main.call("_set_status", "Gruppe %d existiert nicht mehr." % (gid + 1), true) + return + _rename_gid = gid + _rename_field.text = GroupState.group_name(main.doc, gid) + _rename_dialog.dialog_text = "Neuer Name für die Gruppe (leer = wieder „Gruppe %d“):" % (gid + 1) + _rename_field.grab_focus() + _rename_dialog.popup_centered(Vector2i(360, 100)) + + +func _on_rename_confirmed() -> void: + if _rename_gid >= 0: + rename_group(_rename_gid, _rename_field.text) + _rename_gid = -1 + + +## Gruppe löschen: Rückfrage bei > 1 Objekt, dann bestehende Lösch-Logik +## (interaction.delete_selected — EIN Undo-Schritt, Marker/3D räumen mit auf). +func delete_group(gid: int) -> void: + var members: Array = GroupOps.members_of(main.doc, gid) + if members.is_empty(): + main.call("_set_status", "Gruppe %d existiert nicht mehr." % (gid + 1), true) + return + if members.size() > 1: + _confirm_gid = gid + _confirm_delete.dialog_text = "Gruppe „%s“ löschen? Die %d Objekte darin werden ALLE gelöscht — Strg+Z holt sie zurück." % [GroupState.display_name(main.doc, gid), members.size()] + _confirm_delete.popup_centered(Vector2i(480, 140)) + return + delete_group_confirmed(gid) + + +## Lösch-Kern NACH der Rückfrage (auch direkt aufrufbar — Tests). +func delete_group_confirmed(gid: int) -> void: + var members: Array = GroupOps.members_of(main.doc, gid) + if members.is_empty(): + return + _select_for_action(members) + main.interaction.delete_selected() # ruft selbst _after_structure_change → Baum neu + + +func _on_delete_confirmed() -> void: + if _confirm_gid >= 0: + delete_group_confirmed(_confirm_gid) + _confirm_gid = -1 + + +# --- Drag & Drop (Mitgliedschaft ändern) ------------------------------------- + +## Zieh-Menge bestimmen: gezogene Zeile ist Teil der Markierung → die GANZE +## Markierung wandert mit; sonst nur die gezogene Zeile. (Testbar ohne echten +## Drag — _tree_drag_data ruft nur dies + set_drag_preview.) +func _collect_drag_indices(item: TreeItem) -> Array: + if item == null or not item.has_meta("obj_index"): + return [] + var idx := int(item.get_meta("obj_index")) + var sel: Array = main.interaction.selected_indices + if idx in sel: + var out: Array = [] + for i in sel: + if i is int: + out.append(int(i)) + return out + return [idx] + + +## Tree-Drag-Start (set_drag_forwarding-Callable): Datenpaket für Drop-Ziele. +## Vorschau-Control wird NICHT in den Baum gehängt (set_drag_preview verlangt +## ein Control außerhalb des Baums). +func _tree_drag_data(from_position: Vector2) -> Variant: + var item: TreeItem = content_tree.get_item_at_position(from_position) + var indices := _collect_drag_indices(item) + if indices.is_empty(): + return null + var label := Label.new() + label.text = "%d Objekt(e) ziehen" % indices.size() + content_tree.set_drag_preview(label) + return {"svmap_tree_objekte": indices} + + +## Drop erlaubt: AUF einen Gruppen-Knoten (aufnehmen) oder AUF den Objekte-Kopf/ +## eine Kategorie-Zeile (herauslösen — nur wenn mindestens ein gezogenes Objekt +## überhaupt gruppiert ist). +func _tree_can_drop(at_position: Vector2, data: Variant) -> bool: + if not (data is Dictionary) or not (data as Dictionary).has("svmap_tree_objekte"): + return false + var item: TreeItem = content_tree.get_item_at_position(at_position) + if item == null: + return false + if item.has_meta("group_id"): + return true + if item.has_meta("objekte_kopf"): + var objects: Array = main.doc.get("objects", []) + for i in (data["svmap_tree_objekte"] as Array): + if i is int and i >= 0 and i < objects.size() \ + and GroupOps.group_id(objects[i]) != GroupOps.UNGROUPED: + return true + return false + + +## Drop ausführen → Mitgliedschaft ändern (EIN Undo-Schritt), Baum/Auswahl neu. +func _tree_drop(at_position: Vector2, data: Variant) -> void: + var item: TreeItem = content_tree.get_item_at_position(at_position) + if item == null: + return + var indices: Array = (data as Dictionary).get("svmap_tree_objekte", []) + if item.has_meta("group_id"): + _apply_membership_drop(indices, int(item.get_meta("group_id"))) + elif item.has_meta("objekte_kopf"): + _apply_membership_drop(indices, -1) + + +## Mitgliedschafts-Kern (testbar): gid ≥ 0 = aufnehmen, gid < 0 = herauslösen. +func _apply_membership_drop(indices: Array, gid: int) -> void: + var before := _snapshot_group_blocks() + var changed := 0 + var label := "" + if gid >= 0: + changed = GroupState.assign_group(main.doc, indices, gid) + label = "%d Objekt(e) in „%s“ gezogen" % [changed, GroupState.display_name(main.doc, gid)] + else: + changed = GroupState.clear_group_fields(main.doc, indices) + label = "%d Objekt(e) aus der Gruppe herausgelöst" % changed + if changed == 0: + main.call("_set_status", "Nichts zu ändern — die Objekte gehören schon genau so zur Gruppe.") + return + GroupState.prune_orphans(main.doc) + main.history.commit(label, before, _snapshot_group_blocks()) + main.call("_refresh_history_buttons") + _refresh_after_group_change() + _select_for_action(indices) # gezogene Objekte bleiben sichtbar markiert + + +## Nach jeder Gruppen-Änderung: Baum neu + Panels synchron (kein 3D-Rebuild — +## Positionen ändern sich beim Gruppieren/Umbenennen/Zuordnen nicht). +func _refresh_after_group_change() -> void: + main.call("_refresh_tree") + var sel: Array = main.interaction.selected_indices + _tree_select_lock = true + mark_tree_selection_multi(sel) + _tree_select_lock = false + main.call("_sync_selection_panel") diff --git a/ui/panels/scene_tree_panel.gd.uid b/ui/panels/scene_tree_panel.gd.uid new file mode 100644 index 0000000..a7e6184 --- /dev/null +++ b/ui/panels/scene_tree_panel.gd.uid @@ -0,0 +1 @@ +uid://bpbw3vo36ohv1 diff --git a/ui/panels/selection_panel.gd b/ui/panels/selection_panel.gd new file mode 100644 index 0000000..2e87c83 --- /dev/null +++ b/ui/panels/selection_panel.gd @@ -0,0 +1,67 @@ +extends Node +## Auswahl-Logik (U1, Karte t_eaccbfed — aus main.gd ausgezogen). Seit 25.10. +## KEIN Tab mehr (Test-Befund): Drehen/Anheben/Duplizieren/Löschen laufen in der +## 3D-Ansicht (Werkzeugleiste, Gizmo, Tasten), Gruppen im Map-Inhalt links, die +## Gizmo-Schrittweite im Einstellungen-Fenster. Übrig bleibt die Synchronisation: +## Inspektor nachziehen, Gizmo-Schrittweite-Feld folgen, Statuszeile zur frischen +## Auswahl (Klartext + Poly-Budget-Warnung). +## +## Verdrahtung: main.gd ruft sync_selection() an allen Auswahl-Wegen +## (vorher: _sync_selection_panel) und selection_status(idx) bei frischer +## Auswahl (vorher: _selection_status). Alle Objekt-Operationen laufen über +## main.interaction — der Vertrag zu interaction.gd bleibt identisch. + +var main # core/ui/main.gd (script-erweitert → untypisiert) + + +func setup(p_main) -> void: + main = p_main + + +## Objekt-Inspektor + Auswahl-Panel synchron halten (vorher +## main.gd/_sync_selection_panel — Logik unverändert). +func sync_selection() -> void: + # Objekt-Inspektor mit allen Auswahl-Wege synchron halten (Karte t_bb0d11c9). + if main.inspector != null: + main.inspector.call("refresh") + # Gizmo-Schrittweite-Feld mitziehen (Karte t_e0120dc2): Das Mausrad + # WÄHREND eines Zugs kann sie im Gizmo ändern (cycle_step) — das Panel- + # Feld folgt ohne eigene value_changed-Rückkopplung (set_value_no_signal). + var step_box: SpinBox = main.gizmo_step_box + if step_box != null and is_instance_valid(step_box) and not step_box.has_focus(): + step_box.set_value_no_signal(main.gizmo_step_m) + + +## Hilfs-Meldung zur frischen Auswahl (Klartext + Budget-Warnung + nächster +## Schritt) — vorher main.gd/_selection_status, Logik unverändert. +func selection_status(idx: int) -> void: + # Objekt-Inspektor füllen (Karte t_bb0d11c9) — dieser Weg läuft bei + # 3D-Klick, Rahmen-Ausziehen und Map-Inhalt-Baum. + if main.inspector != null: + main.inspector.call("refresh") + # sync_selection ändert bewusst KEINEN Statuszeilen-Text (Stummsync). + sync_selection() + var doc: Dictionary = main.doc + var objects: Array = doc.get("objects", []) + var sel: Array = main.interaction.selected_indices + if (idx < 0 or idx >= objects.size()) and sel.is_empty(): + main._base_tool_status() + return + var used := int(SvmapDocument.poly_usage(doc, main.manifest)) + var budget := int(SvmapIO.MAX_TOTAL_POLY) + var budget_warning := "" + if used > budget * 80 / 100: + budget_warning = " ⚠ Poly-Budget zu %.0f %% voll — bald passt kein Objekt mehr rein." % (100.0 * used / budget) + if sel.size() > 1: + main._set_status("%d Objekte ausgewählt — ziehen bewegt ALLE gemeinsam, R/Mausrad dreht sie um die Mitte, Entf löscht alle, Strg+G gruppiert.%s" % [sel.size(), budget_warning]) + return + if idx < 0 or idx >= objects.size(): + main._base_tool_status() + return + var obj: Dictionary = objects[idx] + var gid := GroupOps.group_id(obj) + var group_hint := "" + if gid != GroupOps.UNGROUPED: + group_hint = " (Gruppe %d — die zieht GEMEINSAM)" % (gid + 1) + main._set_status("%s%s ausgewählt — ziehen zum Verschieben, R oder Mausrad zum Drehen, Bild↑/↓ anheben/absenken, Strg+D duplizieren, Entf zum Löschen.%s" % [ + main.object_label(str(obj.get("id", ""))), group_hint, budget_warning]) \ No newline at end of file diff --git a/ui/panels/selection_panel.gd.uid b/ui/panels/selection_panel.gd.uid new file mode 100644 index 0000000..80c9c4d --- /dev/null +++ b/ui/panels/selection_panel.gd.uid @@ -0,0 +1 @@ +uid://dwp3df5nsfl3s diff --git a/ui/panels/server_panel.gd b/ui/panels/server_panel.gd new file mode 100644 index 0000000..655c1d2 --- /dev/null +++ b/ui/panels/server_panel.gd @@ -0,0 +1,687 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Tab „Server“ (Etappe 3 „Server-Verwaltung“): Client der Match-Server-Admin-API. +## Mehrere Server speichern, verbinden, Status, Einstellungen, Spieler, Maps, +## Rotation, Logs und „Aktuelle Map auf den Server pushen“ – alles per Klick. +## Logik: core/server/ (AdminClient, ServerStore, AdminTexte, MapPush). + +var store: ServerStore +var store_path := ServerStore.DEFAULT_PATH # Tests lenken um +var client: AdminClient +var current_id := "" +var last_notice := "" # letzte Meldung (Tests) +var connected := false +var restart_wait_s := 120.0 # so lange wartet „Server neu starten“ (Tests verkürzen) +var restart_poll_s := 2.0 + +var _sel: OptionButton +var _conn_label: Label +var _notice: Label +var _status_label: Label +var _live_box: VBoxContainer # alles, was einen verbundenen Server braucht +var _push_name: LineEdit +var _push_rot: CheckBox +var _push_result: Label +var _settings_box: VBoxContainer +var _settings_editors: Dictionary = {} # key -> Control +var _settings_loaded: Dictionary = {} +var _settings_spec: Dictionary = {} +var _settings_result: Label +var _players_box: VBoxContainer +var _bans_box: VBoxContainer +var _ban_minutes: OptionButton +var _maps_box: VBoxContainer +var _rotation_checks: Dictionary = {} # map id -> CheckBox +var _log_filter: LineEdit +var _log_text: TextEdit +var _confirm: ConfirmationDialog +var _confirm_action: Callable +var _edit_dialog: ConfirmationDialog +var _edit_fields: Dictionary = {} +var _edit_id := "" + +const BAN_DAUER := [[10, "10 Minuten"], [60, "1 Stunde"], [1440, "1 Tag"], [10080, "1 Woche"], [0, "Dauerhaft"]] + + +static func spec() -> Dictionary: + return {"titel": "Server", "gruppe": "rechts", "order": 50} + + +func panel_title() -> String: + return "Server" + + +func build() -> void: + store = ServerStore.new(store_path) + store.load_store() + client = AdminClient.new() + client.name = "AdminClient" + add_child(client) + + add_child(_header_label("Server verwalten")) + add_child(_muted_label("Verbinde den Editor mit deinem Match-Server (Admin-API), um Maps hochzuladen und den Server zu steuern.")) + var hint := _muted_label(AdminTexte.TUNNEL_HINWEIS) + hint.visible = false + var hint_btn := Button.new() + hint_btn.text = "❓ Wie erreiche ich meinen Server sicher?" + hint_btn.flat = true + hint_btn.pressed.connect(func() -> void: hint.visible = not hint.visible) + add_child(hint_btn) + add_child(hint) + + _sel = OptionButton.new() + _sel.custom_minimum_size = UIScale.v2(0, 36) + _sel.item_selected.connect(func(_i: int) -> void: _on_selected()) + add_child(_sel) + var row := HBoxContainer.new() + add_child(row) + row.add_child(_btn("➕ Neu", _on_add)) + row.add_child(_btn("✏ Bearbeiten", _on_edit)) + row.add_child(_btn("🗑 Entfernen", _on_remove)) + var conn_row := HBoxContainer.new() + add_child(conn_row) + conn_row.add_child(_btn("🔌 Verbinden", func() -> void: connect_selected())) + _conn_label = Label.new() + _conn_label.text = "Nicht verbunden" + _conn_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _conn_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + conn_row.add_child(_conn_label) + _notice = _muted_label("") + add_child(_notice) + + _live_box = VBoxContainer.new() + _live_box.add_theme_constant_override("separation", UIScale.px(8)) + _live_box.visible = false + add_child(_live_box) + _build_live() + _build_dialogs() + _refresh_selector() + wrap_in_scroll() + + +func _btn(text: String, cb: Callable) -> Button: + var b := Button.new() + b.text = text + b.custom_minimum_size = UIScale.v2(0, 36) + b.pressed.connect(cb) + return b + + +func _section(title: String) -> void: + _live_box.add_child(_separator()) + _live_box.add_child(_header_label(title)) + + +func _build_live() -> void: + _section("Übersicht") + _status_label = Label.new() + _status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _live_box.add_child(_status_label) + _live_box.add_child(_btn("🔄 Alles aktualisieren", func() -> void: refresh_all())) + + _section("Map auf Server pushen") + _live_box.add_child(_muted_label("Prüft die Map im Editor und lädt sie hoch. Der Server prüft sie noch einmal.")) + _push_name = LineEdit.new() + _push_name.placeholder_text = "Dateiname (leer = Map-Name)" + _live_box.add_child(_push_name) + _push_rot = CheckBox.new() + _push_rot.text = "In die Rotation aufnehmen" + _live_box.add_child(_push_rot) + var pb := _btn("⬆ Aktuelle Map hochladen", func() -> void: push_current_map()) + pb.custom_minimum_size = UIScale.v2(0, 44) + _live_box.add_child(pb) + _push_result = Label.new() + _push_result.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _live_box.add_child(_push_result) + + _section("Einstellungen") + _settings_box = VBoxContainer.new() + _live_box.add_child(_settings_box) + var srow := HBoxContainer.new() + _live_box.add_child(srow) + srow.add_child(_btn("💾 Speichern", func() -> void: save_settings())) + srow.add_child(_btn("↩ Zurücksetzen", func() -> void: _fill_settings_editors())) + _settings_result = _muted_label("") + _live_box.add_child(_settings_result) + + _section("Spieler") + _players_box = VBoxContainer.new() + _live_box.add_child(_players_box) + var brow := HBoxContainer.new() + _live_box.add_child(brow) + brow.add_child(_muted_label("Sperrdauer:")) + _ban_minutes = OptionButton.new() + for d in BAN_DAUER: + _ban_minutes.add_item(d[1]) + _ban_minutes.select(1) + brow.add_child(_ban_minutes) + _live_box.add_child(_muted_label("Gesperrt:")) + _bans_box = VBoxContainer.new() + _live_box.add_child(_bans_box) + + _section("Match & Maps") + _live_box.add_child(_btn("🔁 Match neu starten", func() -> void: _ask("Match auf dem Server neu starten? Alle Spieler beginnen die Runde von vorn.", _do_restart))) + _maps_box = VBoxContainer.new() + _live_box.add_child(_maps_box) + _live_box.add_child(_btn("💾 Rotation speichern", func() -> void: save_rotation())) + + _section("Server") + _live_box.add_child(_btn("⏻ Server neu starten", func() -> void: _ask("Server komplett neu starten? Alle Spieler werden getrennt und die laufende Runde endet.", func() -> void: await restart_server()))) + + _section("Server-Log") + _log_filter = LineEdit.new() + _log_filter.placeholder_text = "Filter (z. B. Admin, Join, FEHLER)" + _live_box.add_child(_log_filter) + _live_box.add_child(_btn("📜 Log laden", func() -> void: load_logs())) + _log_text = TextEdit.new() + _log_text.editable = false + _log_text.custom_minimum_size = UIScale.v2(0, 220) + _log_text.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY + _live_box.add_child(_log_text) + + +func _build_dialogs() -> void: + _confirm = ConfirmationDialog.new() + _confirm.ok_button_text = "Ja" + _confirm.cancel_button_text = "Abbrechen" + _confirm.confirmed.connect(func() -> void: + if _confirm_action.is_valid(): + _confirm_action.call()) + add_child(_confirm) + + _edit_dialog = ConfirmationDialog.new() + _edit_dialog.title = "Server" + _edit_dialog.ok_button_text = "Speichern" + _edit_dialog.cancel_button_text = "Abbrechen" + var box := VBoxContainer.new() + _edit_dialog.add_child(box) + for f in [["name", "Name (frei wählbar)", "Mein Server"], ["host", "Adresse (IP oder Name)", "127.0.0.1"], + ["port", "Port der Admin-API", str(ServerStore.DEFAULT_PORT)], ["token", "Admin-Token", ""]]: + box.add_child(_field_label(f[1])) + var le := LineEdit.new() + le.placeholder_text = f[2] + le.custom_minimum_size = UIScale.v2(320, 32) + if f[0] == "token": + le.secret = true + box.add_child(le) + _edit_fields[f[0]] = le + var tls := CheckBox.new() + tls.text = "Sichere Verbindung (https) – nur mit Reverse-Proxy" + box.add_child(tls) + _edit_fields["tls"] = tls + var show := CheckBox.new() + show.text = "Token anzeigen" + show.toggled.connect(func(on: bool) -> void: (_edit_fields["token"] as LineEdit).secret = not on) + box.add_child(show) + var err := _muted_label("") + box.add_child(err) + _edit_fields["_err"] = err + box.add_child(_btn("🔌 Verbindung testen", func() -> void: _test_dialog_entry())) + _edit_dialog.confirmed.connect(_on_edit_confirmed) + add_child(_edit_dialog) + + +# ------------------------------------------------------------------ Liste -- + +func _refresh_selector(select_id: String = "") -> void: + _sel.clear() + for e in store.servers: + _sel.add_item("%s (%s:%d)" % [e["name"], e["host"], e["port"]]) + _sel.set_item_metadata(_sel.item_count - 1, e["id"]) + if _sel.item_count == 0: + _sel.add_item("– noch kein Server gespeichert –") + _sel.disabled = true + current_id = "" + return + _sel.disabled = false + var idx := 0 + for i in _sel.item_count: + if str(_sel.get_item_metadata(i)) == (select_id if select_id != "" else current_id): + idx = i + _sel.select(idx) + _on_selected() + + +func _on_selected() -> void: + if _sel.item_count == 0 or _sel.disabled: + return + var id := str(_sel.get_item_metadata(_sel.selected)) + if id != current_id: + _set_connected(false, "Nicht verbunden") + current_id = id + + +func _on_add() -> void: + _open_edit({}) + + +func _on_edit() -> void: + if current_id != "": + _open_edit(store.get_entry(current_id)) + + +func _open_edit(e: Dictionary) -> void: + _edit_id = str(e.get("id", "")) + _edit_dialog.title = "Server bearbeiten" if _edit_id != "" else "Server hinzufügen" + (_edit_fields["name"] as LineEdit).text = str(e.get("name", "")) + (_edit_fields["host"] as LineEdit).text = str(e.get("host", "")) + (_edit_fields["port"] as LineEdit).text = str(e.get("port", ServerStore.DEFAULT_PORT)) + (_edit_fields["token"] as LineEdit).text = str(e.get("token", "")) + (_edit_fields["tls"] as CheckBox).button_pressed = bool(e.get("tls", false)) + (_edit_fields["_err"] as Label).text = "" + _edit_dialog.popup_centered(UIScale.v2i(380, 420)) + + +func _entry_from_dialog() -> Dictionary: + return { + "id": _edit_id, "name": (_edit_fields["name"] as LineEdit).text, + "host": (_edit_fields["host"] as LineEdit).text.strip_edges(), + "port": int((_edit_fields["port"] as LineEdit).text), + "token": (_edit_fields["token"] as LineEdit).text, + "tls": (_edit_fields["tls"] as CheckBox).button_pressed} + + +func _on_edit_confirmed() -> void: + var id := store.upsert(_entry_from_dialog()) + if id == "": + _notify(store.last_error) + return + _refresh_selector(id) + + +func _test_dialog_entry() -> void: + var e := _entry_from_dialog() + var err := AdminClient.check_address(str(e["host"]), int(e["port"])) + var lbl: Label = _edit_fields["_err"] + if err != "": + lbl.text = err + return + lbl.text = "Teste …" + var probe := AdminClient.new() + add_child(probe) + probe.use(e) + var res: Dictionary = await probe.call_api("GET", "/ping") + if bool(res["ok"]): + var auth: Dictionary = await probe.call_api("GET", "/status") + lbl.text = "✅ Server gefunden. " + ("Token passt." if bool(auth["ok"]) else AdminTexte.erklaere(auth)) + else: + lbl.text = "❌ " + AdminTexte.erklaere(res) + probe.queue_free() + + +func _on_remove() -> void: + if current_id == "": + return + var e := store.public_view(current_id) + _ask("„%s“ aus der Liste entfernen? (Der Server selbst bleibt unverändert.)" % str(e["name"]), func() -> void: + store.remove(current_id) + current_id = "" + _set_connected(false, "Nicht verbunden") + _refresh_selector()) + + +# ---------------------------------------------------------------- Verbinden -- + +func connect_selected() -> bool: + if current_id == "": + _notify("Bitte erst einen Server hinzufügen (➕ Neu).") + return false + client.use(store.get_entry(current_id)) + _conn_label.text = "Verbinde …" + var ping: Dictionary = await client.call_api("GET", "/ping") + if not bool(ping["ok"]): + _set_connected(false, "❌ " + AdminTexte.erklaere(ping)) + return false + var st: Dictionary = await client.call_api("GET", "/status") + if not bool(st["ok"]): + _set_connected(false, "❌ " + AdminTexte.erklaere(st)) + return false + _set_connected(true, "✅ Verbunden") + await refresh_all() + return true + + +func _set_connected(on: bool, text: String) -> void: + connected = on + _conn_label.text = text + _live_box.visible = on + + +## Ruft die API auf und meldet Fehler verständlich. null = Fehler (schon gemeldet). +func _api(method: String, path: String, body: Variant = null, query: Dictionary = {}) -> Variant: + var res: Dictionary = await client.call_api(method, path, body, query) + if bool(res["ok"]): + return res["body"] + var text := AdminTexte.erklaere(res) + if int(res.get("status", 0)) in [0, 401, 429]: + _set_connected(false, "❌ " + text.split("\n")[0]) + _notify(text) + return null + + +func _notify(text: String) -> void: + last_notice = text + _notice.text = text + + +func refresh_all() -> void: + await refresh_status() + await refresh_settings() + await refresh_players() + await refresh_maps() + + +# ------------------------------------------------------------------ Status -- + +func refresh_status() -> void: + var s: Variant = await _api("GET", "/status") + if s == null: + return + var p: Dictionary = s["players"] + var h := int(s["uptime_s"]) / 3600 + var m := (int(s["uptime_s"]) % 3600) / 60 + _status_label.text = "%s\nMap: %s · Modus: %s · Phase: %s\nSpieler: %d von %d (+ %d Bots) · Restzeit %d s\nLäuft seit %d h %d min · %s · Passwort: %s · Master: %s" % [ + s["server_name"], s["map"]["name"], str(s["mode"]).to_upper(), s["phase"], + p["humans"], p["max"], p["bots"], int(s["time_remaining_s"]), h, m, + "öffentlich" if s["public"] else "privat", "ja" if s["password_set"] else "nein", s["master"]] + + +# ------------------------------------------------------------ Einstellungen -- + +func refresh_settings() -> void: + var r: Variant = await _api("GET", "/settings") + if r == null: + return + _settings_spec = r["spec"] + _settings_loaded = r["settings"] + for c in _settings_box.get_children(): + c.queue_free() + _settings_editors.clear() + for key in _settings_spec.keys(): + var sp: Dictionary = _settings_spec[key] + var lbl := _field_label("%s (%s)" % [sp["label"], AdminTexte.wirkung(str(sp["effect"]))]) + _settings_box.add_child(lbl) + var ed: Control + match str(sp["type"]): + "bool": + ed = CheckBox.new() + "int", "float": + var sb := SpinBox.new() + sb.min_value = float(sp["min"]) + sb.max_value = float(sp["max"]) + sb.step = 1.0 if str(sp["type"]) == "int" else 0.5 + ed = sb + "enum": + var ob := OptionButton.new() + for v in sp["values"]: + ob.add_item(str(v)) + ed = ob + _: + var le := LineEdit.new() + le.max_length = int(sp.get("max_len", 64)) + if key == "password": + le.secret = true + le.placeholder_text = "(unverändert)" + ed = le + _settings_box.add_child(ed) + _settings_editors[key] = ed + _fill_settings_editors() + + +func _fill_settings_editors() -> void: + for key in _settings_editors.keys(): + var ed: Control = _settings_editors[key] + var sp: Dictionary = _settings_spec[key] + if key == "password": + (ed as LineEdit).text = "" + (ed as LineEdit).placeholder_text = "(gesetzt – leer lassen = unverändert)" if bool(_settings_loaded.get("password_set", false)) else "(kein Passwort)" + continue + var v: Variant = _settings_loaded.get(key) + match str(sp["type"]): + "bool": (ed as CheckBox).button_pressed = bool(v) + "int", "float": (ed as SpinBox).value = float(v) + "enum": (ed as OptionButton).select(maxi(0, (sp["values"] as Array).find(v))) + _: (ed as LineEdit).text = str(v) + _settings_result.text = "" + + +## Nur geänderte Werte (Passwort nur, wenn etwas eingetippt wurde). +func changed_settings() -> Dictionary: + var out := {} + for key in _settings_editors.keys(): + var ed: Control = _settings_editors[key] + var sp: Dictionary = _settings_spec[key] + var v: Variant + match str(sp["type"]): + "bool": v = (ed as CheckBox).button_pressed + "int": v = int((ed as SpinBox).value) + "float": v = (ed as SpinBox).value + "enum": v = (sp["values"] as Array)[(ed as OptionButton).selected] + _: v = (ed as LineEdit).text + if key == "password": + if str(v) != "": + out[key] = v + continue + if v != _settings_loaded.get(key): + out[key] = v + return out + + +func save_settings() -> void: + var ch := changed_settings() + if ch.is_empty(): + _settings_result.text = "Nichts geändert." + return + var r: Variant = await _api("PATCH", "/settings", ch) + if r == null: + return + var lines: PackedStringArray = [] + if not (r["applied_now"] as Array).is_empty(): + lines.append("✅ Sofort aktiv: " + ", ".join(_labels(r["applied_now"]))) + if not (r["applies_next_map"] as Array).is_empty(): + lines.append("⏭ Ab der nächsten Map: " + ", ".join(_labels(r["applies_next_map"]))) + if not (r["needs_restart"] as Array).is_empty(): + lines.append("🔁 Erst nach Server-Neustart: " + ", ".join(_labels(r["needs_restart"]))) + for w in r.get("warnings", []): + lines.append("⚠ " + str(w)) + _settings_result.text = "\n".join(lines) + last_notice = _settings_result.text + _settings_loaded = r["settings"] + _fill_settings_editors() + _settings_result.text = "\n".join(lines) + + +func _labels(keys: Array) -> Array: + return keys.map(func(k: Variant) -> String: return str((_settings_spec.get(k, {}) as Dictionary).get("label", k))) + + +# ----------------------------------------------------------------- Spieler -- + +func refresh_players() -> void: + var r: Variant = await _api("GET", "/players") + var b: Variant = await _api("GET", "/bans") + if r == null or b == null: + return + for c in _players_box.get_children(): + c.queue_free() + if (r["players"] as Array).is_empty(): + _players_box.add_child(_muted_label("Gerade ist niemand auf dem Server.")) + for p in r["players"]: + var row := HBoxContainer.new() + var lbl := Label.new() + lbl.text = "%s (Team %s · %d/%d · %d ms)" % [p["name"], "Blau" if int(p["team"]) == 0 else "Rot", p["kills"], p["deaths"], p["ping_ms"]] + lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL + lbl.clip_text = true + row.add_child(lbl) + var pid := int(p["peer_id"]) + var pname := str(p["name"]) + row.add_child(_btn("Kick", func() -> void: _ask("„%s“ vom Server werfen?" % pname, func() -> void: await kick(pid)))) + row.add_child(_btn("Sperren", func() -> void: _ask("„%s“ sperren (%s)?" % [pname, BAN_DAUER[_ban_minutes.selected][1]], func() -> void: await ban(pid)))) + _players_box.add_child(row) + for c in _bans_box.get_children(): + c.queue_free() + if (b["bans"] as Array).is_empty(): + _bans_box.add_child(_muted_label("Niemand gesperrt.")) + for ban_e in b["bans"]: + var row2 := HBoxContainer.new() + var l2 := Label.new() + var until := int(ban_e.get("until", 0)) + l2.text = "%s – %s" % [str(ban_e.get("name", "?")), "dauerhaft" if until == 0 else "bis " + Time.get_datetime_string_from_unix_time(until, true)] + l2.size_flags_horizontal = Control.SIZE_EXPAND_FILL + l2.clip_text = true + row2.add_child(l2) + var bid := str(ban_e["id"]) + row2.add_child(_btn("Aufheben", func() -> void: await unban(bid))) + _bans_box.add_child(row2) + + +func kick(peer_id: int) -> void: + if await _api("POST", "/players/%d/kick" % peer_id, {}) != null: + _notify("Spieler wurde vom Server geworfen.") + await refresh_players() + + +func ban(peer_id: int, minutes: int = -1) -> void: + if minutes < 0: + minutes = int(BAN_DAUER[_ban_minutes.selected][0]) + if await _api("POST", "/players/%d/ban" % peer_id, {"minutes": minutes}) != null: + _notify("Spieler gesperrt (%s)." % AdminTexte.sperrdauer_text(minutes)) + await refresh_players() + + +func unban(ban_id: String) -> void: + if await _api("DELETE", "/bans/" + ban_id) != null: + _notify("Sperre aufgehoben.") + await refresh_players() + + +# -------------------------------------------------------------- Match, Maps -- + +func _do_restart() -> void: + if await _api("POST", "/match/restart", {}) != null: + _notify("Match wird neu gestartet.") + await refresh_status() + + +## Ganzen Server neu starten und warten, bis er wieder antwortet; dann neu verbinden. +func restart_server() -> bool: + var id := current_id + if await _api("POST", "/restart", {}) == null: + return false + _set_connected(false, "Server startet neu…") + _notify("Server startet neu…") + var res: Dictionary = await _wait_for_new_server(id) + if current_id != id: + return false # inzwischen anderen Server gewählt + if not bool(res["ok"]): + _set_connected(false, "❌ " + str(res["text"])) + _notify(str(res["text"])) + return false + var ok: bool = await connect_selected() + if ok: + _notify("✅ Server wurde neu gestartet.") + return ok + + +## Der alte Prozess antwortet noch kurz: Erst eine Laufzeit, die kleiner ist als die +## Wartezeit bisher, beweist den neuen Prozess. +func _wait_for_new_server(id: String) -> Dictionary: + var start := Time.get_ticks_msec() + while current_id == id: + var waited := (Time.get_ticks_msec() - start) / 1000.0 + if waited >= restart_wait_s: + return {"ok": false, "text": AdminTexte.neustart_zeitueberschreitung(int(restart_wait_s))} + var st: Dictionary = await client.call_api("GET", "/status") + if bool(st["ok"]): + if float(st["body"].get("uptime_s", INF)) <= (Time.get_ticks_msec() - start) / 1000.0: + return {"ok": true, "text": ""} + elif int(st.get("status", 0)) in [401, 429]: + return {"ok": false, "text": AdminTexte.erklaere(st)} + await get_tree().create_timer(restart_poll_s).timeout + return {"ok": false, "text": ""} + + +func refresh_maps() -> void: + var r: Variant = await _api("GET", "/maps") + if r == null: + return + for c in _maps_box.get_children(): + c.queue_free() + _rotation_checks.clear() + for m in r["maps"]: + var row := HBoxContainer.new() + var cb := CheckBox.new() + cb.button_pressed = bool(m["in_rotation"]) + cb.tooltip_text = "In der Rotation" + row.add_child(cb) + _rotation_checks[str(m["id"])] = cb + var lbl := Label.new() + lbl.text = "%s%s%s" % [m.get("name", m["file"]), " ▶ läuft" if m["active"] else "", " (eingebaut)" if m["source"] == "builtin" else ""] + lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL + lbl.clip_text = true + row.add_child(lbl) + var mid := str(m["id"]) + var mname := str(m.get("name", m["file"])) + row.add_child(_btn("Laden", func() -> void: _ask("„%s“ jetzt auf dem Server laden? Die laufende Runde endet." % mname, func() -> void: await load_map(mid)))) + if m["source"] == "upload": + var slug := mid.get_file().get_basename() + row.add_child(_btn("🗑", func() -> void: _ask("„%s“ vom Server LÖSCHEN? Das kann nicht rückgängig gemacht werden." % mname, func() -> void: await delete_map(slug)))) + _maps_box.add_child(row) + + +func load_map(map_id: String) -> void: + if await _api("POST", "/match/map", {"map": map_id}) != null: + _notify("Map wird geladen.") + await refresh_status() + + +func delete_map(slug: String) -> void: + if await _api("DELETE", "/maps/" + slug) != null: + _notify("Map gelöscht.") + await refresh_maps() + + +func save_rotation() -> void: + var ids: Array = [] + for id in _rotation_checks.keys(): + if (_rotation_checks[id] as CheckBox).button_pressed: + ids.append(id) + if ids.is_empty(): + _notify("Die Rotation braucht mindestens eine Map.") + return + if await _api("PUT", "/rotation", {"maps": ids}) != null: + _notify("Rotation gespeichert (%d Maps)." % ids.size()) + await refresh_maps() + + +# -------------------------------------------------------------------- Push -- + +func push_current_map(overwrite: bool = false) -> Dictionary: + _push_result.text = "Prüfe und lade hoch …" + var res: Dictionary = await MapPush.push(client, main.doc, { + "name": _push_name.text, "overwrite": overwrite, "add_to_rotation": _push_rot.button_pressed}) + _push_result.text = ("✅ " if bool(res["ok"]) else "❌ ") + str(res["text"]) + last_notice = _push_result.text + if bool(res["exists"]): + _ask("Auf dem Server gibt es schon eine Map mit diesem Namen. Ersetzen?", func() -> void: await push_current_map(true)) + elif bool(res["ok"]): + await refresh_maps() + return res + + +# -------------------------------------------------------------------- Logs -- + +func load_logs() -> void: + var q := {"lines": 300} + if _log_filter.text.strip_edges() != "": + q["filter"] = _log_filter.text.strip_edges() + var r: Variant = await _api("GET", "/logs", null, q) + if r == null: + return + _log_text.text = "\n".join(r["lines"]) + _log_text.scroll_vertical = _log_text.get_line_count() + + +func _ask(text: String, action: Callable) -> void: + _confirm.dialog_text = text + _confirm_action = action + _confirm.popup_centered(UIScale.v2i(380, 140)) diff --git a/ui/panels/server_panel.gd.uid b/ui/panels/server_panel.gd.uid new file mode 100644 index 0000000..5330efa --- /dev/null +++ b/ui/panels/server_panel.gd.uid @@ -0,0 +1 @@ +uid://62khp60nljbt diff --git a/ui/panels/settings_panel.gd b/ui/panels/settings_panel.gd new file mode 100644 index 0000000..6018c42 --- /dev/null +++ b/ui/panels/settings_panel.gd @@ -0,0 +1,160 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Einstellungen (Fenster „⚙ Einstellungen“ in der Kopfleiste — Karte +## t_aaff5dcc, T3 Komfort; 25.10. Test-Befund: Gitter, Höhenlinien, Symmetrie- +## Modus, UI-Größe, Raster-Snap, Gizmo-Schrittweite und „Karte prüfen“ ziehen +## aus Kopfleiste/Objekt-Werkzeugen hierher). +## +## Registry-Weg (Operator-Vorgabe 20.10.): main.gd bleibt unangetastet, das +## Modul meldet sich nur über core/ui/panels/registry.gd an. Der ZUSTAND lebt +## in core/editor/playtest/playtest_settings.gd (PlaytestSettings, user://-cfg) — +## dieses Panel zeigt/anpasst nur. Läuft gerade ein Testlauf, wird die neue +## Maus-Empfindlichkeit SOFORT über main.playtest.apply_sensitivity wirksam. + +var sens_box: SpinBox +## Perspektive im Testlauf (Karte t_17a35c59): Erste/Dritte Person, derselbe +## Eintrag wie Taste V (PlaytestSettings, persistiert). +var perspektive_box: OptionButton + +# Ansicht / Platzieren (vorher Kopfleiste und Tab „Objekt-Werkzeuge“). main +# hält dieselben Namen als Referenzen (grid_btn, contours_btn, size_menu, …). +var grid_btn: CheckButton +var contours_btn: CheckButton +var size_menu: OptionButton +var sym_menu: OptionButton +var raster_snap_check: CheckButton +var gizmo_step_box: SpinBox +var check_btn: Button + + +static func spec() -> Dictionary: + return {"titel": "Einstellungen", "gruppe": "dialog", "order": 10} + + +func panel_title() -> String: + return "Einstellungen" + + +func build() -> void: + add_child(_header_label("Ansicht")) + grid_btn = CheckButton.new() + grid_btn.text = "⊞ Maßstabsgitter (100-m-Raster)" + grid_btn.custom_minimum_size = UIScale.v2(0, 36) + grid_btn.tooltip_text = "Maßstabsgitter (100-m-Raster) im Viewport ein-/ausblenden. Über sichtbarem Terrain ist es standardmäßig aus." + grid_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_toggled", pressed)) + add_child(grid_btn) + + contours_btn = CheckButton.new() + contours_btn.text = "≈ Höhenlinien fixieren" + contours_btn.custom_minimum_size = UIScale.v2(0, 36) + contours_btn.tooltip_text = "Höhenlinien (1 m, alle 5 m kräftiger) FIXIERT an/aus. Ohne Fixierung erscheinen sie nur im Terrain-Werkzeug (F1). Taste L schaltet ebenfalls. Bei flachem Terrain (< 2 m) sind sie automatisch aus." + contours_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_contours_toggled", pressed)) + add_child(contours_btn) + + add_child(_field_label("UI-Größe")) + size_menu = OptionButton.new() + for i in UIScale.step_count(): + size_menu.add_item(UIScale.step_label(i), i) + size_menu.custom_minimum_size = UIScale.v2(0, 36) + size_menu.tooltip_text = "Größe der Oberfläche: Klein (wie bisher), Mittel (1,25-fach), Groß (1,5-fach). Wirkt sofort und bleibt gespeichert." + size_menu.item_selected.connect(func(index: int) -> void: main.call("_on_ui_size_selected", index)) + add_child(size_menu) + + add_child(_separator()) + add_child(_header_label("Bauen & Platzieren")) + raster_snap_check = CheckButton.new() + raster_snap_check.text = "Raster-Snap (1 m)" + raster_snap_check.custom_minimum_size = UIScale.v2(0, 36) + raster_snap_check.tooltip_text = "Neue und verschobene Objekte rasten auf ein 1-Meter-Raster ein (Strg gedrückt halten geht immer)" + raster_snap_check.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_snap_changed", pressed)) + add_child(raster_snap_check) + + add_child(_field_label("Gizmo-Schrittweite (m)")) + gizmo_step_box = SpinBox.new() + gizmo_step_box.min_value = 0.01 + gizmo_step_box.max_value = 10.0 + gizmo_step_box.step = 0.01 + gizmo_step_box.value = main.gizmo_step_m + gizmo_step_box.custom_minimum_size = UIScale.v2(0, 36) + gizmo_step_box.tooltip_text = "Wie weit bewegt sich das Objekt am Achsen-Gizmo pro Schritt (in Metern). Mausrad WÄHREND des Ziehens wechselt zwischen fein (0.1) / normal (0.5) / grob (2.0)." + gizmo_step_box.value_changed.connect(func(v: float) -> void: + main.gizmo_step_m = v + if main.gizmo != null and is_instance_valid(main.gizmo): + main.gizmo.set_step_m(v)) + add_child(gizmo_step_box) + + add_child(_separator()) + add_child(_header_label("Symmetrie & Prüfen")) + add_child(_field_label("Symmetrie-Modus")) + sym_menu = OptionButton.new() + for i in SymmetryCore.MODE_LABELS.size(): + sym_menu.add_item("Symmetrie: %s" % SymmetryCore.MODE_LABELS[i], i) + sym_menu.custom_minimum_size = UIScale.v2(0, 36) + sym_menu.tooltip_text = "Gespiegeltes Bauen: Änderungen werden automatisch auf die andere Kartenseite übertragen. Aus / Punkt (180° um die Kartenmitte) / Achse X / Achse Z. Standard: Punkt." + sym_menu.item_selected.connect(func(index: int) -> void: main.call("_on_sym_mode_selected", index)) + add_child(sym_menu) + + check_btn = Button.new() + check_btn.text = "✅ Karte prüfen" + check_btn.custom_minimum_size = UIScale.v2(0, 40) + check_btn.tooltip_text = "Prüft, ob die Map bereit für den Export ist — sagt VORHER, was fehlt" + check_btn.pressed.connect(func() -> void: main.call("_on_check_map")) + add_child(check_btn) + + add_child(_separator()) + add_child(_header_label("Testlauf-Einstellungen")) + + add_child(_field_label("Maus-Empfindlichkeit (0,1 – 5,0)")) + sens_box = SpinBox.new() + sens_box.min_value = PlaytestSettings.MIN_SENS + sens_box.max_value = PlaytestSettings.MAX_SENS + sens_box.step = 0.1 + sens_box.value = PlaytestSettings.load_sens() + sens_box.custom_minimum_size = UIScale.v2(0, 36) + sens_box.tooltip_text = "Multiplikator auf die Blick-Geschwindigkeit im Testlauf. 1,0 = Standard; kleine Werte = feinfühlig, große Werte = schnell." + sens_box.value_changed.connect(_on_sens_changed) + add_child(sens_box) + + add_child(_field_label("Perspektive im Testlauf")) + perspektive_box = OptionButton.new() + perspektive_box.add_item("Erste Person (Standard)") + perspektive_box.add_item("Dritte Person (Bot-Avatar)") + perspektive_box.selected = 1 if PlaytestSettings.load_third() else 0 + perspektive_box.custom_minimum_size = UIScale.v2(0, 36) + perspektive_box.tooltip_text = "Dritte Person zeigt denselben Avatar wie die Bots im Spiel (Taste V schaltet im Testlauf ebenfalls). Dritte Person braucht die Avatar-Modelle aus dem Client-Klon." + perspektive_box.item_selected.connect(_on_perspektive_changed) + add_child(perspektive_box) + + add_child(_separator()) + + +func _on_sens_changed(value: float) -> void: + PlaytestSettings.save_sens(value) + var pt: Variant = main.get("playtest") + if pt != null and pt.has_method("apply_sensitivity"): + pt.call("apply_sensitivity", value) + + +func _on_perspektive_changed(index: int) -> void: + var dritte := index == 1 + PlaytestSettings.save_third(dritte) + # Live anwenden, falls der Testlauf gerade läuft. Ohne Avatar-Modelle + # bleibt First Person aktiv (set_perspektive lehnt Third ab) — der + # Klartext-Grund steht beim Start/V-Druck im HUD. + var pt: Variant = main.get("playtest") + if pt == null: + return + var body: Variant = pt.get("body") + if body != null: + body.call("set_perspektive", dritte) + + +## Schalter aus dem Editor-Zustand nachziehen (beim Öffnen des Fensters). +func refresh() -> void: + if main == null: + return + grid_btn.set_pressed_no_signal(bool(main.get("_grid_on"))) + contours_btn.set_pressed_no_signal(bool(main.get("_contours_on"))) + raster_snap_check.set_pressed_no_signal(bool(main.get("grid_snap"))) + gizmo_step_box.set_value_no_signal(float(main.gizmo_step_m)) + size_menu.select(UIScale.step_index()) + sym_menu.select(int(main.get("sym_mode"))) diff --git a/ui/panels/settings_panel.gd.uid b/ui/panels/settings_panel.gd.uid new file mode 100644 index 0000000..342df31 --- /dev/null +++ b/ui/panels/settings_panel.gd.uid @@ -0,0 +1 @@ +uid://bp0ii5exucqcy diff --git a/ui/panels/status_bar.gd b/ui/panels/status_bar.gd new file mode 100644 index 0000000..cb97547 --- /dev/null +++ b/ui/panels/status_bar.gd @@ -0,0 +1,81 @@ +extends HBoxContainer +## Statusleiste (aus main.gd ausgezogen, Karte t_eaccbfed U1 — Logik +## unverändert): Hilfs-Meldungen + permanente Werkzeug-Anzeige + +## Symmetrie-Anzeige + Objekt-/Terrain-Zähler + FPS-Timer. + +var main # core/ui/main.gd +var status_label: Label +var tool_label: Label # permanenter Werkzeugmodus + Flug-Hint +var sym_label: Label +var stats_objects: Label +var stats_terrain: Label + + +static func spec() -> Dictionary: + return {"titel": "Statuszeile", "gruppe": "status", "order": 0} + + +func setup(p_main) -> void: + main = p_main + # Eine Leiste ist EINE Zeile (VBox-Erbe stapelte alle Labels senkrecht → + # riesiger Balken, Befund 25.10.). + size_flags_vertical = Control.SIZE_SHRINK_END + size_flags_horizontal = Control.SIZE_EXPAND_FILL + + +func theme_color(cname: String) -> Color: + if main != null and main.theme != null and main.theme.has_color(cname, "Editor"): + return main.theme.get_color(cname, "Editor") + return get_theme_color(cname, "Editor") + + +func build() -> void: + add_theme_constant_override("separation", UIScale.px(16)) + status_label = Label.new() + status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + status_label.clip_text = true # langer Text darf das Layout NICHT sprengen + add_child(status_label) + + # Karte 3/3 (t_11b5c25b): PERMANENTE Werkzeug-Anzeige — Sly sieht immer, + # in welchem Werkzeug er ist, plus Hint für den Flugmodus (Umschalt+F). + tool_label = Label.new() + # U7: Werkzeug-Farbe über Theme (Editor/colors/accent). + tool_label.add_theme_color_override("font_color", + theme_color("accent")) + # Karte t_297221b5: Statuszeilen-Labels egalisiert (min-size-Druck bei 1.5×) + tool_label.clip_text = true + tool_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + add_child(tool_label) + + # Symmetrie-Anzeige (Karte t_f5935023, M1): Sly sieht in der Statuszeile + # immer, ob/wie gespiegelt wird (Standard: Punkt). + sym_label = Label.new() + # U7: Symmetrie-Farbe über Theme (Editor/colors/accent). + sym_label.add_theme_color_override("font_color", + theme_color("accent")) + sym_label.clip_text = true + sym_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + add_child(sym_label) + + stats_objects = Label.new() + # U7: Nebentext-Farbe über Theme (Editor/colors/field). + stats_objects.add_theme_color_override("font_color", + theme_color("field")) + stats_objects.clip_text = true + stats_objects.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + add_child(stats_objects) + + # Terrain-Anteil + Editor-FPS (Perf-Zähler, Karte t_9aba04f1) + stats_terrain = Label.new() + # U7: Nebentext-Farbe über Theme (Editor/colors/field). + stats_terrain.add_theme_color_override("font_color", + theme_color("field")) + stats_terrain.clip_text = true + stats_terrain.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + add_child(stats_terrain) + + var fps_timer := Timer.new() + fps_timer.wait_time = 0.5 + fps_timer.autostart = true + fps_timer.timeout.connect(main.call.bind("_refresh_stats")) + add_child(fps_timer) diff --git a/ui/panels/status_bar.gd.uid b/ui/panels/status_bar.gd.uid new file mode 100644 index 0000000..afaedfb --- /dev/null +++ b/ui/panels/status_bar.gd.uid @@ -0,0 +1 @@ +uid://dddgvtqqr3lau diff --git a/ui/panels/symmetry_panel.gd b/ui/panels/symmetry_panel.gd new file mode 100644 index 0000000..7d81ca4 --- /dev/null +++ b/ui/panels/symmetry_panel.gd @@ -0,0 +1,237 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Symmetrie-Panel (M5, Karte t_60014669 — Spec /opt/data/scopedvr/ +## KARTEN_MIRROR_vc27.md): Ein-Klick „Karte symmetrisch machen“ + Symmetrie- +## Check mit Klartext-Bericht. Erlaubnis-Regel des Operators (20.10.): +## main.gd NICHT anfassen — dieses Modul meldet sich über die Panel-Registry +## an (registry.gd, Eintrag) und greift wie alle Module über `main` auf +## Dokument/History zu. Die Spiegel-Arbeit lebt KOMPLETT in +## core/editor/symmetry_ops.gd (statisch, headless testbar). +## +## Ablauf „Karte symmetrisch machen“: +## 1. Symmetrie-Modus AUS → Hinweis, nichts passiert (Abnahme „Modus AUS +## ändert nichts“). +## 2. Analyse: nichts zu tun → Klartext „schon symmetrisch“, OHNE Undo. +## 3. Widersprüche? → Dialog nennt die ZAHL und lässt die Quell-Hälfte +## wählen (Sly entscheidet, bevor irgendwas überschrieben wird). +## 4. Anwenden = EIN Undo-Schritt (ActionHistory snapshotet das Dokument). + +const SymmetryOps := preload("res://core/editor/symmetry_ops.gd") + +var mode_label: Label +var half_label: Label +var half_option: OptionButton +var apply_btn: Button +var check_btn: Button +var report_label: Label +var conflict_dialog: ConfirmationDialog + +var _pending_high := false # Quell-Wahl des laufenden Widerspruchs-Dialogs + + +static func spec() -> Dictionary: + return {"titel": "Symmetrie", "gruppe": "map", "order": 30} + + +func panel_title() -> String: + return "Symmetrie" + + +func build() -> void: + add_child(_header_label("Symmetrie")) + add_child(_muted_label("Macht die ganze Karte symmetrisch: ergänzt die andere Seite, löscht nichts.")) + + mode_label = _field_label(_mode_text()) + add_child(mode_label) + + half_label = _field_label("Quell-Hälfte:") + add_child(half_label) + half_option = OptionButton.new() + half_option.custom_minimum_size = UIScale.v2(0, 36) + # U7: Klartext-Tooltip — welche Seite als Vorlage dient. + half_option.tooltip_text = "Welche Hälfte die Vorlage ist (die andere wird angepasst)" + half_option.item_selected.connect(_on_half_selected) + add_child(half_option) + _refresh_half_option() + + apply_btn = Button.new() + apply_btn.text = "🗺️ Karte symmetrisch machen" + apply_btn.custom_minimum_size = UIScale.v2(0, 48) + apply_btn.tooltip_text = "Ergänzt die andere Kartenseite (EIN Strg+Z macht alles rückgängig)" + apply_btn.pressed.connect(_on_apply_pressed) + add_child(apply_btn) + + check_btn = Button.new() + check_btn.text = "🔍 Symmetrie prüfen" + check_btn.custom_minimum_size = UIScale.v2(0, 40) + check_btn.tooltip_text = "Ehrlicher Bericht: Wo weicht die Karte von der Spiegelung ab?" + check_btn.pressed.connect(_on_check_pressed) + add_child(check_btn) + + report_label = _muted_label("") + report_label.custom_minimum_size = UIScale.v2(0, 96) + add_child(report_label) + + + conflict_dialog = ConfirmationDialog.new() + conflict_dialog.title = "Widersprüche — welche Seite gewinnt?" + conflict_dialog.dialog_close_on_escape = true + conflict_dialog.get_ok_button().text = "Anwenden" + conflict_dialog.confirmed.connect(_on_conflict_confirmed) + conflict_dialog.add_button("Andere Hälfte als Quelle", true, "other_half") + conflict_dialog.custom_action.connect(_on_conflict_other) + add_child(conflict_dialog) + + +# ------------------------------------------------------------- Zustands-Helfer + +func _current_mode() -> int: + ## Defensiv wie spawn_tool._sym_mode(): Mock-Mains ohne Feld → OFF. + var v: Variant = main.get("sym_mode") if main != null else null + return SymmetryCore.Mode.OFF if v == null else int(v) + + +func _mode_text() -> String: + var mode := _current_mode() + return "Modus: %s" % SymmetryCore.mode_label(mode) + + +func _source_high() -> bool: + ## false = erste Hälfte (links/vorn), true = zweite (rechts/hinten). + return half_option.selected == 1 + + +func _refresh_half_option() -> void: + var labels: Array = SymmetryOps.side_labels(_current_mode()) + half_option.clear() + half_option.add_item("Quelle: %s" % str(labels[0])) + half_option.add_item("Quelle: %s" % str(labels[1])) + half_option.selected = 0 + + +## Vom Einstellungen-Fenster gerufen, wenn der Modus wechselt. +func refresh_mode() -> void: + if mode_label == null or half_option == null: + return + _refresh_half_option() + _refresh_mode_line() + + +func _on_half_selected(_index: int) -> void: + _refresh_mode_line() + + +func _refresh_mode_line() -> void: + mode_label.text = _mode_text() + + +# ------------------------------------------------------------ Karte spiegeln -- + +func _on_apply_pressed() -> void: + var mode := _current_mode() + if mode == SymmetryCore.Mode.OFF: + _say("Der Symmetrie-Modus ist Aus — erst in den Einstellungen (⚙) Punkt oder eine Achse wählen.") + return + if main.doc.is_empty(): + _say("Keine Karte offen — erst eine Map anlegen oder laden.", true) + return + _refresh_mode_line() + _refresh_half_option() + var an := SymmetryOps.analyze(main.doc, mode) + if int(an["total_changes"]) == 0: + _say("Nichts zu tun — die Karte ist schon symmetrisch ✓") + return + if int(an["conflicts"]) == 0: + _apply(_source_high()) + return + _pending_high = _source_high() + conflict_dialog.dialog_text = _conflict_text(an) + conflict_dialog.get_ok_button().text = "Anwenden — %s gewinnt" % _half_name(_pending_high) + conflict_dialog.popup_centered(Vector2i(520, 260)) + + +## Klartext für den Widerspruchs-Dialog: ZAHL erst nennen, DANN anwenden +## (Spec: „Nichts wird gelöscht, ohne dass die Zahl vorher genannt wurde“). +func _conflict_text(an: Dictionary) -> String: + var parts: Array = [] + var obj_n := int(an["objects"]["diff"]) + var spawn_n := int(an["spawns"]["diff"]) + var boden_n := (an["terrain_cells"] as Array).size() + (an.get("material_cells", []) as Array).size() + if obj_n > 0: + parts.append("%d Objekt-Paare unterscheiden sich" % obj_n) + if spawn_n > 0: + parts.append("%d Spawn-Punkt-Paare unterscheiden sich" % spawn_n) + if boden_n > 0: + parts.append("%d Bodenstellen sind unterschiedlich" % boden_n) + var adds := int(an["adds"]) + var text := ", ".join(PackedStringArray(parts)) + ".\n\n" + text += "Beim Anwenden gewinnt die gewählte Quell-Hälfte (die andere Seite wird angepasst).\n" + if adds > 0: + text += "Zusätzlich werden %d fehlende Zwillinge ergänzt.\n" % adds + text += "Es wird NICHTS gelöscht. Ein Strg+Z macht alles rückgängig." + return text + + +func _half_name(high: bool) -> String: + var labels: Array = SymmetryOps.side_labels(_current_mode()) + return str(labels[1]) if high else str(labels[0]) + + +func _on_conflict_confirmed() -> void: + # OK = die im Auswahlfeld gewählte Quelle behalten (Dialog-Text nennt sie). + _apply(_pending_high) + + +func _on_conflict_other(_action: String) -> void: + conflict_dialog.hide() + _pending_high = not _pending_high + _apply(_pending_high) + + +## Snapshot über ALLE Blöcke; fehlt design_notes (alte Karte), wird es als +## null geseedet — sonst könnte das Undo den neu angelegten Block nicht +## entfernen (U3-Lektion: fehlernde Blöcke explizit als null snapshoten). +func _snapshot_all() -> Dictionary: + var snap: Dictionary = main.history.snapshot(main.doc.keys()) + if not snap.has("design_notes") and not main.doc.has("design_notes"): + snap["design_notes"] = null + return snap + + +func _apply(quelle_hoch: bool) -> void: + var mode := _current_mode() + var before: Dictionary = _snapshot_all() + var res := SymmetryOps.apply(main.doc, mode, quelle_hoch) + main.history.commit("Karte symmetrisch machen (%s → %s)" % [ + _half_name(quelle_hoch), _half_name(not quelle_hoch)], + before, main.history.snapshot(main.doc.keys())) + main.call("_after_structure_change") + var report := str(res["report"]) + report_label.text = report + # U7: Bericht-Farbe über Theme (Editor/colors/status_ok). + report_label.add_theme_color_override("font_color", theme_color("status_ok")) + main.call("_set_status", report.split(".")[0] + ".") + + +# ---------------------------------------------------------- Symmetrie-Check -- + +func _on_check_pressed() -> void: + var mode := _current_mode() + if mode == SymmetryCore.Mode.OFF: + _say("Der Symmetrie-Modus ist Aus — erst in den Einstellungen (⚙) Punkt oder eine Achse wählen, dann prüfen.") + return + _refresh_mode_line() + var rep := SymmetryOps.check(main.doc, mode) + var text := str(rep["text"]) + report_label.text = text + # U7: Bericht-Farben über Theme (Editor/colors/status_ok / warning). + report_label.add_theme_color_override("font_color", + theme_color("status_ok") if bool(rep["ok"]) else theme_color("warning")) + main.call("_set_status", text) + + +func _say(text: String, is_error := false) -> void: + report_label.text = text + # U7: Bericht-Farben über Theme (Editor/colors/warning / status_ok). + report_label.add_theme_color_override("font_color", + theme_color("warning") if is_error else theme_color("status_ok")) + main.call("_set_status", text, is_error) diff --git a/ui/panels/symmetry_panel.gd.uid b/ui/panels/symmetry_panel.gd.uid new file mode 100644 index 0000000..1c311e5 --- /dev/null +++ b/ui/panels/symmetry_panel.gd.uid @@ -0,0 +1 @@ +uid://ceu6y127v6lxc diff --git a/ui/panels/terrain_panel.gd b/ui/panels/terrain_panel.gd new file mode 100644 index 0000000..7841dd1 --- /dev/null +++ b/ui/panels/terrain_panel.gd @@ -0,0 +1,282 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Terrain-Werkzeug-Panel (aus main.gd ausgezogen, Karte t_eaccbfed U1 — +## Logik unverändert, Karte t_9aba04f1 + t_603ec1a8): großer Modus-Button + +## Pinsel-Modi, Material-Palette, Größe/Stärke-Regler. Werkzeug-Ein/Aus bleibt +## im Koordinator (main.gd _enter/_leave terrain), das Panel hält nur die +## Widgets und die Regler-Verdrahtung. + +var terrain_edit_btn: Button +var terrain_panel: VBoxContainer +var terrain_mode_raise: Button +var terrain_mode_lower: Button +var terrain_mode_smooth: Button +var terrain_mode_paint: Button +var terrain_mode_flatten: Button +var terrain_mode_ramp: Button +var terrain_falloff_label: Label +var terrain_falloff_slider: HSlider +var terrain_palette: VBoxContainer +var terrain_mat_buttons: Array = [] # Gras/Sand/Fels (ButtonGroup) +var terrain_size_btn_small: Button +var terrain_size_btn_medium: Button +var terrain_size_btn_large: Button +var terrain_radius_slider: HSlider +var terrain_radius_label: Label +var terrain_strength_slider: HSlider +var terrain_strength_label: Label +var terrain_paint_hint: Label # Malen: Hinweis auf die Raster-Feinheit (24.10.) +var terrain_weich_label: Label # Malen: Kante hart ↔ weich (25.10.) +var terrain_weich_slider: HSlider + + +static func spec() -> Dictionary: + return {"titel": "Terrain", "gruppe": "rechts", "order": 40} + + +func build() -> void: + terrain_edit_btn = Button.new() + terrain_edit_btn.text = "⛰ Terrain formen" + terrain_edit_btn.custom_minimum_size = UIScale.v2(0, 48) + terrain_edit_btn.toggle_mode = true + terrain_edit_btn.tooltip_text = "Boden formen: heben, senken, glätten, einebnen, Rampe (F1)" + terrain_edit_btn.pressed.connect(_on_terrain_btn_pressed) + add_child(terrain_edit_btn) + + terrain_panel = VBoxContainer.new() + terrain_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster + add_child(terrain_panel) + + terrain_panel.add_child(_field_label("Pinsel:")) + var mode_row := GridContainer.new() + mode_row.columns = 3 + mode_row.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + mode_row.add_theme_constant_override("v_separation", UIScale.px(8)) + terrain_panel.add_child(mode_row) + var mode_group := ButtonGroup.new() + terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group, + "Pinsel-Modus Heben: Boden unter dem Pinsel anheben (Taste 1)") + terrain_mode_lower = _terrain_mode_button("Senken ⬇", mode_group, + "Pinsel-Modus Senken: Boden unter dem Pinsel absenken (Taste 2)") + terrain_mode_smooth = _terrain_mode_button("Glätten ≈", mode_group, + "Pinsel-Modus Glätten: Höhenunterschiede verrutschen (Taste 3)") + terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group, + "Pinsel-Modus Malen: Material auf den Boden malen (Taste 4)") + terrain_mode_flatten = _terrain_mode_button("Einebnen ▬", mode_group, + "Pinsel-Modus Einebnen: zieht den Boden auf die Höhe, die beim Strichstart unter dem Cursor war") + terrain_mode_ramp = _terrain_mode_button("Rampe ◢", mode_group, + "Pinsel-Modus Rampe: Klick = Start, ziehen und loslassen = Ende — Höhe läuft gleichmäßig dazwischen (Breite = Pinselgröße)") + terrain_mode_raise.button_pressed = true + terrain_mode_flatten.toggled.connect(_on_terrain_mode_changed) + terrain_mode_ramp.toggled.connect(_on_terrain_mode_changed) + terrain_mode_raise.toggled.connect(_on_terrain_mode_changed) + terrain_mode_lower.toggled.connect(_on_terrain_mode_changed) + terrain_mode_smooth.toggled.connect(_on_terrain_mode_changed) + terrain_mode_paint.toggled.connect(_on_terrain_mode_changed) + mode_row.add_child(terrain_mode_raise) + mode_row.add_child(terrain_mode_lower) + mode_row.add_child(terrain_mode_smooth) + mode_row.add_child(terrain_mode_flatten) + mode_row.add_child(terrain_mode_ramp) + # Malen hat seit 25.10. einen eigenen Tab (malen_panel.gd); der Modus-Knopf + # bleibt unsichtbar in der ButtonGroup (Taste 4, Malen-Tab-Knopf). + terrain_mode_paint.visible = false + terrain_panel.add_child(terrain_mode_paint) + + # Pinselgrößen-Presets (Karte t_603ec1a8): drei große Buttons + freier Slider. + terrain_panel.add_child(_field_label("Pinselgröße:")) + var size_row := HBoxContainer.new() + size_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster + terrain_panel.add_child(size_row) + var size_group := ButtonGroup.new() + terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0) + terrain_size_btn_medium = _terrain_size_button("Mittel\n(12 m)", size_group, 12.0) + terrain_size_btn_large = _terrain_size_button("Groß\n(32 m)", size_group, 32.0) + # Kein Signal: Der Handler greift auf terrain_radius_slider zu, der erst + # unten erzeugt wird — beim Panel-Bau wäre das ein Crash (GUI-Start-Bug + # 15.10.). Der Slider startet unten passend mit 12 m (= Mittel). + terrain_size_btn_medium.set_pressed_no_signal(true) + size_row.add_child(terrain_size_btn_small) + size_row.add_child(terrain_size_btn_medium) + size_row.add_child(terrain_size_btn_large) + + terrain_radius_label = _field_label("Pinsel-Größe: 12 m") + terrain_panel.add_child(terrain_radius_label) + terrain_radius_slider = HSlider.new() + terrain_radius_slider.min_value = 1.0 + terrain_radius_slider.max_value = 64.0 + terrain_radius_slider.step = 1.0 + terrain_radius_slider.value = 12.0 + terrain_radius_slider.custom_minimum_size = UIScale.v2(0, 32) + terrain_radius_slider.value_changed.connect(_on_terrain_radius_changed) + terrain_panel.add_child(terrain_radius_slider) + + terrain_strength_label = _field_label("Stärke: 8 m pro Sekunde") + terrain_panel.add_child(terrain_strength_label) + terrain_strength_slider = HSlider.new() + terrain_strength_slider.min_value = 1.0 + terrain_strength_slider.max_value = 20.0 + terrain_strength_slider.step = 0.5 + terrain_strength_slider.value = 8.0 + terrain_strength_slider.custom_minimum_size = UIScale.v2(0, 32) + terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed) + terrain_panel.add_child(terrain_strength_slider) + + # Pinsel-Kante (Falloff, Karte 25.10.): ein Regler hart ↔ weich für alle + # Höhen-Pinsel (Malen hat seinen eigenen Kanten-Regler weiter unten). + terrain_falloff_label = _field_label("Kante: 50 % weich") + terrain_panel.add_child(terrain_falloff_label) + terrain_falloff_slider = HSlider.new() + terrain_falloff_slider.min_value = 0.0 + terrain_falloff_slider.max_value = 100.0 + terrain_falloff_slider.step = 5.0 + terrain_falloff_slider.value = 50.0 + terrain_falloff_slider.custom_minimum_size = UIScale.v2(0, 32) + terrain_falloff_slider.tooltip_text = "Links = harte Kante (breiter Kern), rechts = weich auslaufend" + terrain_falloff_slider.value_changed.connect(_on_terrain_falloff_changed) + terrain_panel.add_child(terrain_falloff_slider) + + + +func _on_terrain_btn_pressed() -> void: + if terrain_edit_btn.button_pressed: + if terrain_mode_paint.button_pressed: + terrain_mode_raise.button_pressed = true # Malen hat seinen eigenen Tab + main.call("_set_tool_mode", "terrain") + refresh_panels() + else: + main.call("_set_tool_mode", "objects") + + +func _terrain_mode_button(text: String, group: ButtonGroup, hinweis: String) -> Button: + var btn := Button.new() + btn.text = text + btn.toggle_mode = true + btn.button_group = group + btn.custom_minimum_size = UIScale.v2(0, 40) + btn.tooltip_text = hinweis # U7: Werkzeugname + Taste im Tooltip + return btn + + +func _selected_terrain_mode() -> int: + if terrain_mode_lower.button_pressed: + return TerrainField.Mode.LOWER + if terrain_mode_smooth.button_pressed: + return TerrainField.Mode.SMOOTH + if terrain_mode_paint.button_pressed: + return TerrainField.Mode.PAINT + if terrain_mode_flatten != null and terrain_mode_flatten.button_pressed: + return TerrainField.Mode.FLATTEN + if terrain_mode_ramp != null and terrain_mode_ramp.button_pressed: + return TerrainField.Mode.RAMP + return TerrainField.Mode.RAISE + + +## Mode-Buttons (ButtonGroup) → Pinsel-Modus; Palette nur im Malen-Modus. +func _on_terrain_mode_changed(_pressed: bool) -> void: + var paint := _selected_terrain_mode() == TerrainField.Mode.PAINT + if terrain_palette != null: + terrain_palette.visible = paint + if terrain_paint_hint != null: + terrain_paint_hint.text = paint_hint_text(main.doc.get("terrain", {}) if main.doc is Dictionary else {}) + refresh_panels() + if main.terrain_tool != null: + main.terrain_tool.call("set_brush_mode", _selected_terrain_mode()) + + +## Welche Bedienflächen sind sichtbar (Terrain-Tab bzw. Malen-Tab), welcher +## Start-Knopf steht gedrückt — Terrain und Malen teilen das Pinsel-Werkzeug. +func refresh_panels() -> void: + if terrain_panel == null: + return + var aktiv: bool = str(main.get("tool_mode")) == "terrain" + var paint := _selected_terrain_mode() == TerrainField.Mode.PAINT + terrain_edit_btn.set_pressed_no_signal(aktiv and not paint) + var malen = main.get("malen_mod") + if malen != null: + malen.malen_edit_btn.set_pressed_no_signal(aktiv and paint) + if aktiv: + _show_own_tab(malen if paint else self) + + +func _show_own_tab(tab: Control) -> void: + var tabs = main.get("ui_right_panel") + if tabs is TabContainer and tab != null and tab.get_parent() == tabs: + (tabs as TabContainer).current_tab = tab.get_index() + + +## Hinweistext Malen-Modus (pure, testbar): feinste Mal-Einheit = Abstand +## zweier Rasterpunkte der Karte (size / (resolution − 1)). +static func paint_hint_text(terrain: Dictionary) -> String: + var res := SvmapIO.material_resolution(terrain) # feines Material-Raster + var size := maxf(float(terrain.get("size_x_m", 0.0)), float(terrain.get("size_z_m", 0.0))) + if res < 2 or size <= 0.0: + return "Malen setzt das Material zellenweise — Stärke gibt es hier nicht." + var m := size / float(res - 1) + var m_text := str(int(roundf(m))) if absf(m - roundf(m)) < 0.05 \ + else String.num(m, 1).replace(".", ",") + return "Malen setzt das Material zellenweise: feinste Einheit auf dieser Karte ≈ %s m. Stärke gibt es hier nicht." % m_text + + +## Pinselgrößen-Presets (Karte t_603ec1a8): setzen den Radius-Slider +## (der Label-Text + Ring-Optik hängen am value_changed-Signal). +func _terrain_size_button(text: String, group: ButtonGroup, radius_m: float) -> Button: + var btn := Button.new() + btn.text = text + btn.toggle_mode = true + btn.button_group = group + btn.custom_minimum_size = UIScale.v2(0, 48) + btn.tooltip_text = "Pinselradius %d m" % int(radius_m) + btn.toggled.connect(_on_terrain_size_preset.bind(radius_m)) + return btn + + +func _on_terrain_size_preset(pressed: bool, radius_m: float) -> void: + # Null-Check als Schutznetz: beim Panel-Bau existiert der Slider noch + # nicht (Programmatic-Pressed darf den Handler nie früh treffen). + if pressed and terrain_radius_slider != null: + terrain_radius_slider.value = radius_m + + +func _on_terrain_radius_changed(value: float) -> void: + terrain_radius_label.text = "Pinsel-Größe: %d m" % int(value) + var malen = main.get("malen_mod") + if malen != null: + malen.set_radius_silent(value) + if main.terrain_tool != null: + main.terrain_tool.call("set_radius", value) + + +func _on_terrain_falloff_changed(value: float) -> void: + terrain_falloff_label.text = "Kante: %d %% weich" % int(value) if value > 0.0 else "Kante: hart" + if main.terrain_tool != null: + main.terrain_tool.call("set_falloff", value / 100.0) + + +func _on_terrain_strength_changed(value: float) -> void: + terrain_strength_label.text = "Stärke: %.1f m pro Sekunde" % value + if main.terrain_tool != null: + main.terrain_tool.call("set_strength", value) + + +## Terrain-Pinsel-Größe per [ / ] (Karte t_55257792): durch die 3 Presets +## Klein/Mittel/Groß zyklisch — der Preset-Button wird mit gesetzt (set_pressed +## feuert toggled → Slider + Label + Pinssel-Ring wandern mit). +## (Aus main.gd ausgezogen, Logik unverändert; Koordinator ruft weiter.) +func terrain_brush_size_step(up: bool) -> void: + if terrain_radius_slider == null: + return + var next := MoveMath.brush_next_preset(float(terrain_radius_slider.value), up) + var btn: Button = null + match next: + 4.0: + btn = terrain_size_btn_small + 12.0: + btn = terrain_size_btn_medium + 32.0: + btn = terrain_size_btn_large + if btn != null: + btn.set_pressed(true) # feuert _on_terrain_size_preset → Slider/Label + else: + terrain_radius_slider.value = next + main.terrain_tool.call("set_radius", next) + main.call("_set_status", "Pinsel-Größe: %d m (mit [ / ] durch Klein/Mittel/Groß zyklisch)." % int(next)) diff --git a/ui/panels/terrain_panel.gd.uid b/ui/panels/terrain_panel.gd.uid new file mode 100644 index 0000000..fe08e19 --- /dev/null +++ b/ui/panels/terrain_panel.gd.uid @@ -0,0 +1 @@ +uid://qvp3thn60awa diff --git a/ui/panels/test_start_panel.gd b/ui/panels/test_start_panel.gd new file mode 100644 index 0000000..53aa61a --- /dev/null +++ b/ui/panels/test_start_panel.gd @@ -0,0 +1,63 @@ +extends "res://core/ui/panels/editor_panel.gd" +## Test-Start-Bereich (rechte Spalte — Karte t_96e3c4ee / T1b, Wiederanbindung +## durch den Operator am 20.10.). +## +## Aufteilung: der ZUSTAND lebt in main.gd (`test_start_point`) samt den Haken +## `_test_start_apply` / `_test_start_delete` / `_test_start_reload`, weil die +## Prüfsuite tests/test_playtest_spawn.gd (56 checks) genau diese drei Namen auf +## main erwartet — das ist der T1b-Vertrag. Beim U1-Umbau waren sie verschwunden, +## die Suite brach mit „Nonexistent function '_test_start_reload'" ab. +## Dieses Modul zeigt den Zustand nur an (Info-Text + Löschen-Knopf) und meldet +## sich datengetrieben über registry.gd an — main.gd verdrahtet nichts fest. + +var info: Label +var delete_btn: Button +var info_hint: Label + + +static func spec() -> Dictionary: + return {"titel": "Test-Start", "gruppe": "map", "order": 20} + + +func panel_title() -> String: + return "Test-Start (F5)" + + +func build() -> void: + add_child(_header_label("Startpunkt für „▶ Karte testen“")) + info = Label.new() + info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(info) + + delete_btn = Button.new() + delete_btn.text = "🗑 Test-Start löschen" + delete_btn.custom_minimum_size = UIScale.v2(0, 40) + delete_btn.tooltip_text = "Test-Startpunkt entfernen — F5 startet dann wieder am ersten Team-Spawnpunkt." + delete_btn.pressed.connect(func() -> void: main.call("_test_start_delete")) + add_child(delete_btn) + + add_child(_separator()) + info_hint = Label.new() + info_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + info_hint.text = "Leiste „🎯 Test-Start“ oder Taste T, dann in die Karte klicken." + add_child(info_hint) + + # main holt sich das Modul hier ab (main._refresh_test_start_panel) — die + # Prüfsuite liest es als main.test_start_panel. Sichtbar erst, wenn das + # Werkzeug aktiv ist (wie vor dem U1-Umbau). + main.set("test_start_panel", self) + visible = false + refresh_point(main.get("test_start_point")) + + +## Wird von main._refresh_test_start_panel() gerufen. point = Vector3 oder null. +func refresh_point(point: Variant) -> void: + if info == null or delete_btn == null: + return + if point == null: + info.text = "Kein Test-Start gesetzt.\nF5 startet dann am ersten Team-Blau-Spawnpunkt (ohne Spawnpunkte: Kartenmitte)." + delete_btn.disabled = true + else: + var p: Vector3 = point + info.text = "Test-Start: x=%.0f z=%.0f\n(Höhe kommt automatisch vom Terrain)" % [p.x, p.z] + delete_btn.disabled = false diff --git a/ui/panels/test_start_panel.gd.uid b/ui/panels/test_start_panel.gd.uid new file mode 100644 index 0000000..05aaed2 --- /dev/null +++ b/ui/panels/test_start_panel.gd.uid @@ -0,0 +1 @@ +uid://brik7mu7wv0el diff --git a/ui/panels/toolbar.gd b/ui/panels/toolbar.gd new file mode 100644 index 0000000..45050c9 --- /dev/null +++ b/ui/panels/toolbar.gd @@ -0,0 +1,156 @@ +extends HBoxContainer +## Toolbar (aus main.gd ausgezogen, Karte t_eaccbfed U1 — Logik unverändert): +## Menü Datei + Undo/Redo + Gitter/Höhenlinien + Symmetrie + Toolbar-Aktionen +## aus der Registry (Karte prüfen, ▶ Karte testen). Neue Aktionen melden sich +## künftig in registry.gd an (U2–U7, T1-F5 bleibt unverändert erreichbar). +## +## NACHBESSERUNG 20.10. (Operator, Sly-Befund „Damit kann man nicht mehr +## arbeiten"): Diese Leiste erbte von EditorPanel (= VBoxContainer) und wurde +## dadurch SELBST senkrecht — jeder Knopf bekam eine eigene, fensterbreite +## Zeile, die Leiste wuchs auf ~11×40 px und drückte das 3D-Fenster auf einen +## Briefmarken-Streifen. Eine Leiste braucht eine WAAGERECHTE Zeile: extends +## HBoxContainer. Die Helfer der Basis (main-Zeiger, Label-Bauer) braucht nur +## die senkrechte Spalte; hier genügt der main-Zeiger. + +var main # core/ui/main.gd (script-erweitert → untypisiert) + + +func setup(p_main) -> void: + main = p_main + # Eine Leiste darf NIE in die Höhe wachsen (sie würde dem 3D-Fenster die + # Fläche wegnehmen): genau eine Knopfzeile, oben ausgerichtet. + size_flags_vertical = Control.SIZE_SHRINK_BEGIN + size_flags_horizontal = Control.SIZE_EXPAND_FILL + + +func panel_title() -> String: + return "Werkzeuge" + +var undo_btn: Button +var redo_btn: Button +var settings_btn: Button # „⚙ Einstellungen“ öffnet das Einstellungen-Fenster +var title_label: Label +var file_label: Label + + +static func spec() -> Dictionary: + return {"titel": "Toolbar", "gruppe": "toolbar", "order": 0} + + +func build() -> void: + add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster (4/8) + + var file_menu := MenuButton.new() + file_menu.text = "Datei" + file_menu.flat = false + file_menu.custom_minimum_size = UIScale.v2(90, 40) + # U7: Klartext-Tooltip am Datei-Menü (alle Map-Aktionen an einem Ort). + file_menu.tooltip_text = "Map-Aktionen: neu, öffnen, speichern, testen, Updates" + var popup := file_menu.get_popup() + popup.add_item("Neue Map…", 0) + popup.add_item("Öffnen…", 1) + popup.add_item("Kartengröße ändern…", 6) + popup.add_separator() + popup.add_item("Speichern (Strg+S)", 2) + popup.add_item("Speichern unter…", 3) + popup.add_separator() + popup.add_item("▶ Karte testen", 5) + popup.add_separator() + popup.add_item("🖐 Hand-Haltung der Waffen…", 8) + popup.add_item("🔄 Updates prüfen", 7) + popup.add_item("Beenden", 4) + # FIX 20.10. (Sly-Befund „Kartengröße ändern tut nichts“): id_pressed sendet + # die Menü-ID. main.call.bind("_on_menu_action") hängt die ID aber ALS + # ARGUMENT an call() → effektiv call(6, "_on_menu_action") → + # „Cannot convert argument 1 from int to StringName“ (map-builder.log + # 20.10.) — das GESAMTE Datei-Menü verpufft, kein Eintrag tut etwas. + # Lambda macht die Reihenfolge (Name zuerst, ID als Argument) explizit, + # Stil wie grid_btn/contours_btn oben. + popup.id_pressed.connect(func(id: int) -> void: main.call("_on_menu_action", id)) + add_child(file_menu) + + undo_btn = Button.new() + undo_btn.text = "↩ Rückgängig" + undo_btn.custom_minimum_size = UIScale.v2(140, 40) + undo_btn.tooltip_text = "Letzte Aktion zurücknehmen (Strg+Z)" + undo_btn.pressed.connect(main.call.bind("_do_undo")) + add_child(undo_btn) + + redo_btn = Button.new() + redo_btn.text = "↪ Wiederholen" + redo_btn.custom_minimum_size = UIScale.v2(150, 40) + redo_btn.tooltip_text = "Zurückgenommene Aktion wiederherstellen (Strg+Y)" + redo_btn.pressed.connect(main.call.bind("_do_redo")) + add_child(redo_btn) + + # Toolbar-Aktionen DATENGETRIEBEN aus der Registry (Operator-Vorgabe + # 20.10.): Karte prüfen, ▶ Karte testen — künftige Karten melden sich + # hier an, ohne main.gd anzufassen. Die Buttons bekommen den Registry-Namen + # als Node-Namen (main kann sie per get_node/find_child ansprechen, z. B. + # der Shake-Effekt beim fehlgeschlagenen Map-Check). + for entry in PanelRegistry.toolbar_actions(): + var btn := Button.new() + btn.text = str(entry["titel"]) + btn.name = str(entry["id"]) + if str(entry["id"]) == "karte_pruefen": + btn.custom_minimum_size = UIScale.v2(170, 40) + btn.tooltip_text = "Prüft, ob die Map bereit für den Export ist — sagen wir VORHER, was fehlt" + else: + btn.custom_minimum_size = UIScale.v2(190, 40) + btn.tooltip_text = "Startet die Map SOFORT als Spiel zum Durchlaufen — eigenes Fenster, der Editor bleibt offen. ESC im Spiel = zurück zum Editor." + btn.pressed.connect(main.call.bind(str(entry["action"]))) + # Test-Start ist ein Umschalter: main liest/setzt seinen Zustand + # (Prüfsuite test_playtest_spawn.gd erwartet main.test_start_btn). + if str(entry["id"]) == "teststart": + btn.toggle_mode = true + main.set("test_start_btn", btn) + add_child(btn) + + # Einstellungen (25.10.): Gitter, Höhenlinien, Symmetrie-Modus, UI-Größe, + # Raster-Snap … stehen jetzt in einem eigenen Fenster. + settings_btn = Button.new() + settings_btn.text = "⚙ Einstellungen" + settings_btn.name = "einstellungen_knopf" + settings_btn.custom_minimum_size = UIScale.v2(170, 40) + settings_btn.tooltip_text = "Gitter, Höhenlinien, Symmetrie, UI-Größe, Raster-Snap und Testlauf-Einstellungen" + settings_btn.pressed.connect(func() -> void: main.call("_open_settings")) + add_child(settings_btn) + + var help_btn := Button.new() + help_btn.text = "❓ Hilfe" + help_btn.name = "hilfe" + help_btn.custom_minimum_size = UIScale.v2(110, 40) + help_btn.tooltip_text = "Werkzeuge, Steuerung und Tastenkürzel (H)" + help_btn.pressed.connect(func() -> void: main.call("_toggle_help_overlay")) + add_child(help_btn) + + title_label = Label.new() + title_label.text = " %s" % main.APP_TITLE + title_label.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + # Karte t_297221b5: bei großer Stufe darf der Titel EGALISIEREN statt die + # Top-Bar aus dem Fenster zu drücken (Mindestbreite der Schrift skaliert mit). + # Mindestbreite, damit er nicht KOMPLETT verschwindet (Ellipsis macht "Scop…"). + # Expand: bei Platz bekommt er seine VOLLSTÄNDIGE natürliche Breite (wie + # Vorher bei „Klein“), erst bei knappem Fenster egalisiert er mit "…". + title_label.custom_minimum_size = UIScale.v2(180, 0) + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + title_label.clip_text = true + add_child(title_label) + + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + add_child(spacer) + + file_label = Label.new() + file_label.text = "" + # U7: Farbe über Theme (Editor/colors/title) statt modulate-Wert im Code. + file_label.add_theme_color_override("font_color", + main.theme.get_color("title", "Editor")) + # Karte t_297221b5: Map-Pfad darf bei großer Stufe egalisiert werden + # (Voller Name im Tooltip, sonst drückt die Zeile die Top-Bar aus dem Fenster). + # Expand: bekommt zusammen mit dem Spacer seinen Anteil, egalisiert mit "…". + file_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + file_label.clip_text = true + file_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + add_child(file_label) diff --git a/ui/panels/toolbar.gd.uid b/ui/panels/toolbar.gd.uid new file mode 100644 index 0000000..9f1da55 --- /dev/null +++ b/ui/panels/toolbar.gd.uid @@ -0,0 +1 @@ +uid://po0n02xkbp2e diff --git a/ui/resize_map_dialog.gd b/ui/resize_map_dialog.gd new file mode 100644 index 0000000..101707f --- /dev/null +++ b/ui/resize_map_dialog.gd @@ -0,0 +1,275 @@ +extends ConfirmationDialog +## „Kartengröße ändern“-Dialog (R3, Karte t_c863b93b — PLAN.md „Map-Größe +## ändern“): aktuelle Größe anzeigen, neue X/Z in Metern eingeben (Preset- +## Buttons + Freifeld), Live-Vorschau aus SvmapDocument.resize_preview (R2). + +const UIScale := preload("res://core/ui/ui_scale.gd") # Karte t_297221b5 +## +## Stil wie new_map_dialog.gd: alles per Code, Deutsch, Klartext-Warnungen. +## Der Dialog mutiert NICHTS — das Anwenden macht main.gd (_on_resize_confirmed) +## als EINE Undo-Einheit über resize_apply + ActionHistory-Snapshot. + +var size_x_edit: SpinBox +var size_z_edit: SpinBox +var drop_check: CheckBox +var info_label: Label # aktuelle Größe + Auflösung +var preview_label: Label # Live-Vorschau (Verlust-Liste / Warnband) +var _scroll: ScrollContainer +var _vbox: VBoxContainer + +var _old_sx := 0.0 +var _old_sz := 0.0 +var _old_res := 0 +var _doc: Dictionary = {} + +const _PRESET_SIZES := [256, 512, 1024, 2048] + + +func _init() -> void: + title = "Kartengröße ändern" + ok_button_text = "Größe ändern" + cancel_button_text = "Abbrechen" + min_size = UIScale.v2i(520, 0) + unresizable = true # Der Dialog wächst selbst per Inhalt; kein Hand-Resize + + # FIX 20.10.b: Inhalt in einen ScrollContainer — wächst die Live-Vorschau + # NACH dem Öffnen (z. B. Preset 256 → Warnband), kann die Fensterhöhe + # gedeckelt werden, OHNE dass Inhalt oder OK/Abbrechen verloren gehen. + # Start-Mindesthöhe ~ kompletter Normalinhalt (kein Kollaps beim Popup); + # _fit_after_layout setzt sie dynamisch auf Inhalt vs. sichtbarer Platz. + var scroll := ScrollContainer.new() + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + scroll.custom_minimum_size = UIScale.v2(500, 430) + add_child(scroll) + scroll.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + _scroll = scroll + + var vbox := VBoxContainer.new() + vbox.add_theme_constant_override("separation", UIScale.px(12)) + vbox.custom_minimum_size = UIScale.v2(500, 0) + vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll.add_child(vbox) + _vbox = vbox + + info_label = _make_label("") + vbox.add_child(info_label) + + var preset_h := HBoxContainer.new() + preset_h.add_theme_constant_override("separation", UIScale.px(8)) + for size in _PRESET_SIZES: + var btn := Button.new() + btn.text = "%d m" % size + btn.custom_minimum_size = UIScale.v2(0, 40) + # U7: Klartext-Tooltips (Karte t_2336c58e). + btn.tooltip_text = "Preset: Karte quadratisch %d × %d m" % [size, size] + btn.pressed.connect(_on_preset.bind(float(size))) + preset_h.add_child(btn) + vbox.add_child(preset_h) + + vbox.add_child(_make_label("Neue Breite (X) in Metern:")) + size_x_edit = SpinBox.new() + _fill_spin(size_x_edit) + vbox.add_child(size_x_edit) + + vbox.add_child(_make_label("Neue Tiefe (Z) in Metern:")) + size_z_edit = SpinBox.new() + _fill_spin(size_z_edit) + vbox.add_child(size_z_edit) + + var hint := _make_label("Erlaubt: 256–4096 m. Die Karte bleibt mittig — das bestehende Terrain\nbehaftet seine Weltposition, die neue Randzone wird flach (Steppe).") + hint.modulate = Color(1, 1, 1, 0.7) + vbox.add_child(hint) + + preview_label = _make_label("") + preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + # FIX 20.10.b: gleiche autowrap-Explosion wie oben — (0, 60) überschreibt die + # Mindestbreite von _make_label, deshalb hier BREITE + Höhe explizit setzen. + preview_label.custom_minimum_size = UIScale.v2(500, 60) + vbox.add_child(preview_label) + + drop_check = CheckBox.new() + drop_check.text = "Draußen liegende Objekte/Bauteile/Spawn-Punkte beim Verkleinern entfernen" + drop_check.button_pressed = true + # U7: Klartext-Tooltip (Karte t_2336c58e). + drop_check.tooltip_text = "An = Dinge außerhalb der neuen Grenze fliegen raus (Strg+Z holt sie zurück).\nAus = sie bleiben, aber die Map ist dann NICHT speicherbar." + drop_check.toggled.connect(func(_p: bool) -> void: _update_preview()) + vbox.add_child(drop_check) + + size_x_edit.value_changed.connect(func(_v: float) -> void: _update_preview()) + size_z_edit.value_changed.connect(func(_v: float) -> void: _update_preview()) + + +func _make_label(text: String) -> Label: + var label := Label.new() + label.text = text + label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + # FIX 20.10. (Sly-Befund „Fenster viel zu lang, nichts bestätigbar“): ein + # autowrap-Label OHNE Mindestbreite meldet VOR dem ersten Layout eine + # explosionsartige Mindesthöhe (es wrappet bei ~1 px Breite → jedes Wort + # eine Zeile). popup_centered() nimmt genau diesen Wert als Fensterhöhe + # (Probe: 10 322 px statt ~320). Mindestbreite ~ Dialogbreite verankert + # den Umbruch an der echten Breite → Fenster normal hoch. + label.custom_minimum_size = Vector2(UIScale.px(500), 0) + return label + + +func _fill_spin(spin: SpinBox) -> void: + spin.min_value = SvmapDocument.MIN_SIZE_M + spin.max_value = SvmapDocument.MAX_SIZE_M + spin.step = 1.0 + spin.suffix = " m" + spin.value = 1024.0 + spin.custom_minimum_size = UIScale.v2(0, 40) + + +## Vom Editor vor dem Öffnen füllen: aktueller Stand + Live-Vorschau. +func setup(doc: Dictionary) -> void: + _doc = doc + var terrain: Dictionary = doc.get("terrain", {}) + _old_sx = float(terrain.get("size_x_m", 0.0)) + _old_sz = float(terrain.get("size_z_m", 0.0)) + _old_res = int(terrain.get("resolution", 0)) + info_label.text = "Aktuelle Größe: %d × %d m — Auflösung: %d × %d Höhenpunkte" % [ + int(_old_sx), int(_old_sz), _old_res, _old_res] + size_x_edit.value = _old_sx + size_z_edit.value = _old_sz + _update_preview() + + +func _on_preset(size: float) -> void: + size_x_edit.value = size + size_z_edit.value = size + + +func get_new_size_x() -> float: + return clampf(size_x_edit.value, SvmapDocument.MIN_SIZE_M, SvmapDocument.MAX_SIZE_M) + + +func get_new_size_z() -> float: + return clampf(size_z_edit.value, SvmapDocument.MIN_SIZE_M, SvmapDocument.MAX_SIZE_M) + + +func get_drop_outside() -> bool: + return drop_check.button_pressed + + +## Live-Vorschau: Verlust-Liste (R2 resize_preview), Spawn-Speicher-Warnung +## (V11 verlangt ≥ 4 Punkte/Team), Warnband bei Vergrößern über ~1024 m +## (Dichte-Leiter deckelt bei 513 Punkten → grobes Raster, Quest-Perf). +func _update_preview() -> void: + if _doc.is_empty(): + preview_label.text = "" + preview_label.modulate = Color(1, 1, 1, 0.7) + return + var new_sx := get_new_size_x() + var new_sz := get_new_size_z() + var preview: Dictionary = SvmapDocument.resize_preview(_doc, new_sx, new_sz) + var lines: Array[String] = [] + + var shrink := new_sx < _old_sx - 0.5 or new_sz < _old_sz - 0.5 + var grow := new_sx > _old_sx + 0.5 or new_sz > _old_sz + 0.5 + var out_total := (preview["objects_out"] as Array).size() \ + + (preview["parts_out"] as Array).size() \ + + (preview["spawns_out"] as Array).size() + + if shrink and out_total > 0: + var detail := "Beim Verkleinern liegen draußen: %d Objekt(e), %d Bauteil(e), %d Spawn-Punkt(e)." % [ + (preview["objects_out"] as Array).size(), + (preview["parts_out"] as Array).size(), + (preview["spawns_out"] as Array).size()] + if drop_check.button_pressed: + detail += "\nDiese werden beim Anwenden ENTFERNT (Strg+Z holt sie zurück)." + else: + detail += "\nSie bleiben in der Map, ragen aber über den Rand hinaus — die Map ist dann NICHT speicherbar (nichts darf außerhalb der Match-Grenzen liegen). Zieh sie später wieder rein oder entferne sie." + preview_label.modulate = Color(1.0, 0.75, 0.3) # Warnorange + lines.append(detail) + elif shrink: + preview_label.modulate = Color(1, 1, 1, 0.7) + lines.append("Beim Verkleinern geht nichts verloren — alles liegt innerhalb der neuen Grenzen.") + elif grow: + preview_label.modulate = Color(1, 1, 1, 0.7) + lines.append("Beim Vergrößern bleibt alles erhalten — die neue Randzone wird flache Steppe.") + + # Spawn-Speicher-Warnung (R2-Befund): V11 verlangt ≥ 4 Punkte je Team beim + # SPEICHERN — nach dem Entfernen drohen Teams unter den Deckwert. + if drop_check.button_pressed and out_total > 0: + var zones = _doc.get("spawn_zones", {}) + if zones is Dictionary: + var dropped: Dictionary = {} + for entry in (preview["spawns_out"] as Array): + if entry is Dictionary: + dropped[int(entry.get("team", -1))] = int(dropped.get(int(entry.get("team", -1)), 0)) + 1 + for team_key in (zones as Dictionary).keys(): + var zone = zones[team_key] + if not (zone is Dictionary): + continue + var kept := (zone.get("points", []) as Array).size() - int(dropped.get(int(team_key), 0)) + if kept < 4: + lines.append("Achtung: Team „%s“ hätte danach nur %d Spawn-Punkte — Maps brauchen mindestens 4 pro Team, um SPEICHERN zu können. Abhilfe: Entfernen ausstellen oder später Punkte ergänzen." % [ + str(zone.get("label", "Team %s" % str(team_key))), kept]) + preview_label.modulate = Color(1.0, 0.75, 0.3) + + # Warnband Vergrößern: über ~1024 m Kante grobt die Dichte (Deckel 513 + # Punkte) — Detail-Dichte + Quest-Perf (Objektzahl/Draw-Calls) erwähnen. + if grow: + var m_per_point := SvmapDocument.resize_m_per_point(_projected_resolution(), maxf(new_sx, new_sz)) + if m_per_point > SvmapDocument.RESIZE_DENSITY_TARGET_M: + lines.append("Achtung: Ab dieser Größe wird das Terrain grober aufgelöst (%.1f m pro Höhenpunkt statt 2 m). Die Hügel-Details werden weicher — für Quest-Perf gilt: mehr Fläche = mehr Objekte/Draw-Calls beachten." % m_per_point) + preview_label.modulate = Color(1.0, 0.75, 0.3) + + preview_label.text = "\n".join(lines) + _fit_after_layout.call_deferred() + + +## Fenster im sichtbaren Bereich halten: Höhe deckeln (überzähliger Inhalt +## scrollt im ScrollContainer) und neu zentrieren — sonst rutschen +## OK/Abbrechen unter den Bildschirmrand, wenn die Live-Vorschau das Fenster +## NACH dem Öffnen wachsen lässt (Probe: Fenster blieb oben hängen, Buttons +## bei y≈940 in einem 900-px-Sichtbereich). +func _fit_dialog() -> void: + var area := _parent_area() + if area.x <= 0.0 or area.y <= 0.0: + return + var max_h := int(area.y) - 8 + if size.y > max_h: + size = Vector2i(size.x, max_h) + var half := (area - Vector2(size)) * 0.5 + position = Vector2(maxi(0, int(half.x)), maxi(0, int(half.y))) + + +## Erst das Layout (und damit das Fenster-Wachstum) abwarten, dann den +## Scrollbereich auf „Inhalt vs. sichtbarer Platz“ justieren und verankern. +func _fit_after_layout() -> void: + await get_tree().process_frame + await get_tree().process_frame + var area := _parent_area() + if area.x <= 0.0 or area.y <= 0.0: + return + # Scrollbereich: so hoch wie der Inhalt, aber nie höher als der sichtbare + # Platz (Titelleiste + OK/Abbrechen + Ränder bleiben reserviert). Ist der + # Inhalt größer, scrollt er INNEN — die Buttons bleiben immer erreichbar. + var needed := _vbox.get_combined_minimum_size().y + UIScale.px(16) + var max_body := area.y - UIScale.px(90) + _scroll.custom_minimum_size = Vector2(UIScale.px(500), minf(needed, max_body)) + _fit_dialog() + + +func _parent_area() -> Vector2: + var p := get_parent() + if p is Control: + return (p as Control).size + if p is Window: + return (p as Window).size + return Vector2.ZERO + + +## Projected new resolution nach dem Resize (gleiche Leiter wie resize_terrain, +## hier nur für die Warnband-Anzeige — kein Terrain-Resample im Dialog). +func _projected_resolution() -> int: + var res := _old_res + for step in SvmapDocument.RESIZE_RESOLUTION_LADDER: + if step < res: + continue + if maxf(get_new_size_x(), get_new_size_z()) / float(step - 1) <= SvmapDocument.RESIZE_DENSITY_TARGET_M: + return step + return SvmapIO.MAX_TERRAIN_RESOLUTION \ No newline at end of file diff --git a/ui/resize_map_dialog.gd.uid b/ui/resize_map_dialog.gd.uid new file mode 100644 index 0000000..67c203e --- /dev/null +++ b/ui/resize_map_dialog.gd.uid @@ -0,0 +1 @@ +uid://6bjars8usr43 diff --git a/ui/ui_scale.gd b/ui/ui_scale.gd new file mode 100644 index 0000000..75f5fdc --- /dev/null +++ b/ui/ui_scale.gd @@ -0,0 +1,136 @@ +class_name UIScale +extends RefCounted + +## Zentrale UI-Skalierung (Karte t_297221b5): EIN Faktor für den ganzen Editor. +## +## Stufen: Klein (1.0) / Mittel (1.25) / Groß (1.5). Der Faktor multipliziert +## alle Layout-Werte beim UI-Bau (Schrift, Zeilen-/Knopfhöhen, Panelbreiten, +## Thumbnail-Größe, Abstände) — bewusst als Faktor-Multiplikation und NICHT als +## stretch/canvas_items, damit (a) die 3D-Map-Ansicht pixelgenau unberührt bleibt, +## (b) nichts doppelt skaliert und (c) die Stufe vom Nutzer gewählt wird statt +## von der Fenstergröße abzuhängen. +## +## Die gewählte Stufe liegt in user://scopedvr_editor.cfg, Abschnitt [ui] +## (Key "groesse"). ConfigFile lädt+speichert die GANZE Datei — der Abschnitt +## [library] (Klapp-Zustand, Karte t_59fc0f21) bleibt dabei erhalten. +## +## Für Tests/Probes kann per Umgebungsvariable SCOPEDVR_UI_CFG eine andere +## Config-Datei erzwungen werden (hermetische Läufe ohne Nutzer-Config). + +## Typografie-Ebenen (Karte t_2336c58e/U7 — verbindlich): genau ZWEI Schrift- +## Größen (Body + Klein) plus eine Überschriften-Stufe. KEINE weiteren Größen +## im Code — alles über die drei Helfer unten, skaliert mit der UI-Stufe. +const HEADING_FONT_SIZE := 20 # Überschriften (Panel-/Gruppen-Header, Titel) +const SMALL_FONT_SIZE := 13 # Nebentext (Zähler, Badges, Tile-IDs) + +const CFG_PATH := "user://scopedvr_editor.cfg" +const CFG_SECTION := "ui" +const CFG_KEY := "groesse" # "klein" | "mittel" | "gross" +const WIN_SECTION := "fenster" # Fenster-Zustand (maximiert/Größe/Position) + +## [key, Anzeige-Name, Faktor] +const STEPS: Array = [ + ["klein", "Klein", 1.0], + ["mittel", "Mittel", 1.25], + ["gross", "Groß", 1.5], +] +const BASE_FONT_SIZE := 16 # bisherige feste Schriftgröße (main.gd, Vorher) + +static var _factor: float = -1.0 # Cache; < 0 = noch nicht aus der Config gelesen + + +static func cfg_path() -> String: + var env := OS.get_environment("SCOPEDVR_UI_CFG") + if not env.is_empty(): + return env + return CFG_PATH + + +static func step_count() -> int: + return STEPS.size() + + +static func step_label(i: int) -> String: + return str(STEPS[clampi(i, 0, STEPS.size() - 1)][1]) + + +static func step_key(i: int) -> String: + return str(STEPS[clampi(i, 0, STEPS.size() - 1)][0]) + + +static func step_index_from_key(key: String) -> int: + for i in STEPS.size(): + if str(STEPS[i][0]) == key: + return i + return 0 # unbekannt/leer → Klein (Vorher-Verhalten) + + +## Aktuelle Stufe aus der Config (ohne Cache). +static func step_index() -> int: + var cfg := ConfigFile.new() + if cfg.load(cfg_path()) != OK: + return 0 # keine Config = Klein (altes Verhalten) + return step_index_from_key(str(cfg.get_value(CFG_SECTION, CFG_KEY, ""))) + + +## Faktor der aktuellen Stufe (cached — UI-Bau ruft das sehr oft). +static func factor() -> float: + if _factor < 0.0: + _factor = float(STEPS[step_index()][2]) + return _factor + + +## Stufe setzen UND sofort in die Config schreiben (Abschnitte wie [library] +## bleiben erhalten — ConfigFile liest/schreibt die ganze Datei). +static func set_step_index(i: int) -> void: + i = clampi(i, 0, STEPS.size() - 1) + var cfg := ConfigFile.new() + cfg.load(cfg_path()) # fehlt die Datei: leer anfangen, Abschnitte bleiben + cfg.set_value(CFG_SECTION, CFG_KEY, str(STEPS[i][0])) + var err := cfg.save(cfg_path()) + if err != OK: + push_warning("UIScale: Config nicht schreibbar (%s), Stufe nur für diese Sitzung." % err) + _factor = float(STEPS[i][2]) + + +## Cache verwerfen und neu lesen — simuliert den Neustart (für Tests/Probes). +static func reload() -> void: + _factor = -1.0 + factor() + + +# ------------------------------------------------------- Rechenhelfer (Bau) -- + +## Ganzzahliges Pixel-Maß (Höhen, Breiten, Abstände, Schriftgrößen). +static func px(v: float) -> int: + return int(round(v * factor())) + + +## Fließkommamaß (z. B. split_offset). +static func f(v: float) -> float: + return v * factor() + + +## custom_minimum_size = UIScale.v2(b, h) statt Vector2(b, h). +static func v2(x: float, y: float) -> Vector2: + return Vector2(px(x), px(y)) + + +## Dialog-/Fenstergrößen. +static func v2i(x: float, y: float) -> Vector2i: + return Vector2i(px(x), px(y)) + + +## Standardschriftgröße (Body-Text, bisher fest 16). +static func font_size() -> int: + return px(BASE_FONT_SIZE) + + +## Überschriften-Größe (Panel-/Gruppen-Header, Titel) — U7-Raster. +static func heading_font_size() -> int: + return px(HEADING_FONT_SIZE) + + +## Nebentext-Größe (Zähler, Badges, Tile-IDs) — U7-Raster. +static func small_font_size() -> int: + return px(SMALL_FONT_SIZE) diff --git a/ui/ui_scale.gd.uid b/ui/ui_scale.gd.uid new file mode 100644 index 0000000..beeeff8 --- /dev/null +++ b/ui/ui_scale.gd.uid @@ -0,0 +1 @@ +uid://d4d60533njpla diff --git a/ui/viewport_toolbar.gd b/ui/viewport_toolbar.gd new file mode 100644 index 0000000..1f85a2f --- /dev/null +++ b/ui/viewport_toolbar.gd @@ -0,0 +1,78 @@ +extends PanelContainer +## Werkzeugleiste oben links in der 3D-Ansicht (25.10.): erscheint, sobald ein +## Objekt ausgewählt ist. Umschalter Verschieben / Drehen & Kippen (steuert den +## Gizmo) plus schnelle Aktionen (Auf den Boden, Duplizieren, Löschen). +## Objekte werden bewusst NIE skaliert — es gibt kein Skalieren-Werkzeug. + +var main +var move_btn: Button +var rotate_btn: Button +var ground_btn: Button +var dup_btn: Button +var delete_btn: Button + + +func setup(p_main) -> void: + main = p_main + set_anchors_preset(Control.PRESET_TOP_LEFT) + offset_left = UIScale.px(12) + offset_top = UIScale.px(12) + mouse_filter = Control.MOUSE_FILTER_STOP + visible = false + var box := HBoxContainer.new() + box.add_theme_constant_override("separation", UIScale.px(6)) + add_child(box) + var group := ButtonGroup.new() + move_btn = _btn("↔ Verschieben", "Pfeile am Objekt ziehen: X rot, Y grün, Z blau", true, group) + move_btn.pressed.connect(func() -> void: _set_mode("move")) + box.add_child(move_btn) + rotate_btn = _btn("⟳ Drehen / Kippen", "Ringe am Objekt ziehen: um alle drei Achsen, in 15°-Schritten", true, group) + rotate_btn.pressed.connect(func() -> void: _set_mode("rotate")) + box.add_child(rotate_btn) + move_btn.set_pressed_no_signal(true) + box.add_child(VSeparator.new()) + ground_btn = _btn("⤓ Auf den Boden", "Auswahl aufs Terrain absetzen (Strg+Bild↑)", false, null) + ground_btn.pressed.connect(func() -> void: main.interaction.snap_selection_to_floor()) + box.add_child(ground_btn) + dup_btn = _btn("⧉ Kopie", "Duplizieren (Strg+D)", false, null) + dup_btn.pressed.connect(func() -> void: main.interaction.duplicate_selected()) + box.add_child(dup_btn) + delete_btn = _btn("🗑 Löschen", "Löschen (Entf) — Strg+Z holt es zurück", false, null) + delete_btn.pressed.connect(func() -> void: main.interaction.delete_selected()) + box.add_child(delete_btn) + + +func _btn(text: String, tip: String, toggle: bool, group: ButtonGroup) -> Button: + var b := Button.new() + b.text = text + b.tooltip_text = tip + b.toggle_mode = toggle + if group != null: + b.button_group = group + b.custom_minimum_size = UIScale.v2(0, 36) + b.focus_mode = Control.FOCUS_NONE # Leertaste/Pfeile bleiben beim Viewport + return b + + +func _set_mode(m: String) -> void: + if main.gizmo != null: + main.gizmo.set_mode(m) + + +## Sichtbarkeit pro Frame aus der Auswahl ableiten (wie der Gizmo selbst). +func _process(_delta: float) -> void: + if main == null or main.get("interaction") == null: + return + var inter = main.interaction + var mode: String = str(main.get("tool_mode")) + var want: bool = mode == "objects" \ + and not inter.selected_indices.is_empty() \ + and not bool(inter.get("placing")) \ + and main.get("playtest") == null + if want != visible: + visible = want + if visible and main.gizmo != null: + var rotate_on: bool = main.gizmo.mode == "rotate" + if rotate_btn.button_pressed != rotate_on: + rotate_btn.set_pressed_no_signal(rotate_on) + move_btn.set_pressed_no_signal(not rotate_on) diff --git a/ui/viewport_toolbar.gd.uid b/ui/viewport_toolbar.gd.uid new file mode 100644 index 0000000..d93f503 --- /dev/null +++ b/ui/viewport_toolbar.gd.uid @@ -0,0 +1 @@ +uid://jjp1jooped0q diff --git a/vendor/modular_builder/LICENSE b/vendor/modular_builder/LICENSE new file mode 100644 index 0000000..1e93566 --- /dev/null +++ b/vendor/modular_builder/LICENSE @@ -0,0 +1,125 @@ +CC0 1.0 Universal + +Copyright (c) 2026 Michel Savadim + +This CC0 dedication applies only to the original Modular Builder source code, +icons, and documentation in this directory authored by Michel Savadim. It does +not apply to Godot Engine, user-created project content, trademarks, patents, +or third-party materials distributed separately. + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN +ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS +PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. + +https://creativecommons.org/publicdomain/zero/1.0/legalcode diff --git a/vendor/modular_builder/modular_building_detail.gd b/vendor/modular_builder/modular_building_detail.gd new file mode 100644 index 0000000..afe295d --- /dev/null +++ b/vendor/modular_builder/modular_building_detail.gd @@ -0,0 +1,284 @@ +@tool +class_name ModularBuilderDetail +extends Node3D + +enum DetailType { WINDOW, DOOR, HATCH, PILLAR, FENCE, PARAPET } +enum DetailMode { OPENING, SEPARATE_OBJECT } +enum WindowStyle { SINGLE_PANE, CROSS_PANE } +enum DoorStyle { FLAT, PANELED } + +@export var detail_type: DetailType = DetailType.WINDOW: + set(value): + detail_type = value + notify_property_list_changed() + _rebuild() +@export var detail_mode: DetailMode = DetailMode.OPENING: + set(value): + detail_mode = value + notify_property_list_changed() + _rebuild() +@export var center_align: bool = false: + set(value): + center_align = value + _apply_pillar_alignment() + notify_property_list_changed() +@export_storage var pillar_direction: Vector3 = Vector3.UP +@export_storage var pillar_alignment_basis: Basis = Basis.IDENTITY +@export var window_style: WindowStyle = WindowStyle.SINGLE_PANE: + set(value): + window_style = value + _rebuild() +@export var door_style: DoorStyle = DoorStyle.FLAT: + set(value): + door_style = value + _rebuild() +@export_range(0.01, 5.0, 0.001, "suffix:m") var width: float = 1.0: + set(value): + width = value + _rebuild() +@export_range(0.2, 5.0, 0.001, "suffix:m") var height: float = 1.0: + set(value): + height = value + _rebuild() +@export_range(0.02, 0.5, 0.001, "suffix:m") var frame_depth: float = 0.08: + set(value): + frame_depth = value + _rebuild() +@export_range(0.1, 8.0, 0.001, "suffix:m") var fence_height: float = 0.7: + set(value): + fence_height = value + _rebuild() +@export_range(0, 100, 1, "suffix: posts") var fence_divisions: int = 1: + set(value): + fence_divisions = value + _rebuild() +@export_storage var path_points: PackedVector3Array = PackedVector3Array() +@export_range(0.01, 0.5, 0.001, "suffix:m") var frame_width: float = 0.08: + set(value): + frame_width = value + _rebuild() +@export var follow_host_thickness: bool = true: + set(value): + follow_host_thickness = value + _rebuild() +@export var surface_material: Material: + set(value): + surface_material = value + _rebuild() +@export var frame_material: Material: + set(value): + frame_material = value + _rebuild() +@export var panel_material: Material: + set(value): + panel_material = value + _rebuild() + +func _validate_property(property: Dictionary) -> void: + var separate_object := detail_mode == DetailMode.SEPARATE_OBJECT + var is_window := detail_type == DetailType.WINDOW + var is_door := detail_type == DetailType.DOOR + var has_frame_and_panel := separate_object and detail_type != DetailType.PILLAR and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET + var show_property := true + match StringName(property.name): + &"detail_mode": + show_property = detail_type != DetailType.PILLAR and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET + &"center_align": + show_property = detail_type == DetailType.PILLAR + &"fence_height", &"fence_divisions": + show_property = detail_type == DetailType.FENCE or detail_type == DetailType.PARAPET + &"path_points": + show_property = false + &"width": + show_property = detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET + if detail_type == DetailType.PILLAR: + property.hint = PROPERTY_HINT_RANGE + property.hint_string = "0.01,5.0,0.001,suffix:m" + &"height": + show_property = detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET + &"window_style": + show_property = is_window and separate_object + &"door_style": + show_property = is_door and separate_object + &"frame_width", &"frame_material", &"panel_material": + show_property = has_frame_and_panel + &"follow_host_thickness": + show_property = has_frame_and_panel + &"frame_depth": + show_property = has_frame_and_panel and not follow_host_thickness + &"surface_material": + show_property = separate_object + if not show_property: + property.usage = PROPERTY_USAGE_NO_EDITOR + +func _ready() -> void: + set_notify_transform(true) + _rebuild() + +func _notification(what: int) -> void: + if what == NOTIFICATION_TRANSFORM_CHANGED and is_inside_tree(): + var host := get_parent() + if host != null and host.has_method("_rebuild"): + host.call("_rebuild") + +func _on_host_thickness_changed() -> void: + _rebuild(false) + +func _apply_pillar_alignment() -> void: + if detail_type != DetailType.PILLAR: + return + var direction := pillar_direction.normalized() + if direction.is_zero_approx(): + direction = Vector3.UP + if center_align: + var object_axes := pillar_alignment_basis.orthonormalized() + var best_axis := direction + var best_dot := -1.0 + for axis in [object_axes.x, object_axes.y, object_axes.z]: + var normalized_axis: Vector3 = axis.normalized() + var alignment := absf(direction.dot(normalized_axis)) + if alignment > best_dot: + best_dot = alignment + best_axis = normalized_axis if direction.dot(normalized_axis) >= 0.0 else -normalized_axis + direction = best_axis + var reference_axis := Vector3.FORWARD if absf(direction.dot(Vector3.UP)) > 0.99 else Vector3.UP + var x_axis := reference_axis.cross(direction).normalized() + var z_axis := x_axis.cross(direction).normalized() + transform.basis = Basis(x_axis, direction, z_axis).orthonormalized() + if center_align and pillar_direction.length() > 0.0: + height = pillar_direction.length() + +func _rebuild(notify_host: bool = true) -> void: + if not is_inside_tree(): + return + for child in get_children(): + if child.name == "DetailGeometry": + remove_child(child) + child.queue_free() + var host := get_parent() + if notify_host and host != null and host.has_method("_rebuild"): + host.call("_rebuild") + if detail_mode == DetailMode.OPENING and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET: + return + var holder := Node3D.new() + holder.name = "DetailGeometry" + add_child(holder) + var scene_root: Node = null + if Engine.is_editor_hint() and not has_meta("modular_builder_preview"): + scene_root = get_tree().edited_scene_root + if scene_root: + holder.owner = scene_root + var effective_depth := _effective_frame_depth() + var frame := minf(maxf(frame_width, 0.01), minf(width, height) * 0.45) + if detail_type == DetailType.FENCE or detail_type == DetailType.PARAPET: + _build_fence_geometry(holder) + return + if detail_type == DetailType.PILLAR: + _add_box(holder, "PillarBody", Vector3(width, height, width), Vector3.ZERO, surface_material) + return + if detail_type == DetailType.HATCH: + _add_box(holder, "LeftFrame", Vector3(frame, effective_depth, height), Vector3(-width * 0.5 + frame * 0.5, 0.0, 0.0), _frame_material()) + _add_box(holder, "RightFrame", Vector3(frame, effective_depth, height), Vector3(width * 0.5 - frame * 0.5, 0.0, 0.0), _frame_material()) + _add_box(holder, "NearFrame", Vector3(width, effective_depth, frame), Vector3(0.0, 0.0, -height * 0.5 + frame * 0.5), _frame_material()) + _add_box(holder, "FarFrame", Vector3(width, effective_depth, frame), Vector3(0.0, 0.0, height * 0.5 - frame * 0.5), _frame_material()) + _add_box(holder, "HatchPanel", Vector3(width - frame * 2.0, effective_depth * 0.35, height - frame * 2.0), Vector3(0.0, effective_depth * 0.4, 0.0), _panel_material()) + return + _add_box(holder, "LeftFrame", Vector3(frame, height, effective_depth), Vector3(-width * 0.5 + frame * 0.5, 0.0, 0.0), _frame_material()) + _add_box(holder, "RightFrame", Vector3(frame, height, effective_depth), Vector3(width * 0.5 - frame * 0.5, 0.0, 0.0), _frame_material()) + _add_box(holder, "TopFrame", Vector3(width, frame, effective_depth), Vector3(0.0, height * 0.5 - frame * 0.5, 0.0), _frame_material()) + if detail_type != DetailType.DOOR: + _add_box(holder, "BottomFrame", Vector3(width, frame, effective_depth), Vector3(0.0, -height * 0.5 + frame * 0.5, 0.0), _frame_material()) + if detail_type == DetailType.WINDOW: + _add_box(holder, "WindowPanel", Vector3(maxf(width - frame * 2.0, 0.001), maxf(height - frame * 2.0, 0.001), effective_depth * 0.35), Vector3.ZERO, _panel_material()) + if window_style == WindowStyle.CROSS_PANE: + _add_box(holder, "VerticalMullion", Vector3(frame * 0.55, height - frame * 2.0, effective_depth * 0.4), Vector3.ZERO, _frame_material()) + _add_box(holder, "HorizontalMullion", Vector3(width - frame * 2.0, frame * 0.55, effective_depth * 0.4), Vector3.ZERO, _frame_material()) + elif detail_type == DetailType.DOOR: + _add_box(holder, "DoorPanel", Vector3(width - frame * 2.0, height - frame, effective_depth * 0.45), Vector3(0.0, -frame * 0.5, 0.0), _panel_material()) + if door_style == DoorStyle.PANELED: + var inset_depth := maxf(effective_depth * 0.04, 0.005) + var panel_width := (width - frame * 2.0) * 0.82 + var panel_height := maxf((height - frame) * 0.28, 0.05) + for panel_index in range(2): + var panel_y := -frame * 0.5 + (panel_index - 0.5) * (panel_height + frame * 0.25) + _add_box(holder, "DoorInset_%d" % panel_index, Vector3(panel_width, panel_height, inset_depth), Vector3(0.0, panel_y, effective_depth * 0.24), _frame_material()) + +func _effective_frame_depth() -> float: + if follow_host_thickness: + var host := get_parent() + if host != null: + var host_thickness = host.get("thickness") + if host_thickness is float or host_thickness is int: + var depth := float(host_thickness) + if detail_type == DetailType.WINDOW or detail_type == DetailType.DOOR: + depth *= 1.25 + return maxf(depth, 0.02) + return frame_depth + +func _build_fence_geometry(holder: Node3D) -> void: + if path_points.size() < 2 or fence_height <= 0.0: + return + var start := path_points[0] + var finish := path_points[1] + var path := finish - start + var path_length := path.length() + if path_length < 0.05: + return + var path_axis := path / path_length + var up_axis := Vector3.UP - path_axis * path_axis.dot(Vector3.UP) + if up_axis.length_squared() < 0.001: + up_axis = Vector3.FORWARD - path_axis * path_axis.dot(Vector3.FORWARD) + up_axis = up_axis.normalized() + var side_axis := path_axis.cross(up_axis).normalized() + var post_width := 0.16 if detail_type == DetailType.PARAPET else 0.08 + var post_count := fence_divisions + 2 + var posts: Array[Vector3] = [] + for i in range(post_count): + var base := start.lerp(finish, float(i) / float(post_count - 1)) + posts.append(base) + _add_oriented_box(holder, "Post_%d" % i, base + up_axis * fence_height * 0.5, path_axis, up_axis, side_axis, Vector3(post_width, fence_height, post_width)) + for i in range(posts.size() - 1): + var a := posts[i] + var b := posts[i + 1] + var segment := b - a + var segment_length := segment.length() + if detail_type == DetailType.PARAPET: + _add_oriented_box(holder, "Rail_%d" % i, (a + b) * 0.5 + up_axis * fence_height * 0.78, segment.normalized(), up_axis, side_axis, Vector3(segment_length, post_width * 0.8, post_width * 0.8)) + else: + for height_ratio in [0.28, 0.78]: + _add_oriented_box(holder, "Rail_%d_%d" % [i, int(height_ratio * 100.0)], (a + b) * 0.5 + up_axis * fence_height * height_ratio, segment.normalized(), up_axis, side_axis, Vector3(segment_length, post_width, post_width)) + +func _add_oriented_box(holder: Node3D, node_name: String, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3, dimensions: Vector3) -> void: + var mesh_instance := MeshInstance3D.new() + mesh_instance.name = node_name + var box := BoxMesh.new() + box.size = dimensions + mesh_instance.mesh = box + var basis := Basis(axis_x.normalized(), axis_y.normalized(), axis_z.normalized()).orthonormalized() + mesh_instance.transform = Transform3D(basis, center) + mesh_instance.material_override = surface_material + holder.add_child(mesh_instance) + if Engine.is_editor_hint() and not has_meta("modular_builder_preview"): + var scene_root := get_tree().edited_scene_root + if scene_root: + mesh_instance.owner = scene_root + +func _frame_material() -> Material: + return frame_material if frame_material != null else surface_material + +func _panel_material() -> Material: + return panel_material if panel_material != null else surface_material + +func _add_box(holder: Node3D, node_name: String, box_size: Vector3, box_position: Vector3, material: Material = null) -> void: + var mesh_instance := MeshInstance3D.new() + mesh_instance.name = node_name + var box := BoxMesh.new() + box.size = box_size + mesh_instance.mesh = box + mesh_instance.position = box_position + mesh_instance.material_override = material if material != null else surface_material + holder.add_child(mesh_instance) + if Engine.is_editor_hint() and not has_meta("modular_builder_preview"): + var scene_root := get_tree().edited_scene_root + if scene_root: + mesh_instance.owner = scene_root diff --git a/vendor/modular_builder/modular_building_detail.gd.uid b/vendor/modular_builder/modular_building_detail.gd.uid new file mode 100644 index 0000000..4f8775f --- /dev/null +++ b/vendor/modular_builder/modular_building_detail.gd.uid @@ -0,0 +1 @@ +uid://3niej5acydsb diff --git a/vendor/modular_builder/modular_piece_geometry.gd b/vendor/modular_builder/modular_piece_geometry.gd new file mode 100644 index 0000000..24becb0 --- /dev/null +++ b/vendor/modular_builder/modular_piece_geometry.gd @@ -0,0 +1,748 @@ +@tool +class_name ModularBuilderPieceGeometry +extends Node3D + +const DETAIL_SCRIPT_PATH := "res://core/vendor/modular_builder/modular_building_detail.gd" +const DETAIL_TYPE_HATCH := 2 +const DETAIL_TYPE_PILLAR := 3 + +enum PieceType { FOUNDATION_SQUARE, FOUNDATION_TRIANGLE, FLOOR, WALL, BUILDING, GABLE_WALL, ROOF_GABLE, ROOF_SHED, GABLE_WALL_RIGHT, ROOF_PYRAMID, STAIRS, FENCE } + +@export var piece_type: PieceType = PieceType.FOUNDATION_SQUARE: + set(value): + piece_type = value + notify_property_list_changed() + _rebuild() +@export_range(0.5, 8.0, 0.001, "suffix:m") var unit_size: float = 2.0: + set(value): + unit_size = value + _rebuild() +@export_range(0.01, 1.0, 0.001, "suffix:m") var thickness: float = 0.2: + set(value): + thickness = value + _rebuild() + for child in get_children(): + if child.has_method("_on_host_thickness_changed"): + child.call("_on_host_thickness_changed") +@export_range(0.1, 8.0, 0.001, "suffix:m") var rise: float = 1.0: + set(value): + rise = value + _rebuild() +@export_range(0.01, 32.0, 0.001, "suffix:m") var roof_width: float = 2.0: + set(value): + roof_width = value + _rebuild() +@export_range(1, 100, 1, "suffix: steps") var stair_count: int = 10: + set(value): + stair_count = value + _rebuild() +@export_range(0.01, 8.0, 0.001, "suffix:m") var stair_width: float = 1.0: + set(value): + stair_width = value + _rebuild() +@export_range(0, 100, 1, "suffix: posts") var fence_divisions: int = 1: + set(value): + fence_divisions = value + _rebuild() +@export_range(0.1, 8.0, 0.001, "suffix:m") var fence_height: float = 1.0: + set(value): + fence_height = value + _rebuild() +@export var symmetric_dimensions: bool = true: + set(value): + if symmetric_dimensions == value: + return + if not value: + _capture_mesh_dimensions() + symmetric_dimensions = value + notify_property_list_changed() + if value: + scale = Vector3.ONE + else: + _apply_dimension_scale() +@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_width: float = 2.0: + set(value): + dimension_width = value + _apply_dimension_scale() +@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_height: float = 2.0: + set(value): + dimension_height = value + _apply_dimension_scale() +@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_depth: float = 2.0: + set(value): + dimension_depth = value + _apply_dimension_scale() +@export_storage var _dimension_reference: Vector3 = Vector3.ZERO +@export var center_align: bool = false: + set(value): + center_align = value + _rebuild() +@export var surface_material: Material: + set(value): + surface_material = value + _rebuild() +@export var custom_points: PackedVector3Array = PackedVector3Array(): + set(value): + custom_points = value + _rebuild() +@export var centered_origin: bool = false: + set(value): + centered_origin = value + _rebuild() + +func _rebuild() -> void: + pass + +func _capture_mesh_dimensions() -> void: + var geometry := get_node_or_null("Geometry") as MeshInstance3D + if geometry == null or geometry.mesh == null: + return + var dimensions := geometry.mesh.get_aabb().size + if dimensions.x > 0.001: + dimension_width = dimensions.x + if dimensions.y > 0.001: + dimension_height = dimensions.y + if dimensions.z > 0.001: + dimension_depth = dimensions.z + _dimension_reference = dimensions + +func _apply_dimension_scale() -> void: + if symmetric_dimensions or _dimension_reference.x <= 0.001 or _dimension_reference.y <= 0.001 or _dimension_reference.z <= 0.001: + return + var target := Vector3(dimension_width, dimension_height, dimension_depth) + if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE or piece_type == PieceType.FLOOR: + target.y = _dimension_reference.y + if piece_type == PieceType.WALL: + target.z = _dimension_reference.z + scale = Vector3(target.x / _dimension_reference.x, target.y / _dimension_reference.y, target.z / _dimension_reference.z) + +func _make_mesh() -> ArrayMesh: + if piece_type == PieceType.GABLE_WALL and custom_points.size() >= 2: + return _make_gable_wall_mesh() + if piece_type == PieceType.GABLE_WALL_RIGHT and custom_points.size() >= 2: + return _make_right_gable_wall_mesh() + if (piece_type == PieceType.ROOF_GABLE or piece_type == PieceType.ROOF_SHED or piece_type == PieceType.ROOF_PYRAMID) and custom_points.size() >= 2: + return _make_roof_mesh() + if piece_type == PieceType.STAIRS and custom_points.size() >= 2: + return _make_stair_mesh() + if piece_type == PieceType.FENCE and custom_points.size() >= 2: + return _make_fence_mesh() + if custom_points.size() >= 2 and piece_type == PieceType.WALL: + return _make_custom_wall_mesh() + if custom_points.size() >= 3 and piece_type == PieceType.FLOOR: + return _make_custom_floor_mesh() + if custom_points.size() >= 3 and (piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE): + return _make_custom_foundation_mesh() + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var s := unit_size + var polygon: Array[Vector2] + match piece_type: + PieceType.FOUNDATION_TRIANGLE: + polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(-s * 0.5, s * 0.5)] + _: + polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(s * 0.5, s * 0.5), Vector2(-s * 0.5, s * 0.5)] + var y0 := -thickness * 0.5 if centered_origin else 0.0 + var y1 := thickness * 0.5 if centered_origin else thickness + if piece_type == PieceType.WALL: + polygon = [Vector2(-s * 0.5, -thickness * 0.5), Vector2(s * 0.5, -thickness * 0.5), Vector2(s * 0.5, thickness * 0.5), Vector2(-s * 0.5, thickness * 0.5)] + y0 = -s * 0.5 if centered_origin else 0.0 + y1 = s * 0.5 if centered_origin else s + if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE: + var hatch_mesh := _make_hatch_surface_mesh(polygon, y1, y0) + if hatch_mesh != null: + return hatch_mesh + for i in range(1, polygon.size() - 1): + _add_tri(vertices, normals, Vector3(polygon[0].x, y1, polygon[0].y), Vector3(polygon[i + 1].x, y1, polygon[i + 1].y), Vector3(polygon[i].x, y1, polygon[i].y)) + _add_tri(vertices, normals, Vector3(polygon[0].x, y0, polygon[0].y), Vector3(polygon[i].x, y0, polygon[i].y), Vector3(polygon[i + 1].x, y0, polygon[i + 1].y)) + for i in range(polygon.size()): + var j := (i + 1) % polygon.size() + var a := polygon[i] + var b := polygon[j] + _add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y0, b.y), Vector3(b.x, y1, b.y)) + _add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y1, b.y), Vector3(a.x, y1, a.y)) + var arrays: Array = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertices + arrays[Mesh.ARRAY_NORMAL] = normals + var result := ArrayMesh.new() + result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + return result + +func _make_gable_wall_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]]) + var a: Vector3 = base_points[0] + var b: Vector3 = base_points[1] + var direction := (b - a).normalized() + var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5 + var peak := (a + b) * 0.5 + Vector3.UP * rise + var front_a := a + side + var front_b := b + side + var front_peak := peak + side + var back_a := a - side + var back_b := b - side + var back_peak := peak - side + _add_tri(vertices, normals, front_a, front_b, front_peak) + _add_tri(vertices, normals, back_b, back_a, back_peak) + _add_quad(vertices, normals, front_a, back_a, back_b, front_b) + _add_quad(vertices, normals, front_b, back_b, back_peak, front_peak) + _add_quad(vertices, normals, front_peak, back_peak, back_a, front_a) + return _array_mesh(vertices, normals) + +func _make_right_gable_wall_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]]) + var a: Vector3 = base_points[0] + var b: Vector3 = base_points[1] + var direction := (b - a).normalized() + var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5 + var peak := b + Vector3.UP * rise + var front_a := a + side + var front_b := b + side + var front_peak := peak + side + var back_a := a - side + var back_b := b - side + var back_peak := peak - side + _add_tri(vertices, normals, front_a, front_b, front_peak) + _add_tri(vertices, normals, back_peak, back_b, back_a) + _add_quad(vertices, normals, front_a, back_a, back_b, front_b) + _add_quad(vertices, normals, front_b, back_b, back_peak, front_peak) + _add_quad(vertices, normals, front_peak, back_peak, back_a, front_a) + return _array_mesh(vertices, normals) + +func _make_roof_mesh() -> ArrayMesh: + if piece_type == PieceType.ROOF_SHED and custom_points.size() == 2: + return _make_shed_roof_mesh() + if custom_points.size() >= 4: + return _make_oriented_roof_mesh() + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var first: Vector3 = custom_points[0] + var second: Vector3 = custom_points[1] + var min_x := minf(first.x, second.x) + var max_x := maxf(first.x, second.x) + var min_z := minf(first.z, second.z) + var max_z := maxf(first.z, second.z) + var span_x := max_x - min_x + var span_z := max_z - min_z + if span_x < 0.02 or span_z < 0.02: + return _array_mesh(vertices, normals) + var panels: Array = [] + if piece_type == PieceType.ROOF_GABLE: + if span_x <= span_z: + var ridge_x := (min_x + max_x) * 0.5 + panels.append([Vector3(min_x, 0.0, min_z), Vector3(min_x, 0.0, max_z), Vector3(ridge_x, rise, max_z), Vector3(ridge_x, rise, min_z)]) + panels.append([Vector3(ridge_x, rise, min_z), Vector3(ridge_x, rise, max_z), Vector3(max_x, 0.0, max_z), Vector3(max_x, 0.0, min_z)]) + else: + var ridge_z := (min_z + max_z) * 0.5 + panels.append([Vector3(min_x, 0.0, min_z), Vector3(max_x, 0.0, min_z), Vector3(max_x, rise, ridge_z), Vector3(min_x, rise, ridge_z)]) + panels.append([Vector3(min_x, rise, ridge_z), Vector3(max_x, rise, ridge_z), Vector3(max_x, 0.0, max_z), Vector3(min_x, 0.0, max_z)]) + else: + if span_x <= span_z: + var low_x := first.x + var high_x := second.x + panels.append([Vector3(low_x, 0.0, min_z), Vector3(low_x, 0.0, max_z), Vector3(high_x, rise, max_z), Vector3(high_x, rise, min_z)]) + else: + var low_z := first.z + var high_z := second.z + panels.append([Vector3(min_x, 0.0, low_z), Vector3(max_x, 0.0, low_z), Vector3(max_x, rise, high_z), Vector3(min_x, rise, high_z)]) + for panel in panels: + _append_solid_panel(vertices, normals, panel) + return _array_mesh(vertices, normals) + +func _make_shed_roof_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var low: Vector3 = custom_points[0] + var high: Vector3 = custom_points[1] + var slope := high - low + var horizontal := Vector3(slope.x, 0.0, slope.z) + if horizontal.length_squared() < 0.0001 or roof_width <= 0.0: + return _array_mesh(vertices, normals) + var across := Vector3(horizontal.z, 0.0, -horizontal.x).normalized() * roof_width * 0.5 + var panel := [low - across, low + across, high + across, high - across] + _append_solid_panel(vertices, normals, panel) + return _array_mesh(vertices, normals) + +func _make_oriented_roof_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var p0: Vector3 = custom_points[0] + var p1: Vector3 = custom_points[1] + var p2: Vector3 = custom_points[2] + var p3: Vector3 = custom_points[3] + var edge_01 := Vector2(p1.x - p0.x, p1.z - p0.z).length() + var edge_12 := Vector2(p2.x - p1.x, p2.z - p1.z).length() + var panels: Array = [] + if piece_type == PieceType.ROOF_GABLE: + if edge_01 <= edge_12: + var ridge_01 := (p0 + p1) * 0.5 + Vector3.UP * rise + var ridge_23 := (p2 + p3) * 0.5 + Vector3.UP * rise + panels.append([p0, p3, ridge_23, ridge_01]) + panels.append([ridge_01, ridge_23, p2, p1]) + else: + var ridge_12 := (p1 + p2) * 0.5 + Vector3.UP * rise + var ridge_30 := (p3 + p0) * 0.5 + Vector3.UP * rise + panels.append([p0, p1, ridge_12, ridge_30]) + panels.append([ridge_30, ridge_12, p2, p3]) + elif piece_type == PieceType.ROOF_PYRAMID: + var peak := (p0 + p1 + p2 + p3) * 0.25 + Vector3.UP * rise + panels.append([p0, p1, peak]) + panels.append([p1, p2, peak]) + panels.append([p2, p3, peak]) + panels.append([p3, p0, peak]) + else: + # The four points define the low and high edges. Keep their centers + # fixed when adjusting both edge widths in the Inspector. + var base_width := Vector2(p1.x - p0.x, p1.z - p0.z).length() + if base_width > 0.001 and roof_width > 0.0: + var direction := Vector3(p1.x - p0.x, 0.0, p1.z - p0.z).normalized() + var low_center := (p0 + p1) * 0.5 + var high_center := (p2 + p3) * 0.5 + var half_width := roof_width * 0.5 + panels.append([ + low_center - direction * half_width, + low_center + direction * half_width, + high_center + direction * half_width, + high_center - direction * half_width, + ]) + else: + panels.append([p0, p1, p2, p3]) + for panel in panels: + _append_solid_panel(vertices, normals, panel) + return _array_mesh(vertices, normals) + +func _make_stair_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var start: Vector3 = custom_points[0] + var finish: Vector3 = custom_points[1] + var horizontal := Vector3(finish.x - start.x, 0.0, finish.z - start.z) + var run := horizontal.length() + var rise_amount := finish.y - start.y + if run < 0.05 or rise_amount <= 0.05 or stair_count < 1 or stair_width <= 0.0: + return _array_mesh(vertices, normals) + var forward := horizontal / run + var width_axis := Vector3(-forward.z, 0.0, forward.x) * stair_width * 0.5 + var profile := PackedVector2Array([Vector2(0.0, 0.0), Vector2(run, 0.0), Vector2(run, rise_amount)]) + for step_index in range(stair_count - 1, -1, -1): + var tread_start := run * float(step_index) / stair_count + var tread_end := run * float(step_index + 1) / stair_count + var tread_height := rise_amount * float(step_index + 1) / stair_count + profile.append(Vector2(tread_start, tread_height)) + if step_index > 0: + profile.append(Vector2(tread_start, rise_amount * float(step_index) / stair_count)) + var triangulation := Geometry2D.triangulate_polygon(profile) + for side_sign in [-1.0, 1.0]: + for triangle_index in range(0, triangulation.size(), 3): + var a := profile[triangulation[triangle_index]] + var b := profile[triangulation[triangle_index + 1]] + var c := profile[triangulation[triangle_index + 2]] + var point_a: Vector3 = start + forward * a.x + Vector3.UP * a.y + width_axis * side_sign + var point_b: Vector3 = start + forward * b.x + Vector3.UP * b.y + width_axis * side_sign + var point_c: Vector3 = start + forward * c.x + Vector3.UP * c.y + width_axis * side_sign + if side_sign > 0.0: + _add_tri(vertices, normals, point_a, point_b, point_c) + else: + _add_tri(vertices, normals, point_c, point_b, point_a) + for profile_index in range(profile.size()): + var next_index := (profile_index + 1) % profile.size() + var a := profile[profile_index] + var b := profile[next_index] + var point_a_left := start + forward * a.x + Vector3.UP * a.y - width_axis + var point_b_left := start + forward * b.x + Vector3.UP * b.y - width_axis + var point_b_right := start + forward * b.x + Vector3.UP * b.y + width_axis + var point_a_right := start + forward * a.x + Vector3.UP * a.y + width_axis + _add_quad(vertices, normals, point_a_left, point_b_left, point_b_right, point_a_right) + return _array_mesh(vertices, normals) + +func _make_fence_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var start: Vector3 = custom_points[0] + var finish: Vector3 = custom_points[1] + var path := finish - start + var path_length := path.length() + if path_length < 0.05 or fence_height <= 0.0: + return _array_mesh(vertices, normals) + var path_axis := path / path_length + var post_count := fence_divisions + 2 + var post_positions: Array[Vector3] = [] + for post_index in range(post_count): + var base_position := start.lerp(finish, float(post_index) / float(post_count - 1)) + post_positions.append(base_position) + var side_axis := path_axis.cross(Vector3.UP).normalized() + if side_axis.length_squared() < 0.001: + side_axis = Vector3.FORWARD + _append_oriented_box(vertices, normals, base_position + Vector3.UP * fence_height * 0.5, path_axis, Vector3.UP, side_axis, Vector3(thickness, fence_height, thickness)) + for section_index in range(post_positions.size() - 1): + var section_start := post_positions[section_index] + var section_end := post_positions[section_index + 1] + var section_axis := (section_end - section_start).normalized() + var rail_up := Vector3.UP - section_axis * section_axis.dot(Vector3.UP) + if rail_up.length_squared() < 0.001: + rail_up = Vector3.FORWARD - section_axis * section_axis.dot(Vector3.FORWARD) + rail_up = rail_up.normalized() + var rail_side := section_axis.cross(rail_up).normalized() + var section_length := section_start.distance_to(section_end) + for height_ratio in [0.28, 0.78]: + var rail_center: Vector3 = (section_start + section_end) * 0.5 + Vector3.UP * fence_height * height_ratio + _append_oriented_box(vertices, normals, rail_center, section_axis, rail_up, rail_side, Vector3(section_length, thickness, thickness)) + return _array_mesh(vertices, normals) + +func _append_oriented_box(vertices: PackedVector3Array, normals: PackedVector3Array, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3, dimensions: Vector3) -> void: + var x := axis_x.normalized() * dimensions.x * 0.5 + var y := axis_y.normalized() * dimensions.y * 0.5 + var z := axis_z.normalized() * dimensions.z * 0.5 + var nnn := center - x - y - z + var pnn := center + x - y - z + var ppn := center + x + y - z + var npn := center - x + y - z + var nnp := center - x - y + z + var pnp := center + x - y + z + var ppp := center + x + y + z + var npp := center - x + y + z + _add_quad(vertices, normals, nnp, pnp, ppp, npp) + _add_quad(vertices, normals, pnn, nnn, npn, ppn) + _add_quad(vertices, normals, npp, ppp, ppn, npn) + _add_quad(vertices, normals, nnn, pnn, pnp, nnp) + _add_quad(vertices, normals, nnn, nnp, npp, npn) + _add_quad(vertices, normals, pnp, pnn, ppn, ppp) + +func _get_centered_gable_base() -> PackedVector3Array: + var selected_a := to_global(custom_points[0]) + var selected_b := to_global(custom_points[1]) + var search_root: Node = get_parent() + while search_root != null and not search_root is ModularBuilderPieceGeometry: + search_root = search_root.get_parent() + if search_root == null: + return PackedVector3Array([custom_points[0], custom_points[1]]) + var walls: Array[ModularBuilderPieceGeometry] = [] + _collect_centering_walls(search_root, walls) + var centered_a := selected_a + var centered_b := selected_b + var nearest_a := INF + var nearest_b := INF + for wall in walls: + if wall.custom_points.size() < 2: + continue + var wall_a := wall.to_global(wall.custom_points[0] + Vector3.UP * wall.unit_size) + var wall_b := wall.to_global(wall.custom_points[1] + Vector3.UP * wall.unit_size) + var tolerance: float = wall.thickness + 0.04 + for endpoint in [wall_a, wall_b]: + var distance_a := selected_a.distance_to(endpoint) + if distance_a < nearest_a and distance_a <= tolerance: + nearest_a = distance_a + centered_a = endpoint + var distance_b := selected_b.distance_to(endpoint) + if distance_b < nearest_b and distance_b <= tolerance: + nearest_b = distance_b + centered_b = endpoint + if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02: + return PackedVector3Array([to_local(centered_a), to_local(centered_b)]) + return PackedVector3Array([custom_points[0], custom_points[1]]) + +func _collect_centering_walls(node: Node, walls: Array[ModularBuilderPieceGeometry]) -> void: + if node is ModularBuilderPieceGeometry and node.piece_type == PieceType.WALL: + walls.append(node) + for child in node.get_children(): + _collect_centering_walls(child, walls) + +func _append_solid_panel(vertices: PackedVector3Array, normals: PackedVector3Array, panel: Array) -> void: + if panel.size() < 3: + return + var p0: Vector3 = panel[0] + var p1: Vector3 = panel[1] + var p2: Vector3 = panel[2] + var normal: Vector3 = (p1 - p0).cross(p2 - p0).normalized() + if normal.is_zero_approx(): + return + var half_depth: Vector3 = normal * thickness * 0.5 + var front: Array[Vector3] = [] + var back: Array[Vector3] = [] + for point in panel: + front.append(point + half_depth) + back.append(point - half_depth) + for i in range(1, panel.size() - 1): + _add_tri(vertices, normals, front[0], front[i], front[i + 1]) + _add_tri(vertices, normals, back[i + 1], back[i], back[0]) + for i in range(panel.size()): + var next := (i + 1) % panel.size() + _add_quad(vertices, normals, front[i], back[i], back[next], front[next]) + +func _make_custom_wall_mesh() -> ArrayMesh: + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var wall_points: PackedVector3Array = _get_centered_wall_points() if center_align else custom_points + var a: Vector3 = wall_points[0] + var b: Vector3 = wall_points[1] + var direction := (b - a).normalized() + var length := a.distance_to(b) + var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5 + var height := Vector3.UP * unit_size + var openings: Array[PackedVector2Array] = [] + for child in get_children(): + if _is_detail_node(child) and int(child.get("detail_type")) != DETAIL_TYPE_HATCH and int(child.get("detail_type")) != DETAIL_TYPE_PILLAR: + var detail := child as Node3D + var center := Vector2((detail.position - a).dot(direction), detail.position.y - a.y) + var axis_x := Vector2(detail.transform.basis.x.dot(direction), detail.transform.basis.x.y).normalized() + var axis_y := Vector2(detail.transform.basis.y.dot(direction), detail.transform.basis.y.y).normalized() + var opening := PackedVector2Array([ + center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5, + center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5, + center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5, + center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5 + ]) + var wall_rect := PackedVector2Array([Vector2(0.0, 0.0), Vector2(length, 0.0), Vector2(length, unit_size), Vector2(0.0, unit_size)]) + for clipped_opening in Geometry2D.intersect_polygons(opening, wall_rect): + if clipped_opening.size() >= 3: + openings.append(clipped_opening) + var xs: Array[float] = [0.0, length] + var ys: Array[float] = [0.0, unit_size] + for opening in openings: + for point in opening: + xs.append(clampf(point.x, 0.0, length)) + ys.append(clampf(point.y, 0.0, unit_size)) + xs.sort() + ys.sort() + for xi in range(xs.size() - 1): + for yi in range(ys.size() - 1): + var u0: float = xs[xi] + var u1: float = xs[xi + 1] + var v0: float = ys[yi] + var v1: float = ys[yi + 1] + var mid := Vector2((u0 + u1) * 0.5, (v0 + v1) * 0.5) + var cell := PackedVector2Array([Vector2(u0, v0), Vector2(u1, v0), Vector2(u1, v1), Vector2(u0, v1)]) + var remaining: Array[PackedVector2Array] = [cell] + for opening in openings: + var next_remaining: Array[PackedVector2Array] = [] + for polygon in remaining: + for clipped in Geometry2D.clip_polygons(polygon, opening): + if clipped.size() >= 3: + next_remaining.append(clipped) + remaining = next_remaining + for polygon in remaining: + var indices := Geometry2D.triangulate_polygon(polygon) + for ti in range(0, indices.size(), 3): + var p0: Vector2 = polygon[indices[ti]] + var p1: Vector2 = polygon[indices[ti + 1]] + var p2: Vector2 = polygon[indices[ti + 2]] + var f0 := a + direction * p0.x + Vector3.UP * p0.y + var f1 := a + direction * p1.x + Vector3.UP * p1.y + var f2 := a + direction * p2.x + Vector3.UP * p2.y + _add_tri(vertices, normals, f0 + side, f1 + side, f2 + side) + _add_tri(vertices, normals, f2 - side, f1 - side, f0 - side) + # Close the wall body at its ends, top, and base. + _add_quad(vertices, normals, a - side, a + side, a + side + height, a - side + height) + _add_quad(vertices, normals, b + side, b - side, b - side + height, b + side + height) + _add_quad(vertices, normals, a + side + height, b + side + height, b - side + height, a - side + height) + _add_quad(vertices, normals, a - side, b - side, b + side, a + side) + for opening in openings: + for edge_index in range(opening.size()): + var p0 := opening[edge_index] + var p1 := opening[(edge_index + 1) % opening.size()] + var a0 := a + direction * p0.x + Vector3.UP * p0.y + var a1 := a + direction * p1.x + Vector3.UP * p1.y + _add_quad(vertices, normals, a0 + side, a0 - side, a1 - side, a1 + side) + return _array_mesh(vertices, normals) + +func _get_centered_wall_points() -> PackedVector3Array: + if custom_points.size() < 2: + return custom_points + var selected_a := to_global(custom_points[0]) + var selected_b := to_global(custom_points[1]) + var search_root: Node = get_parent() + while search_root != null and not search_root is ModularBuilderPieceGeometry: + search_root = search_root.get_parent() + if search_root == null: + return custom_points + var walls: Array[ModularBuilderPieceGeometry] = [] + _collect_centering_walls(search_root, walls) + var centered_a := selected_a + var centered_b := selected_b + var nearest_a := INF + var nearest_b := INF + for wall in walls: + if wall == self or wall.custom_points.size() < 2: + continue + var direction := (wall.custom_points[1] - wall.custom_points[0]).normalized() + var side := Vector3(-direction.z, 0.0, direction.x).normalized() * wall.thickness * 0.5 + for endpoint in wall.custom_points: + var center := wall.to_global(endpoint + Vector3.UP * wall.unit_size) + for surface_corner in [center + side, center - side]: + var distance_a := selected_a.distance_to(surface_corner) + if distance_a < nearest_a and distance_a <= wall.thickness + 0.04: + nearest_a = distance_a + centered_a = center + var distance_b := selected_b.distance_to(surface_corner) + if distance_b < nearest_b and distance_b <= wall.thickness + 0.04: + nearest_b = distance_b + centered_b = center + if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02: + return PackedVector3Array([to_local(centered_a), to_local(centered_b)]) + return custom_points + +func _make_custom_floor_mesh() -> ArrayMesh: + var polygon: Array[Vector2] = [] + for point in custom_points: + polygon.append(Vector2(point.x, point.z)) + var hatch_mesh := _make_hatch_surface_mesh(polygon, custom_points[0].y, custom_points[0].y - thickness) + if hatch_mesh != null: + return hatch_mesh + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var points := custom_points + var bottom: Array[Vector3] = [] + for point in points: + bottom.append(point - Vector3.UP * thickness) + for i in range(1, points.size() - 1): + _add_tri(vertices, normals, points[0], points[i + 1], points[i]) + _add_tri(vertices, normals, bottom[0], bottom[i], bottom[i + 1]) + for i in range(points.size()): + var j := (i + 1) % points.size() + _add_quad(vertices, normals, bottom[i], bottom[j], points[j], points[i]) + return _array_mesh(vertices, normals) + +func _make_hatch_surface_mesh(polygon: Array[Vector2], top_y: float, bottom_y: float) -> ArrayMesh: + var openings: Array[PackedVector2Array] = [] + for child in get_children(): + if _is_detail_node(child) and int(child.get("detail_type")) == DETAIL_TYPE_HATCH: + var detail := child as Node3D + var center := Vector2(detail.position.x, detail.position.z) + var axis_x := Vector2(detail.transform.basis.x.x, detail.transform.basis.x.z).normalized() + var axis_y := Vector2(detail.transform.basis.z.x, detail.transform.basis.z.z).normalized() + var opening := PackedVector2Array([ + center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5, + center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5, + center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5, + center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5 + ]) + openings.append(opening) + if openings.is_empty() or polygon.size() < 3: + return null + var min_x := polygon[0].x + var max_x := polygon[0].x + var min_z := polygon[0].y + var max_z := polygon[0].y + var xs: Array[float] = [] + var zs: Array[float] = [] + for point in polygon: + min_x = minf(min_x, point.x) + max_x = maxf(max_x, point.x) + min_z = minf(min_z, point.y) + max_z = maxf(max_z, point.y) + xs.append(point.x) + zs.append(point.y) + for opening in openings: + for point in opening: + xs.append(clampf(point.x, min_x, max_x)) + zs.append(clampf(point.y, min_z, max_z)) + xs.sort() + zs.sort() + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + var source_polygon := PackedVector2Array() + for point in polygon: + source_polygon.append(point) + for xi in range(xs.size() - 1): + for zi in range(zs.size() - 1): + var x0: float = xs[xi] + var x1: float = xs[xi + 1] + var z0: float = zs[zi] + var z1: float = zs[zi + 1] + if x1 - x0 < 0.0001 or z1 - z0 < 0.0001: + continue + var cell := PackedVector2Array([Vector2(x0, z0), Vector2(x1, z0), Vector2(x1, z1), Vector2(x0, z1)]) + var remaining: Array[PackedVector2Array] = [] + for clipped_surface in Geometry2D.intersect_polygons(source_polygon, cell): + remaining.append(clipped_surface) + for opening in openings: + var next_remaining: Array[PackedVector2Array] = [] + for surface_polygon in remaining: + for clipped_surface in Geometry2D.clip_polygons(surface_polygon, opening): + if clipped_surface.size() >= 3: + next_remaining.append(clipped_surface) + remaining = next_remaining + for clipped_surface in remaining: + var triangulated := Geometry2D.triangulate_polygon(clipped_surface) + for ti in range(0, triangulated.size(), 3): + var a: Vector2 = clipped_surface[triangulated[ti]] + var b: Vector2 = clipped_surface[triangulated[ti + 1]] + var c: Vector2 = clipped_surface[triangulated[ti + 2]] + _add_tri(vertices, normals, Vector3(a.x, top_y, a.y), Vector3(c.x, top_y, c.y), Vector3(b.x, top_y, b.y)) + _add_tri(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(c.x, bottom_y, c.y)) + for i in range(polygon.size()): + var j := (i + 1) % polygon.size() + var a := polygon[i] + var b := polygon[j] + _add_quad(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(b.x, top_y, b.y), Vector3(a.x, top_y, a.y)) + for opening in openings: + var visible_edges := Geometry2D.intersect_polygons(opening, source_polygon) + for visible_opening in visible_edges: + for edge_index in range(visible_opening.size()): + var p0 := visible_opening[edge_index] + var p1 := visible_opening[(edge_index + 1) % visible_opening.size()] + _add_quad(vertices, normals, Vector3(p0.x, bottom_y, p0.y), Vector3(p0.x, top_y, p0.y), Vector3(p1.x, top_y, p1.y), Vector3(p1.x, bottom_y, p1.y)) + return _array_mesh(vertices, normals) + +func _is_detail_node(node: Node) -> bool: + if not node is Node3D or node.get_script() == null: + return false + return node.get_script().resource_path == DETAIL_SCRIPT_PATH + +func _make_custom_foundation_mesh() -> ArrayMesh: + var polygon: Array[Vector2] = [] + for point in custom_points: + polygon.append(Vector2(point.x, point.z)) + var bottom_y := -thickness * 0.5 if centered_origin else 0.0 + var top_y := thickness * 0.5 if centered_origin else thickness + var hatch_mesh := _make_hatch_surface_mesh(polygon, top_y, bottom_y) + if hatch_mesh != null: + return hatch_mesh + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + for i in range(1, custom_points.size() - 1): + var a := custom_points[0] + var b := custom_points[i] + var c := custom_points[i + 1] + _add_tri(vertices, normals, Vector3(a.x, top_y, a.z), Vector3(c.x, top_y, c.z), Vector3(b.x, top_y, b.z)) + _add_tri(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(c.x, bottom_y, c.z)) + for i in range(custom_points.size()): + var j := (i + 1) % custom_points.size() + var a := custom_points[i] + var b := custom_points[j] + _add_quad(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(b.x, top_y, b.z), Vector3(a.x, top_y, a.z)) + return _array_mesh(vertices, normals) + +func _add_quad(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> void: + _add_tri(v, n, a, b, c) + _add_tri(v, n, a, c, d) + +func _array_mesh(v: PackedVector3Array, n: PackedVector3Array) -> ArrayMesh: + var arrays: Array = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = v + arrays[Mesh.ARRAY_NORMAL] = n + var result := ArrayMesh.new() + result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + return result + +func _add_tri(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3) -> void: + var normal := (b - a).cross(c - a).normalized() + v.append(a) + v.append(b) + v.append(c) + n.append(normal) + n.append(normal) + n.append(normal) + # Also generate reversed faces to show both sides. + # This keeps floors and foundations visible from below with any material. + v.append(c) + v.append(b) + v.append(a) + n.append(-normal) + n.append(-normal) + n.append(-normal) diff --git a/vendor/modular_builder/modular_piece_geometry.gd.uid b/vendor/modular_builder/modular_piece_geometry.gd.uid new file mode 100644 index 0000000..a2905f8 --- /dev/null +++ b/vendor/modular_builder/modular_piece_geometry.gd.uid @@ -0,0 +1 @@ +uid://jkyypf7girrd diff --git a/viewport/gizmo.gd b/viewport/gizmo.gd new file mode 100644 index 0000000..4fa020a --- /dev/null +++ b/viewport/gizmo.gd @@ -0,0 +1,628 @@ +extends Node3D +## Achsen-Gizmo (Karte t_e0120dc2): drei farbige Pfeile (X rot, Y grün, +## Z blau) am EINZELN markierten Objekt. Zieht man einen Pfeil, bewegt sich +## das Objekt GENAU entlang dieser Achse — die Zahlen im Objekt-Inspektor +## laufen LIVE mit. Mausrad WÄHREND des Ziehens wechselt die Schrittweite +## (fein/normal/grob); derselbe Wert steht als „Schrittweite“-Feld im +## rechten Panel (main.gd). +## +## Regeln der Karte: +## - NUR Einzelauswahl (Mehrfachauswahl bekommt kein Gizmo — Folgekarte). +## - JEDER Zug = EIN Undo-Schritt (Commit beim Loslassen). Esc bricht EXAKT +## auf die Startposition zurück (byte-identisch, wie grab_cancel). +## - Zieh-Werte rasten auf Vielfache der Schrittweite (Voreinstellungen +## 0,1 / 0,5 / 2 m; das Feld im Panel nimmt jeden Wert 0,01–10 m). +## - Klemmen wie der Inspektor: X/Z an der Map-Grenze, Y nie unter dem +## Terrain (X/Z-Zug folgt dem Boden wie der normale Objekt-Zug) und +## oben an der Höhen-Grenze. +## +## Eingabe-Verdrahtung: dieses Gizmo hängt als KIND an interaction.gd — +## Knoten bekommen _unhandled_input TIEFER IM BAUM ZUERST, so fängt es +## Pfeil-Klicks ab, BEVOR die Auswahl-/Zieh-Logik des Parents sie sieht. +## interaction.gd bleibt dadurch UNVERÄNDERT (Karten-Grenze) und der +## bestehende Klick-/Zieh-Pfad wird 1:1 wiederverwendet. + +const SvmapDocument := preload("res://core/svmap/map_document.gd") + +const AXIS_LEN := 2.2 # Pfeil-Länge in Metern (Weltmaß) +const SHAFT_R := 0.045 +const CONE_H := 0.45 +const CONE_R := 0.13 +const HIT_PX := 18.0 # Treffer-Toleranz am Bildschirm (Pixel) +const MIN_PX_PER_M := 10.0 # Zieh-Sensitivität deckeln (Achse fast im Blick) +const STEP_MIN := 0.01 +const STEP_MAX := 10.0 + +const STEP_PRESETS := [0.1, 0.5, 2.0] +const STEP_PRESET_NAMES := ["fein", "normal", "grob"] + +const AXES := { + "x": Vector3(1.0, 0.0, 0.0), + "y": Vector3(0.0, 1.0, 0.0), + "z": Vector3(0.0, 0.0, 1.0), +} +const AXIS_COLORS := { + "x": Color(1.0, 0.25, 0.25), + "y": Color(0.3, 0.95, 0.35), + "z": Color(0.35, 0.55, 1.0), +} + +var main # core/ui/main.gd (script-erweitert) +var camera: Camera3D +var view_builder # core/viewport/map_view_builder.gd + +var step_m := 0.5 # Schrittweite (m) — Panel-Feld + Mausrad-Presets + +# --- Zieh-Zustand --- +var _dragging := false +var _drag_idx := -1 +var _drag_axis := "" +var _drag_axis_vec := Vector3.ZERO +var _press_screen := Vector2.ZERO +var _screen_axis := Vector2.RIGHT # Achse am Bildschirm (normalisiert, px) +var _px_per_m := 1.0 +var _start_pos := Vector3.ZERO # doc-Position beim Zieh-Start +var _start_saved: Array = [] # exakte Start-Position für Esc-Rückabwicklung +var _drag_lift := 0.0 # Boden-Abstand (X/Z-Zug folgt dem Terrain) +var _drag_moved := false +var _last_delta := 0.0 # zuletzt angewendeter Delta (m) — 0 = kein Commit +var _drag_before: Dictionary = {} # History-Snapshot VOR dem Zug + +var _arrows := {} # "x"/"y"/"z" → Node3D (Pfeil-Root) + +# --- Dreh-/Kipp-Modus (25.10.): Ringe um alle drei Achsen ------------------- +const RING_R := 1.5 # Ring-Radius in Metern +const RING_SEGMENTS := 48 +const ROT_STEP_DEG := 15.0 +var mode := "move" # "move" = Pfeile, "rotate" = Ringe +var rot_step_deg := ROT_STEP_DEG +var _rings := {} # "x"/"y"/"z" → MeshInstance3D +var _rot_dragging := false +var _rot_axis := "" +var _rot_center := Vector2.ZERO +var _rot_start_angle := 0.0 +var _rot_applied := 0.0 # bisher angewendete (gerastete) Grad +var _rot_suffix := "" # merge-Kennung: EIN Undo-Schritt pro Zug +var _rot_serial := 0 + + +func setup(p_main, p_camera: Camera3D, p_builder) -> void: + main = p_main + camera = p_camera + view_builder = p_builder + _build_arrows() + _build_rings() + _apply_mode_visibility() + visible = false + + +## Pfeile bauen: Schaft (Zylinder) + Spitze (Kegel), ungeschattet und OHNE +## Tiefentest — die Pfeile bleiben auch vor/über Objekten gut sichtbar. +func _build_arrows() -> void: + for a in AXES: + var root := Node3D.new() + # Alle Pfeile zeigen lokal +Y; der Root dreht sie auf die Welt-Achse. + if a == "x": + root.rotation.z = -PI / 2.0 # +Y → +X + elif a == "z": + root.rotation.x = PI / 2.0 # +Y → +Z + var mat := StandardMaterial3D.new() + mat.albedo_color = AXIS_COLORS[a] + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.no_depth_test = true + mat.render_priority = 20 + var shaft := MeshInstance3D.new() + var cyl := CylinderMesh.new() + cyl.top_radius = SHAFT_R + cyl.bottom_radius = SHAFT_R + cyl.height = AXIS_LEN + shaft.mesh = cyl + shaft.material_override = mat + shaft.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + shaft.position.y = AXIS_LEN * 0.5 + root.add_child(shaft) + var tip := MeshInstance3D.new() + var cone := CylinderMesh.new() + cone.top_radius = 0.0 + cone.bottom_radius = CONE_R + cone.height = CONE_H + tip.mesh = cone + tip.material_override = mat + tip.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + tip.position.y = AXIS_LEN + CONE_H * 0.5 + root.add_child(tip) + add_child(root) + _arrows[a] = root + + +## Drehringe bauen: je Achse ein dünner Torus, ungeschattet, ohne Tiefentest. +func _build_rings() -> void: + for a in AXES: + var mat := StandardMaterial3D.new() + mat.albedo_color = AXIS_COLORS[a] + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.no_depth_test = true + mat.render_priority = 20 + var ring := MeshInstance3D.new() + var torus := TorusMesh.new() + torus.inner_radius = RING_R - 0.06 + torus.outer_radius = RING_R + 0.06 + torus.rings = 48 + torus.ring_segments = 8 + ring.mesh = torus + ring.material_override = mat + ring.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + # Torus liegt lokal in der XZ-Ebene (Achse = Y). + if a == "x": + ring.rotation.z = PI / 2.0 + elif a == "z": + ring.rotation.x = PI / 2.0 + ring.visible = false + add_child(ring) + _rings[a] = ring + + +## Modus wechseln ("move" = Verschieben, "rotate" = Drehen/Kippen). +func set_mode(m: String) -> void: + if m != "move" and m != "rotate": + return + if _dragging: + _finish_drag(false) + if _rot_dragging: + _finish_rotate(false) + mode = m + var inter = main.get("interaction") if main != null else null + if inter != null: + inter.no_drag_move = (m == "rotate") + _apply_mode_visibility() + + +func _apply_mode_visibility() -> void: + for a in _arrows: + (_arrows[a] as Node3D).visible = mode == "move" + for a in _rings: + (_rings[a] as Node3D).visible = mode == "rotate" + + +func _axis_vec(a: String) -> Vector3: + return AXES.get(a, Vector3.ZERO) as Vector3 + + +## Sichtbarkeit + Position JEDES Frames aus der Auswahl ableiten (kein +## Verdrahten jedes Auswahl-Wegs nötig — Auswahl kommt aus interaction.gd, +## Baum, Undo/Redo, Inspektor … alles landet automatisch hier). +func _process(_delta: float) -> void: + if main == null or view_builder == null: + return + var inter = main.get("interaction") + if inter == null: + return + var sel: Array = inter.selected_indices + if sel.size() != 1: + visible = false + return + if bool(inter.get("placing")): + visible = false # Ghost-Platzieren: keine Pfeile im Weg + return + var idx: int = int(inter.selected_index) + var markers: Array = view_builder.object_markers + if idx < 0 or idx >= markers.size(): + visible = false + return + var marker = markers[idx] + if marker == null or not is_instance_valid(marker): + visible = false + return + visible = true + global_position = marker.global_position + + +func _unhandled_input(event: InputEvent) -> void: + var p := MousePointer.from_input(event) + if p != null and handle_pointer(p): + get_viewport().set_input_as_handled() + + +func handle_pointer(p: PointerEvent) -> bool: + if main == null or camera == null: + return false + var doc: Dictionary = main.get("doc") + if doc == null or doc.is_empty(): + return false + var tool_mode: String = str(main.get("tool_mode")) + if tool_mode == "terrain" or tool_mode == "spawns": + return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter + match p.kind: + PointerEvent.Kind.PRESS: + if _dragging: + return false + var inter = main.get("interaction") + if inter == null or bool(inter.get("placing")): + return false + if mode == "rotate": + var ring_axis := pick_ring(p.position) + if ring_axis != "": + _begin_rotate(ring_axis, p.position) + return true + return false + var axis := pick(p.position) + if axis != "": + _begin_drag(axis, p.position) + return true + PointerEvent.Kind.RELEASE: + if _rot_dragging: + _finish_rotate(true) + return true + if _dragging: + _finish_drag(true) + return true + PointerEvent.Kind.SCROLL: + if _rot_dragging: + return true # Mausrad zoomt nicht mitten im Dreh-Zug + if _dragging: + cycle_step(p.scroll) + return true + PointerEvent.Kind.MOVE: + if _rot_dragging: + _rotate_motion(p.position) + return true + if _dragging: + _drag_motion(p.position) + return true + PointerEvent.Kind.CANCEL: + if _rot_dragging: + _finish_rotate(false) + return true + if _dragging: + _finish_drag(false) + return true + return false + + +# ------------------------------------------------------------- Treffer ----- + +## Achse unter dem Cursor (Bildschirm-Segment-Distanz) — "" = kein Pfeil. +func pick(screen_pos: Vector2) -> String: + if not visible: + return "" + var o := _safe_unproject(global_position) + if o == Vector2.INF: + return "" + var best := "" + var best_d := HIT_PX + for a in AXES: + var t := _safe_unproject(global_position + _axis_vec(a) * AXIS_LEN) + if t == Vector2.INF: + continue + var d := _dist_point_segment(screen_pos, o, t) + if d < best_d: + best_d = d + best = a + return best + + +## Ring unter dem Cursor (Abstand zum projizierten Kreis) — "" = keiner. +func pick_ring(screen_pos: Vector2) -> String: + if not visible or mode != "rotate": + return "" + var best := "" + var best_d := HIT_PX * 1.6 + for a in AXES: + var pts := _ring_screen_points(a) + for i in pts.size() - 1: + if pts[i] == Vector2.INF or pts[i + 1] == Vector2.INF: + continue + var d := _dist_point_segment(screen_pos, pts[i], pts[i + 1]) + if d < best_d: + best_d = d + best = a + return best + + +func _ring_screen_points(a: String) -> Array: + var axis := _axis_vec(a) + var u := axis.cross(Vector3.UP if absf(axis.dot(Vector3.UP)) < 0.9 else Vector3.RIGHT).normalized() + var v := axis.cross(u).normalized() + var out: Array = [] + for i in RING_SEGMENTS + 1: + var t := TAU * float(i) / float(RING_SEGMENTS) + out.append(_safe_unproject(global_position + (u * cos(t) + v * sin(t)) * RING_R)) + return out + + +func _safe_unproject(p: Vector3) -> Vector2: + if camera == null: + return Vector2.INF + if camera.is_position_behind(p): + return Vector2.INF + return camera.unproject_position(p) + + +func _dist_point_segment(p: Vector2, a: Vector2, b: Vector2) -> float: + var ab := b - a + var len_sq := ab.length_squared() + if len_sq < 0.0001: + return p.distance_to(a) + var t := clampf((p - a).dot(ab) / len_sq, 0.0, 1.0) + return p.distance_to(a + ab * t) + + +# -------------------------------------------------------------- Ziehen ----- + +func _begin_drag(axis: String, mb_pos: Vector2) -> void: + if not _begin_drag_state(axis): + return + # Bildschirm-Mathe: Achse in Pixeln → Zieh-Sensitivität (Meter pro Pixel). + var markers: Array = view_builder.object_markers + var marker = markers[_drag_idx] + var origin: Vector3 = marker.global_position + var o := _safe_unproject(origin) + var t := _safe_unproject(origin + _drag_axis_vec * AXIS_LEN) + if o == Vector2.INF or t == Vector2.INF: + _dragging = false + return + var seg := t - o + var seg_len := seg.length() + if seg_len < 1.0: + _dragging = false + return + _screen_axis = seg.normalized() + _px_per_m = maxf(seg_len / AXIS_LEN, MIN_PX_PER_M) + _press_screen = mb_pos + main.call("_set_status", "Gizmo-Zug: entlang %s ziehen — Mausrad wechselt die Schrittweite (%.2f m), Esc bricht ab." % [ + _drag_axis.to_upper(), step_m]) + + +## Zieh-Zustand NUR aus dem Doc/Marke r aufsetzen (ohne Bildschirm-Mathe — +## auch für den Demo-/Test-Hook demo_drag_axis). +func _begin_drag_state(axis: String) -> bool: + var inter = main.get("interaction") + if inter == null or inter.selected_indices.size() != 1: + return false + var idx: int = int(inter.selected_index) + var markers: Array = view_builder.object_markers + if idx < 0 or idx >= markers.size(): + return false + var marker = markers[idx] + if marker == null or not is_instance_valid(marker): + return false + var objects: Array = main.doc.get("objects", []) + if idx >= objects.size(): + return false + var pos: Array = (objects[idx] as Dictionary).get("pos", [0.0, 0.0, 0.0]) + _drag_idx = idx + _drag_axis = axis + _drag_axis_vec = _axis_vec(axis) + _press_screen = Vector2.ZERO + _start_pos = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + _start_saved = (pos as Array).duplicate() + _drag_lift = marker.position.y - SvmapDocument.terrain_height_at( + main.doc, _start_pos.x, _start_pos.z) + _drag_before = main.history.snapshot(["objects"]) + _drag_moved = false + _last_delta = 0.0 + _dragging = true + return true + + +func _drag_motion(mb_pos: Vector2) -> void: + var d := mb_pos - _press_screen + var raw := d.dot(_screen_axis) / _px_per_m + _apply_axis_delta(snappedf(raw, step_m)) + + +## Delta entlang der Achse anwenden: doc + Marke + Gizmo, Inspektor LIVE. +func _apply_axis_delta(delta: float) -> void: + if not _dragging or absf(delta - _last_delta) < 0.0005: + return + var objects: Array = main.doc.get("objects", []) + if _drag_idx >= objects.size(): + _finish_drag(false) + return + var np := _start_pos + _drag_axis_vec * delta + var half := _terrain_half() + np.x = clampf(np.x, -half.x, half.x) + np.z = clampf(np.z, -half.y, half.y) + if _drag_axis == "y": + np.y = clampf(np.y, SvmapDocument.terrain_height_at(main.doc, np.x, np.z), _max_y()) + else: + # X/Z-Zug folgt dem Boden (gleiches Verhalten wie der normale Zug): + # der Boden-Abstand bleibt erhalten, nichts begräbt sich. + np.y = SvmapDocument.terrain_height_at(main.doc, np.x, np.z) + _drag_lift + var obj: Dictionary = objects[_drag_idx] + obj["pos"] = [np.x, np.y, np.z] + var markers: Array = view_builder.object_markers + if _drag_idx < markers.size(): + var marker = markers[_drag_idx] + if marker != null and is_instance_valid(marker): + marker.position = np + global_position = np + _last_delta = delta + _drag_moved = true + main.call("_sync_selection_panel") # Inspektor-Zahlen LIVE mitziehen + + +## Zug beenden: commit = true → EIN Undo-Schritt (wirklich bewegt), +## commit = false → exakte Rückabwicklung, KEIN History-Eintrag. +func _finish_drag(commit: bool) -> void: + if not _dragging: + return + _dragging = false + var objects: Array = main.doc.get("objects", []) + var idx := _drag_idx + if commit and absf(_last_delta) > 0.0005 and idx >= 0 and idx < objects.size(): + var desc: String + match _drag_axis: + "x": + desc = "Objekt entlang X verschoben (Gizmo)" + "y": + desc = "Objekt angehoben/abgesenkt (Gizmo, Y-Achse)" + _: + desc = "Objekt entlang Z verschoben (Gizmo)" + main.history.commit(desc, _drag_before, main.history.snapshot(["objects"])) + main.set("dirty", true) + main.call("_refresh_file_label") + main.call("_refresh_history_buttons") + main.call("_refresh_stats") + main.call("_sync_selection_panel") + main.call("_set_status", "%s — %.2f m. Strg+Z nimmt den Zug zurück; Mausrad WÄHREND des Ziehens wechselt fein/grob." % [ + desc, absf(_last_delta)]) + elif not commit and idx >= 0 and idx < objects.size(): + (objects[idx] as Dictionary)["pos"] = _start_saved.duplicate() + var markers: Array = view_builder.object_markers + if idx < markers.size(): + var marker = markers[idx] + if marker != null and is_instance_valid(marker): + marker.position = _start_pos + global_position = _start_pos + main.call("_sync_selection_panel") + main.call("_set_status", "Gizmo-Zug abgebrochen — Position wie vorher.") + _drag_before = {} + _drag_idx = -1 + _drag_axis = "" + _drag_moved = false + _last_delta = 0.0 + + +## Für die Esc-Kette in main.gd: Zug abbrechen, falls einer läuft. true = +## es lief einer und wurde abgebrochen (idempotent, weil _dragging prüft). +func cancel_drag_if_active() -> bool: + if _rot_dragging: + _finish_rotate(false) + return true + if not _dragging: + return false + _finish_drag(false) + return true + + +# --------------------------------------------------- Drehen / Kippen ------- + +func _ring_angle(mouse: Vector2) -> float: + var d := mouse - _rot_center + return atan2(d.y, d.x) + + +func _begin_rotate(axis: String, mouse: Vector2) -> void: + var inter = main.get("interaction") + if inter == null or inter.selected_indices.is_empty(): + return + var c := _safe_unproject(global_position) + if c == Vector2.INF: + return + _rot_axis = axis + _rot_center = c + _rot_start_angle = _ring_angle(mouse) + _rot_applied = 0.0 + _rot_serial += 1 + _rot_suffix = "#g%d" % _rot_serial + _rot_dragging = true + var name := "Drehen (Y)" if axis == "y" else ("Kippen um X" if axis == "x" else "Kippen um Z") + main.call("_set_status", "%s — Ring ziehen, rastet in %.0f°-Schritten; Esc bricht ab, Strg+Z nimmt den Zug zurück." % [name, rot_step_deg]) + + +func _rotate_motion(mouse: Vector2) -> void: + # Bildschirmwinkel → Drehwinkel um die Achse (Vorzeichen je nach Blickrichtung). + var delta_screen := wrapf(_ring_angle(mouse) - _rot_start_angle, -PI, PI) + var facing := _axis_vec(_rot_axis).dot(camera.global_transform.basis.z) + var deg := -rad_to_deg(delta_screen) * (1.0 if facing >= 0.0 else -1.0) + var snapped_deg := snappedf(deg, rot_step_deg) + _rotate_to(snapped_deg) + + +## Auf einen Gesamtwinkel (gerastet) nachführen — in Schritten, EIN Undo-Eintrag. +func _rotate_to(total_deg: float) -> void: + var diff := total_deg - _rot_applied + if absf(diff) < 0.001: + return + var inter = main.get("interaction") + if inter == null: + return + if _rot_axis == "y": + inter.rotate_selected(diff, _rot_suffix) + else: + inter.tilt_selected(_rot_axis == "x", diff, _rot_suffix) + _rot_applied = total_deg + + +func _finish_rotate(commit: bool) -> void: + if not _rot_dragging: + return + _rot_dragging = false + if not commit and absf(_rot_applied) > 0.001: + main.call("_do_undo") # der ganze Zug ist EIN Eintrag → komplett zurück + main.call("_set_status", "Dreh-Zug abgebrochen — Objekt wie vorher.") + _rot_axis = "" + _rot_applied = 0.0 + + +## Probes/Tests: derselbe Pfad wie ein echter Ring-Zug (ohne Maus-Mathe). +func demo_rotate_axis(axis: String, deg: float) -> bool: + var inter = main.get("interaction") if main != null else null + if inter == null or inter.selected_indices.is_empty() or absf(deg) < 0.001: + return false + _rot_axis = axis + _rot_serial += 1 + _rot_suffix = "#g%d" % _rot_serial + _rot_applied = 0.0 + _rotate_to(snappedf(deg, rot_step_deg)) + _rot_axis = "" + _rot_applied = 0.0 + return true + + +# ------------------------------------------------------ Schrittweite ------- + +## Mausrad WÄHREND des Ziehens: durch die Voreinstellungen blättern +## (fein 0,1 → normal 0,5 → grob 2,0 m, wrappt). Das Panel-Feld folgt. +func cycle_step(dir: int) -> void: + var idx := STEP_PRESETS.find(step_m) + if idx < 0: + idx = STEP_PRESETS.size() - 1 # eigener Feld-Wert: vom größten Preset starten + idx = wrapi(idx + dir, 0, STEP_PRESETS.size()) + set_step_m(STEP_PRESETS[idx]) + main.call("_set_status", "Gizmo-Schrittweite: %.2f m (%s) — Mausrad wechselt weiter, das Feld rechts setzt sie exakt." % [ + step_m, STEP_PRESET_NAMES[idx]]) + + +## Feld-Wert übernehmen (Panel-Feld ODER Mausrad-Preset). Haupt-Var hält den +## Wert über UI-Rebuilds (UIScale) hinweg. +func set_step_m(v: float) -> void: + step_m = clampf(v, STEP_MIN, STEP_MAX) + if main != null: + main.set("gizmo_step_m", step_m) + + +# ------------------------------------------------------- Demo-/Test-Hook --- + +## Probes/Tests: denselben Anwendungs- + Commit-Pfad wie ein echter Zug, +## ohne Bildschirm-Mathe. true = es wurde wirklich bewegt + committed. +func demo_drag_axis(axis: String, meters: float) -> bool: + if main == null: + return false + var applied := snappedf(meters, step_m) + if absf(applied) < 0.0005: + return false + if not _begin_drag_state(axis): + return false + _apply_axis_delta(applied) + _finish_drag(true) + return true + + +# ------------------------------------------------------------ Helfer ------- + +func _terrain_half() -> Vector2: + var terrain: Dictionary = main.doc.get("terrain", {}) + return Vector2(float(terrain.get("size_x_m", 1024.0)), + float(terrain.get("size_z_m", 1024.0))) * 0.5 + + +func _max_y() -> float: + var bounds: Dictionary = main.doc.get("bounds", {}) + var mx = bounds.get("max", null) + if mx is Array and (mx as Array).size() == 3: + return float((mx as Array)[1]) + return 80.0 diff --git a/viewport/gizmo.gd.uid b/viewport/gizmo.gd.uid new file mode 100644 index 0000000..c114071 --- /dev/null +++ b/viewport/gizmo.gd.uid @@ -0,0 +1 @@ +uid://bchu6tv6aetfh diff --git a/viewport/glb_mesh_store.gd b/viewport/glb_mesh_store.gd new file mode 100644 index 0000000..e4f3eda --- /dev/null +++ b/viewport/glb_mesh_store.gd @@ -0,0 +1,450 @@ +extends RefCounted +class_name GlbMeshStore +## Echte GLB-Renderings für Bibliotheks-Objekte im Editor-Viewport +## (Karte t_9b50b231). Der Viewport-Bauer zeigt bisher nur den +## Kollisions-Platzhalter (MeshShapes.make_placeholder) — diese statische +## Loader-Schicht holt stattdessen das ECHTE Modell aus dem Mesh-Lager +## (Sibling-Repos, gleiche Quellen wie tools/gen_thumbs.gd) und fällt ohne +## GLB auf den Platzhalter zurück. +## +## Messkonvention wie addons/modular_builder/scopedvr_export.gd +## `normalisiere`: das geladene Mesh wird in Fußpunkt-Koordinaten gebacken — +## Unterkante auf y = 0, XZ um den Mittelpunkt zentriert. Pack-GLBs haben +## ihren Mesh-Ursprung oft in der Mitte, deshalb wird die AABB-Unterkante +## GERECHNET und nie blind platziert. Damit gilt wie beim Compound-Platzhalter: +## MeshShapes.half_height(mesh) == 0 und marker.position == Fußpunkt. +## +## Plattform-Regel (PLAN.md): nur relative Kandidaten-Pfade, konfigurierbar +## über ProjectSettings — läuft identisch auf Linux und Windows. +## +## Performance: EIN Parse pro OID, Ergebnis gecacht — der Rebuild bei jedem +## Platzieren/Undo liest nur noch aus dem Cache. + +## ProjectSettings-Schlüssel für die GLB-Quellen (relativ zum Editor-Projekt). +const QUELLEN_SETTING := "scopedvr/editor/glb_quellen" +## Voreinstellung = dieselben Quellen wie tools/gen_thumbs.gd (Sibling-Repos). +const DEFAULT_QUELLEN := ["../scopedvr-match-server/assets/objekte", "../scopedvr-client/assets/objekte"] +## Mitgelieferter Modell-Ordner (Karte t_12cf94fe): direkt neben der App +## (im Editor = Projektordner, in der exportierten App = neben der EXE). +## Der sync über tools/sync_models.sh legt hier die GLBs + Pack-Texturen +## ab — dann läuft der Map Builder auf einem fremden PC OHNE Sibling-Repos. +## Der Ordner trägt eine .gdignore (kein Import/PCK-Aufnahme; FileAccess +## mit absolutem Pfad funktioniert trotzdem). +const MODELS_DIR := "models" +## Klarer Hinweistext (Karte t_12cf94fe, Auftrag 3), wenn Modelle fehlen: +## wird als Klartext-Meldung gezeigt — kein stiller Platzhalter. +## Notfall-Modus: Terrain/Bauteile brauchen keine GLBs und funktionieren immer. +const FEHLT_TEXT := "Für die Anzeige fehlen die Modelle. So bekommst du sie:\n" \ + + " 1. tools/sync_models.sh aus dem Map-Editor-Ordner laufen lassen\n" \ + + " (kopiert die Modelle in den Ordner %s),\n" \ + + " 2. oder in den Einstellungen einen Ordner mit den Objekt-GLBs setzen.\n" \ + + "Terrain und Bauteile funktionieren trotzdem vollständig (Notfall-Modus)." +## Registry (A6): liefert das Feld "glb" für die Kategorie-verschachtelte Ablage. +const MANIFEST_PATH := "res://library/objects.json" +static var _manifest: Dictionary = {} +static var _manifest_geladen := false + +# WYSIWYG-Fix (t_c8748efc, korrigiert in t_279df760): GLTFDocument löst externe +# Textur-URIs (Sibling-Repos, außerhalb res://) NICHT auf — im Editor-Viewport +# blieben die GLB-Objekte untexturiert/Editor-Farbe, während sie im Spiel +# getextured sind. Flächen OHNE albedo_texture bekommen hier die Textur, die das +# ITHappy-Pack für ihren MATERIALNAMEN vorsieht — Zuordnung identisch zu +# tools/PACK_TEXTURE_MAPPING.md (Client-Repo, verbindlich): +# military_base (+_00x) = Palette (Demonstration_Textures1.png, 1024²) +# picture_1/2/3 = echtes Multicam (Demonstration_picture_N.png, 2048²) +# Wichtig: Nichts wird überschrieben — GLTF-aufgelöste Texturen bleiben unangetastet. +# (Der frühere Inhalt-Vergleich + Kategorie-Camo stammte aus aa94bde und hat auch +# korrekte Pack-Texturen wieder durch Camo ersetzt — das war der Fehler.) +const PACK_TEX_PALETTE := "Demonstration_Textures1.png" +const PACK_TEX_PICTURE_1 := "Demonstration_picture_1.png" +const PACK_TEX_PICTURE_2 := "Demonstration_picture_2.png" +const PACK_TEX_PICTURE_3 := "Demonstration_picture_3.png" + +# ImageTexture pro Textur-Pfad gecacht — sonst erzeugt jede der 291 GLBs +# ihre eigene 2048²-Textur (~16 MB VRAM je Stück statt 2 × 16 MB gesamt). +# false = Ladeversuch fehlgeschlagen (kein endloser Retry + Warn-Flut). +static var _camo_tex_cache: Dictionary = {} +## Dekodierte Multicam-Bilder (RAM, 2 × 2048² ≈ 32 MB) für den Inhalts- +## Vergleich gegen GLTF-eigene Textur-Instanzen. +static var _camo_bild_cache: Dictionary = {} +## Urteil pro GLTF-Textur-Instanz (instance_id → geteilte Textur | false), +## damit der 16-MB-Datenvergleich nur einmal je Instanz läuft. +static var _camo_ersetzung_cache: Dictionary = {} + +# Cache pro OID: Mesh (normalisiert) oder `false` = GLB gefunden, aber nicht +# ladbar (Parse-Fehler — kein endloser Retry pro Rebuild). Fehlende Datei +# bleibt UNGECACHED (nur ein billiger Datei-Check pro Rebuild; legt man die +# GLB später ab, erscheint sie ohne Editor-Neustart). +static var _cache: Dictionary = {} + + +## Kandidaten-Pfade der Quellen in Ladereihenfolge (Karte t_12cf94fe, +## Auftrag 1 — EINE Stelle, erster Treffer gewinnt): +## 1. in den Einstellungen gesetzter Pfad (QUELLEN_SETTING), +## 2. bisheriges Verhalten = Sibling-Repos (DEFAULT_QUELLEN), +## 3. mitgelieferter Ordner models/ direkt neben der App (MODELS_DIR). +## Doppelnennungen werden gefiltert (Reihenfolge bleibt erhalten); ein +## ungültiger Settings-Wert (kein Array) fällt komplett auf die Vorgabe. +static func quellen() -> Array: + var out: Array = [] + var setting: Variant = ProjectSettings.get_setting(QUELLEN_SETTING, DEFAULT_QUELLEN) + if setting is Array or setting is PackedStringArray: + for q in setting: + _quelle_add(out, q) + for q in DEFAULT_QUELLEN: + _quelle_add(out, q) + _quelle_add(out, MODELS_DIR) + return out + + +## Gültigen Quellpfad dedupliziert anhängen (typisiert, keine impliziten casts). +static func _quelle_add(out: Array, q: Variant) -> void: + if not (q is String): + return + var s := (q as String).strip_edges() + if s.is_empty() or out.has(s): + return + out.append(s) + + +## Absoluter Pfad des mitgelieferten Modell-Ordners (für die Klartext- +## Meldung und tools/sync_models.sh-Verweis). Im Editor = Projektordner, +## in der exportierten App = Ordner der EXE. +static func models_pfad() -> String: + return ProjectSettings.globalize_path("res://").path_join(MODELS_DIR) + + +## Erster EXISTIERENDER Dateipfad für die OID (ohne Laden/Parse — billiger +## Datei-Check über alle Kandidaten in Quellen-Reihenfolge). Leerer String = +## kein Quellpfad hat die Datei. Für Klartext-Meldungen/Probe-Logs: zeigt, +## AUS WELCHEM Ordner die Modelle kommen. +static func probe_source_fuer(oid: String) -> String: + if oid.is_empty(): + return "" + for pfad in _kandidaten(oid): + if FileAccess.file_exists(pfad): + return pfad + return "" + + +## Ausgefüllter Klartext-Hinweis (FEHLT_TEXT + echter models/-Pfad) — +## diese EINE Funktion liefert die Meldung, die die App zeigt, wenn +## Modelle fehlen (Karte t_12cf94fe, Auftrag 3). +static func fehl_text() -> String: + return FEHLT_TEXT % models_pfad() + + +## Kandidaten-Pfade (absolut) in Ladereihenfolge: Registry-`glb` (A6, Kategorie- +## Ablage assets/objekte/military_desert//.glb) VOR der flachen +## Alt-Ablage /.glb. Die Quelle ist ein Mesh-Lager (/assets/ +## objekte) — für den verschachtelten Pfad wird sie auf die Repo-Wurzel gekürzt. +static func _kandidaten(oid: String) -> Array: + var basis := ProjectSettings.globalize_path("res://") + var out: Array = [] + var rel := str(_eintrag(oid).get("glb", "")) + if rel.begins_with("res://"): + var sub := rel.substr(6) + # Operator-Korrektur 20.10. (Eigenständigkeits-Beweis): der mitgelieferte + # models/-Ordner NEBEN der App enthält die Pack-Struktur DIREKT + # (models/military_desert/props/box_010.glb) — nicht unter + # models/assets/objekte/. Ohne den zweiten Kandidaten unten werden ALLE + # verschachtelten Pack-Objekte in der eigenständigen App nicht gefunden + # (belegt: barrel_001/box_010 -> leer, fehlende_glbs=2 trotz 334 GLBs). + var sub_kurz := sub.trim_prefix("assets/objekte/") + for quelle in quellen(): + var q := str(quelle) + var marke := "/assets/objekte" + var wurzel := q.substr(0, q.length() - marke.length()) if q.ends_with(marke) else q + out.append(basis.path_join(wurzel).path_join(sub)) + if sub_kurz != sub: + out.append(basis.path_join(wurzel).path_join(sub_kurz)) + for quelle in quellen(): + out.append(basis.path_join(str(quelle)).path_join(oid + ".glb")) + return out + + +## Registry-Eintrag der OID (einmal gelesen, dann gecacht). +static func _eintrag(oid: String) -> Dictionary: + if not _manifest_geladen: + _manifest_geladen = true + var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(MANIFEST_PATH)) + if parsed is Dictionary: + _manifest = (parsed as Dictionary).get("objects", {}) + return _manifest.get(oid, {}) + + +## Echtes Mesh für eine Objekt-ID (normalisiert, Fußpunkt-Koordinaten) oder +## null = keine brauchbare GLB → Aufrufer zeigt den Platzhalter. +static func mesh_fuer(oid: String) -> Mesh: + if oid.is_empty(): + return null + # Bauteile (bau_*) entstehen aus dem Katalog — identisch zu den GLBs, die + # der Client lädt (tools/gen_bauteile.gd), aber ohne Modell-Ordner nötig. + if BauKatalog.ist_bau(oid): + return BauKatalog.mesh(oid) + if _cache.has(oid): + return _cache[oid] if _cache[oid] is Mesh else null + for pfad in _kandidaten(oid): + if FileAccess.file_exists(pfad): + var m := _glb_mesh(pfad) + if m != null: + camo_tex_setzen(m, pfad) + _cache[oid] = m + return m + # Datei da, aber kaputt → NEGATIV cachen (Parse-Fehler wiederholt + # sich sonst bei jedem Rebuild und kostet Zeit). + _cache[oid] = false + return null + return null + + +## Liegt für die OID überhaupt eine GLB-Datei im Lager? (Billiger Check ohne +## Parse — für Statuszeilen-Hinweise beim Platzieren.) +static func fehlt(oid: String) -> bool: + if oid.is_empty(): + return true + if BauKatalog.ist_bau(oid): + return false + if _cache.has(oid): + return not (_cache[oid] is Mesh) + for pfad in _kandidaten(oid): + if FileAccess.file_exists(pfad): + return false + return true + + +## true = KEINE Quelle (Einstellungen/Siblings/models/) enthält irgend eine +## GLB-Datei → die App hat nirgends echte Modelle. Für das Klartext-Banner +## beim Start (Karte t_12cf94fe, Auftrag 3). Nicht-cachend, einmal pro Start. +static func lager_ist_leer() -> bool: + for quelle in quellen(): + if _quelle_hat_glb(ProjectSettings.globalize_path("res://").path_join(str(quelle))): + return false + return true + + +## Enthält der Ordner (rekursiv) mindestens eine .glb-Datei? +static func _quelle_hat_glb(basis: String) -> bool: + var d := DirAccess.open(basis) + if d == null: + return false + d.list_dir_begin() + var name := d.get_next() + while name != "": + var pfad := basis.path_join(name) + if d.current_is_dir(): + if _quelle_hat_glb(pfad): + return true + elif name.ends_with(".glb"): + return true + name = d.get_next() + return false + + +## Cache leeren (Tests / nach manuellem Eingriff ins Mesh-Lager). +static func cache_leeren() -> void: + _cache.clear() + _camo_tex_cache.clear() + _camo_bild_cache.clear() + _camo_ersetzung_cache.clear() + _manifest.clear() + _manifest_geladen = false + + +# ------------------------------------------------------------ Laden -------- + +## GLB OHNE Import-System laden (GLTFDocument, wie gen_thumbs) und ALLE +## MeshInstance3D der Szene in EIN ArrayMesh zusammenbacken (Jedes Teil-Node +## mit seiner Node-Transform; Materialien je Oberfläche bleiben erhalten — +## echte Texturen statt Editor-Farbe). null = Load-Fehlschlag. +static func _glb_mesh(pfad: String) -> Mesh: + var gltf := GLTFDocument.new() + var state := GLTFState.new() + if gltf.append_from_file(pfad, state) != OK: + push_warning("GlbMeshStore: GLB nicht ladbar: %s" % pfad) + return null + var szene: Node = gltf.generate_scene(state) + if szene == null: + return null + var m := _zusammenbacken(szene) + # Das Mesh ist eine Resource (RefCounted) — das Freigeben der Szene + # berührt die Referenz im Rückgabewert nicht. + szene.free() + return m + + +## Alle Mesh-Instanzen der Szene → ein ArrayMesh (Transforms angewandt, +## Materialien übernommen), dann normalisiert (Fußpunkt-Konvention). +static func _zusammenbacken(szene: Node) -> Mesh: + var roh := ArrayMesh.new() + _sammeln(szene, Transform3D.IDENTITY, roh) + if roh.get_surface_count() == 0: + return null + return _normalisiert(roh) + + +static func _sammeln(n: Node, xf: Transform3D, out: ArrayMesh) -> void: + if n is Node3D: + xf = xf * (n as Node3D).transform + if n is MeshInstance3D and (n as MeshInstance3D).mesh != null: + var m: Mesh = (n as MeshInstance3D).mesh + for s in m.get_surface_count(): + var arrays: Array = m.surface_get_arrays(s) + if arrays.is_empty() or not (arrays[Mesh.ARRAY_VERTEX] is PackedVector3Array): + continue + var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] + for i in verts.size(): + verts[i] = xf * verts[i] + arrays[Mesh.ARRAY_VERTEX] = verts + if arrays[Mesh.ARRAY_NORMAL] is PackedVector3Array: + var norms: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL] + for i in norms.size(): + norms[i] = (xf.basis * norms[i]).normalized() + arrays[Mesh.ARRAY_NORMAL] = norms + # Statisch fürs Editor-Viewport: Skin-Daten fliegen raus (sonst + # verlangt add_surface_from_arrays ein Skeleton). + arrays[Mesh.ARRAY_BONES] = null + arrays[Mesh.ARRAY_WEIGHTS] = null + out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var mat: Material = m.surface_get_material(s) + if mat != null: + out.surface_set_material(out.get_surface_count() - 1, mat) + for c in n.get_children(): + _sammeln(c, xf, out) + + +## Fußpunkt-Konvention (wie scopedvr_export.normalisiere): Unterkante y = 0, +## XZ um den AABB-Mittelpunkt zentriert. Materialien bleiben an den Flächen. +static func _normalisiert(mesh: ArrayMesh) -> ArrayMesh: + var aabb := mesh.get_aabb() + var offset := Vector3(-aabb.get_center().x, -aabb.position.y, -aabb.get_center().z) + var out := ArrayMesh.new() + out.resource_name = mesh.resource_name + for s in mesh.get_surface_count(): + var arrays: Array = mesh.surface_get_arrays(s) + if arrays.is_empty() or not (arrays[Mesh.ARRAY_VERTEX] is PackedVector3Array): + continue + var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] + for i in verts.size(): + verts[i] = verts[i] + offset + arrays[Mesh.ARRAY_VERTEX] = verts + out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var mat: Material = mesh.surface_get_material(s) + if mat != null: + out.surface_set_material(out.get_surface_count() - 1, mat) + return out + + +# ------------------------------------------------------- Camo-Textur -------- + +## Camo-Fix (t_50a07d4b): Multicam-Textur von der Platte laden und auf alle +## Flächen ohne albedo_texture setzen (GLTFDocument löst externe URIs der +## Sibling-Repos nicht auf). Zuordnung exakt wie tools/CAMO_TEXTURE_MAPPING.md +## bzw. gen_thumbs._camo_tex_setzen — diese Funktion ist ab jetzt DIE EINE +## Quelle der Zuordnung (gen_thumbs delegiert hierher). Objekte außerhalb +## military_desert//.glb bleiben unangetastet. Fehlt die +## Textur-Datei, wird klar gewarnt (push_warning) statt still weiß zu rendern. +## +## EINE-Quelle-Dedup (t_50a07d4b): GLTFDocument lädt externe Texturen +## inzwischen selbst — aber als EIGENE ImageTexture-Instanz JEDES Parses +## (291 GLBs = 291 × 16 MB VRAM statt 2 × 16 MB, bewiesen per Probe: +## iid pro Parse unterschiedlich, Inhalt identisch zur Datei). Deshalb wird +## hier ZUSÄTZLICH geprüft: stimmt der Inhalt einer GLTF-Textur mit einer +## Multicam-Datei überein, ersetzen wir sie durch DIE eine geteilte Instanz +## aus dem Cache — gleiche Optik (WYSIWYG bleibt), eine Quelle statt Kopien. +## Eingebettete/Fremd-Texturen (Alt-Bestand, Waffen-GLBs) bleiben unangetastet. +static func camo_tex_setzen(mesh: Mesh, glb_pfad: String) -> void: + # Siehe Kopf-Kommentar: nur Flächen OHNE aufgelöste Textur werden versorgt. + var tex_dir := glb_pfad.get_base_dir().path_join("../../../textures/military_desert") + for s in mesh.get_surface_count(): + var mat := mesh.surface_get_material(s) + if not (mat is BaseMaterial3D): + continue + var bm := mat as BaseMaterial3D + if bm.albedo_texture != null: + continue + var datei := pack_datei_fuer(bm.resource_name) + var tex := _camo_tex(tex_dir.path_join(datei)) + if tex != null: + bm.albedo_texture = tex + else: + push_warning("GlbMeshStore: Pack-Textur fehlt fuer Material '%s': %s" % [bm.resource_name, datei]) + + +## Materialname (GLTF) -> Pack-Texturdatei. Quelle: tools/PACK_TEXTURE_MAPPING.md (Client). +static func pack_datei_fuer(material_name: String) -> String: + match material_name: + "picture_1": + return PACK_TEX_PICTURE_1 + "picture_2": + return PACK_TEX_PICTURE_2 + "picture_3": + return PACK_TEX_PICTURE_3 + "military_base": + return PACK_TEX_PALETTE + if material_name.begins_with("military_base"): + return PACK_TEX_PALETTE + return PACK_TEX_PALETTE + +static func _camo_image(pfad: String, warnen: bool) -> Image: + if _camo_bild_cache.has(pfad): + var v: Variant = _camo_bild_cache[pfad] + return v if v is Image else null + if not FileAccess.file_exists(pfad): + if warnen: + push_warning("GlbMeshStore: Camo-Textur fehlt: %s" % pfad) + _camo_bild_cache[pfad] = false + return null + var img := Image.load_from_file(pfad) + if img == null: + if warnen: + push_warning("GlbMeshStore: Camo-Textur nicht ladbar: %s" % pfad) + _camo_bild_cache[pfad] = false + return null + _camo_bild_cache[pfad] = img + return img + + +## DIE EINE geteilte ImageTexture für eine Multicam-Datei (VRAM-Cache). +## Alle getextureden GLB-Flächen teilen sich genau diese Instanz. +static func _camo_tex(tex_pfad: String) -> ImageTexture: + if _camo_tex_cache.has(tex_pfad): + var v: Variant = _camo_tex_cache[tex_pfad] + return v if v is ImageTexture else null + var img := _camo_image(tex_pfad, true) + if img == null: + _camo_tex_cache[tex_pfad] = false + return null + var tex := ImageTexture.create_from_image(img) + _camo_tex_cache[tex_pfad] = tex + return tex + + +## Ersetzt eine GLTF-eigene ImageTexture durch die geteilte Multicam-Instanz, +## WENN der Inhalt (Größe, Format, Pixeldaten) mit einer der Kandidaten- +## Dateien übereinstimmt. null = kein Match → Textur bleibt unangetastet +## (eingebettete/Fremd-Texturen). Urteil je Instanz gecacht. +static func _camo_ersetzung_fuer(tex: Texture2D, kandidaten: Array) -> ImageTexture: + if not (tex is ImageTexture): + return null + var iid := tex.get_instance_id() + if _camo_ersetzung_cache.has(iid): + var v: Variant = _camo_ersetzung_cache[iid] + return v if v is ImageTexture else null + var img: Image = (tex as ImageTexture).get_image() + if img == null: + _camo_ersetzung_cache[iid] = false + return null + var out: ImageTexture = null + for pfad in kandidaten: + var vergleich := _camo_image(str(pfad), false) + if vergleich == null: + continue + if img.get_width() == vergleich.get_width() \ + and img.get_height() == vergleich.get_height() \ + and img.get_format() == vergleich.get_format() \ + and img.data == vergleich.data: + out = _camo_tex(str(pfad)) + break + _camo_ersetzung_cache[iid] = out if out != null else false + return out diff --git a/viewport/glb_mesh_store.gd.uid b/viewport/glb_mesh_store.gd.uid new file mode 100644 index 0000000..7fbaa0f --- /dev/null +++ b/viewport/glb_mesh_store.gd.uid @@ -0,0 +1 @@ +uid://bdbx4rc3fx5ix diff --git a/viewport/map_view_builder.gd b/viewport/map_view_builder.gd new file mode 100644 index 0000000..aec3570 --- /dev/null +++ b/viewport/map_view_builder.gd @@ -0,0 +1,883 @@ +extends Node3D +## Baut die visuelle Repräsentation der Map im Editor-Viewport: +## Terrain-Höhenfeld-Mesh + Maßstabsgitter + Himmel/Licht +## + Echte GLB-Meshes für Bibliotheks-Objekte (GlbMeshStore; ohne GLB = +## Platzhalter-Mesh aus dem Kollisions-Eintrag) + Spawn-Marker mit Beschriftung. +## ## +## Objekt-Marker werden in object_markers gemerkt (Meta: obj_index, +## pick_radius) — Auswahl/Werkzeuge (interaction.gd) greifen darauf zu, +## statt die Szene selbst zu durchsuchen. + +const GRID_COLOR := Color(0.35, 0.45, 0.55, 0.6) +const AXIS_COLOR := Color(0.9, 0.7, 0.2, 0.9) +const OBJECT_COLOR := Color(0.55, 0.75, 0.95) +const TEAM_COLORS := [Color(0.3, 0.6, 1.0), Color(1.0, 0.35, 0.3)] # Blau / Rot +# Auswahl-Markierung (U6, t_639135dd): Farbe + Breite kommen aus dem Theme +# (eine Quelle) — Auswahl-Kontur, schwächere Hover-Kontur, dünn-gestrichelte +# Partner-Kontur. Kein Vollflächen-Orange mehr: das Grundmaterial (mit Textur) +# bleibt sichtbar, die Markierung lebt als Außenhüllen-Kontur darum herum. +const THEME_PATH := "res://core/theme/editor_theme.tres" +const OUTLINE_SHADER := "res://core/shaders/selection_outline.gdshader" +# Fallback, falls das Theme (noch) nicht geladen werden kann. +const OUTLINE_FALLBACK := Color(1.0, 0.6, 0.15, 0.95) + +# Wetter → Licht/Himmel — 1:1-SPIEGEL aus client/scripts/world/weather_presets.gd +# (Karte t_f72913d4: Editor-Vorschau zeigt exakt was der Client zeigt — dieselbe +# Projekt-Konvention wie KolliderBau als 1:1-Spiegel von svmap_world, t_1da96275). +# Bei Preset-Änderungen im Client HIER MITPFLEGEN. Schlüssel = Client-Keys. +const WEATHER_LIGHT := { + "mittag": { + "sky_top": Color(0.30, 0.54, 0.86), "sky_horizon": Color(0.72, 0.83, 0.92), + "ground_bottom": Color(0.20, 0.23, 0.27), "ground_horizon": Color(0.66, 0.75, 0.83), + "sky_energy": 1.0, + "sun_deg": Vector2(-52.0, 30.0), "sun_energy": 1.2, "sun_color": Color(1.0, 0.98, 0.92), + "ambient_source": "sky", "ambient_color": Color(0.60, 0.70, 0.85), "ambient_energy": 1.0, + "fog_enabled": false, "fog_color": Color(0.80, 0.85, 0.90), + "fog_density": 0.0, "fog_sky_affect": 0.0, + "sky_panorama": false, + "panorama_fog_color": Color(0.93, 0.80, 0.66), "panorama_energy": 1.0, + }, + "abend": { + "sky_top": Color(0.25, 0.30, 0.55), "sky_horizon": Color(0.95, 0.55, 0.28), + "ground_bottom": Color(0.16, 0.13, 0.14), "ground_horizon": Color(0.80, 0.45, 0.25), + "sky_energy": 1.0, + "sun_deg": Vector2(-8.0, 250.0), "sun_energy": 0.9, "sun_color": Color(1.0, 0.62, 0.35), + "ambient_source": "color", "ambient_color": Color(0.55, 0.40, 0.32), "ambient_energy": 0.8, + "fog_enabled": true, "fog_color": Color(0.85, 0.55, 0.35), + "fog_density": 0.004, "fog_sky_affect": 0.2, + "sky_panorama": false, + "panorama_fog_color": Color(0.95, 0.72, 0.48), "panorama_energy": 0.8, + }, + "regen": { + "sky_top": Color(0.38, 0.41, 0.45), "sky_horizon": Color(0.55, 0.58, 0.62), + "ground_bottom": Color(0.22, 0.23, 0.25), "ground_horizon": Color(0.50, 0.53, 0.57), + "sky_energy": 0.9, + "sun_deg": Vector2(-40.0, 120.0), "sun_energy": 0.25, "sun_color": Color(0.75, 0.78, 0.82), + "ambient_source": "color", "ambient_color": Color(0.55, 0.58, 0.62), "ambient_energy": 1.0, + "fog_enabled": true, "fog_color": Color(0.55, 0.58, 0.62), + "fog_density": 0.015, "fog_sky_affect": 0.6, + "sky_panorama": false, + "panorama_fog_color": Color(0.76, 0.72, 0.68), "panorama_energy": 0.55, + }, + "nacht": { + "sky_top": Color(0.015, 0.03, 0.08), "sky_horizon": Color(0.05, 0.08, 0.15), + "ground_bottom": Color(0.01, 0.015, 0.025), "ground_horizon": Color(0.04, 0.06, 0.11), + "sky_energy": 1.0, + "sun_deg": Vector2(-65.0, 200.0), "sun_energy": 0.35, "sun_color": Color(0.62, 0.72, 1.0), + "ambient_source": "color", "ambient_color": Color(0.10, 0.14, 0.25), "ambient_energy": 0.5, + "fog_enabled": true, "fog_color": Color(0.04, 0.06, 0.11), + "fog_density": 0.006, "fog_sky_affect": 0.4, + "sky_panorama": false, + "panorama_fog_color": Color(0.10, 0.12, 0.18), "panorama_energy": 0.10, + }, +} + +## Pack-Panorama-Skybox (Karte t_f72913d4): die Textur liegt im Client-Repo +## (Nachbar-Klon — KEINE Duplikat-Pflicht im Editor, siehe Spec) und wird über +## einen absoluten Pfad geladen; fehlt sie → Fallback ProceduralSky. +const PANORAMA_SKY_CLIENT_REL := "assets/textures/military_desert/Skybox_3.png" + +## Absoluter Pfad der Pack-Skybox im Nachbar-Klon ("" = kein Klon gefunden). +static func panorama_sky_pfad() -> String: + var root := TestRun.find_client(ProjectSettings.globalize_path("res://"), + TestRun.load_cfg()) + if root.is_empty(): + return "" + return root.path_join(PANORAMA_SKY_CLIENT_REL) + +var selected_index := -1 # aktuelles Auswahl-Objekt (Kontur-Markierung) +var selected_indices: Array = [] # komplette Auswahl (Karte t_f6f29d86 — Mehrfachauswahl) +var hover_index := -1 # Hover-Objekt (schwächste Kontur; U6-Abstufung) +var object_markers: Array = [] # Node3D je Objekt (gleiche Reihenfolge wie doc.objects) +var fehlende_glbs: Array = [] # OIDs ohne echte GLB (Platzhalter-Fallback — Statuszeilen-Hinweis) +var zone_root: Node3D = null # Conquest-Zonen bzw. Hills (Spielmodus), Kinder je Zone +var spawn_markers: Array = [] # Node3D je Spawn-Point (Meta: spawn_team/spawn_id/spawn_radius) +var terrain_view # TerrainMesh-Ansicht (script-erweitert, Variant) +var grid_node: MeshInstance3D # Maßstabsgitter (Toggle „⊞ Gitter“, Karte t_7ac70014) + +var _doc_ref: Dictionary = {} # letztes rebuild-Doc (Auswahl-Markierung) + +var _base_mat: StandardMaterial3D +var _sym_partners: Array = [] # Indizes der Zwillinge der aktuellen Auswahl +# Kontur-Materialien (U6): Auswahl / Hover / Spiegel-Zwilling — Farben und +# Breiten kommen aus dem Theme, der Shader ist die Außenhüllen-Technik. +var _outline_mat_sel: ShaderMaterial +var _outline_mat_hover: ShaderMaterial +var _outline_mat_partner: ShaderMaterial + + +func _ready() -> void: + rebuild({}, {}) + + +## Komplette Szene aus dem Map-Dict neu aufbauen. manifest = Bibliotheks-Registry. +func rebuild(data: Dictionary, manifest: Dictionary) -> void: + for child in get_children(): + child.queue_free() + object_markers = [] + spawn_markers = [] + zone_root = null + grid_node = null + _doc_ref = data + _base_mat = StandardMaterial3D.new() + _base_mat.albedo_color = OBJECT_COLOR + _base_mat.roughness = 0.8 + # U6 (t_639135dd): keine Vollfarben-Materialien mehr — die Markierung + # (Auswahl/Hover/Partner) lebt als Außenhüllen-Kontur um das Modell, + # Farben + Breiten aus dem Theme. + _markierung_theme_laden() + _sym_partners = [] + + if data.is_empty(): + return + + var terrain: Dictionary = data.get("terrain", {}) + var size_x := float(terrain.get("size_x_m", 1024.0)) + var size_z := float(terrain.get("size_z_m", 1024.0)) + var ground_y := float(terrain.get("min_height_m", 0.0)) + var weather := str(data.get("weather", "mittag")) + # Skybox-Option (Karte t_f72913d4): optionales svmap-Feld `weather_sky` + # ("sky_panorama"/"prozedural"/leer) — dieselbe Semantik wie im Client. + var weather_sky := str(data.get("weather_sky", "")) + + _build_environment(weather, weather_sky) + _build_terrain(data) + _build_wasser(data) # Karte t_28fdef03: nach dem Terrain (Senken füllen sich) + _build_grid(size_x, size_z, ground_y) + # Gitter default AUS über sichtbarem Terrain (Karte t_7ac70014): das + # Terrain ist die Bodendarstellung — das Gitter lag als Linien-Overlay + # DARÜBER und wirkte wie ein „unfertiges“ Editor-Raster. Ohne Terrain- + # Block bleibt es als Boden-Referenz sichtbar. + if grid_node != null: + grid_node.visible = terrain.is_empty() + _build_objects(data.get("objects", []), manifest) + _rebuild_spawn_markers(data) + rebuild_zone_markers(data) + + +## Terrain als echtes Höhenfeld-Mesh statt flacher Ebene (Karte t_9aba04f1): +## der Terrain-Pinsel schreibt die Vertices live neu (terrain_view.update_terrain). +func _build_terrain(data: Dictionary) -> void: + terrain_view = Node3D.new() + terrain_view.set_script(load("res://core/viewport/terrain_mesh.gd")) + add_child(terrain_view) + terrain_view.call("rebuild_from_doc", data) + + +## Wasserfläche (Karte t_28fdef03): EINE transparente Fläche auf `water_level` +## über der GANZEN Map (SvmapWasser.baue — Senken unter der Höhe füllen sich +## automatisch). OHNE Feld → kein Wasser (Legacy-Maps unverändert). Dasselbe +## Material-Verfahren wie der Client (SvmapWasser.make_material — Identitäts- +## Vertrag der Vorschau: Editor zeigt exakt, was der Quest-Client zeigt). +## Leicht ÜBER dem Terrain eingehängt (y-Offset +0.01 über Reihenfolge), aber +## die Fläche selbst liegt exakt auf der Wasserhöhe (Z-Fight-Vermeidung macht +## der Shader über die Tiefen-Textur, nicht über einen Off-Aufschlag). +func _build_wasser(data: Dictionary) -> void: + var level := SvmapWasser.level(data) + if not is_finite(level): + return # Legacy/fehlendes Feld — kein Wasser, nichts bauen. + var terrain: Dictionary = data.get("terrain", {}) + var wetter := str(data.get("weather", "mittag")) + var wasser := SvmapWasser.baue(terrain, level, wetter) + if wasser != null: + add_child(wasser) + + +## Live-Update während eines Pinsel-Strichs (aus terrain_tool.gd): nur die +## Vertices werden neu geschrieben — Grid/Objekte/Marker bleiben unangetastet. +## mats (optional) = Material-Schicht (Karte t_603ec1a8, Malen-Modus) — +## leer = terrain_mesh verwendet die zuletzt gesehene Schicht weiter (Cache, +## Karte t_55ed041a); nur bei NIE gesehener Schicht (Legacy-v1) gilt Gras. +## splat_rect_cells (optional, Karte t_7a0a6b79) = Zellrechteck des Mal- +## Schritts (inkl. Spiegelzellen) — terrain_mesh leitet nur die Splat-Region +## neu ab (Sub-Rect-Update, kein Voll-Rebuild pro Mal-Frame). +func update_terrain(heights: PackedFloat32Array, mats: PackedByteArray = PackedByteArray(), + splat_rect_cells: Rect2i = Rect2i()) -> void: + if terrain_view != null and is_instance_valid(terrain_view): + terrain_view.call("update_heights", heights, 0, 0.0, 0.0, mats, splat_rect_cells) + + +## Misch-Schicht für das nächste Live-Update (Malen mit weichem Rand, Karte 25.10.). +func set_terrain_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void: + if terrain_view != null and is_instance_valid(terrain_view): + terrain_view.call("set_mix", mix_b, mix_w) + + +## Nur das Terrain neu aufbauen (nach Strich-Commit/Undo) — Objekt-Marker und +## Auswahl bleiben bestehen, kein Flackern der ganzen Szene. +func rebuild_terrain_from_doc(data: Dictionary) -> void: + if terrain_view != null and is_instance_valid(terrain_view): + terrain_view.call("rebuild_from_doc", data) + + +## Himmel/Licht — 1:1-Spiegel von client autoload/weather.gd._apply_environment +## (Karte t_f72913d4): Map-Override > Preset-Key > ProceduralSky; ACES-Tonemap; +## Fog koppelt im Panorama-Fall an den Skybox-Horizont (aerial perspective). +## Kein Glow/Bloom/Volumetric — dokumentierte Absage (Mobile/Perf). +func _build_environment(weather: String, sky_override: String = "") -> void: + var preset: Dictionary = WEATHER_LIGHT.get(weather, WEATHER_LIGHT["mittag"]) + var env := Environment.new() + var sky := Sky.new() + # Katalog-ID (weather_sky) > Preset-Key `sky_panorama` > ProceduralSky. + var pano_id := "" + if sky_override == "prozedural": + pass + elif sky_override != "": + pano_id = sky_override + elif bool(preset.get("sky_panorama", false)): + pano_id = SkyboxKatalog.ID_PACK_WUESTE + var panorama := false + if pano_id != "": + var tex := SkyboxKatalog.panorama_textur(pano_id) + if tex != null: + var pano_mat := PanoramaSkyMaterial.new() + pano_mat.panorama = tex + pano_mat.energy_multiplier = float(preset.get("panorama_energy", 1.0)) + sky.sky_material = pano_mat + panorama = true + else: + push_warning("MAPBUILDER: weather_sky=%s, aber Skybox-Textur fehlt/unbekannt — Fallback ProceduralSky" % pano_id) + if sky.sky_material == null: + var sky_mat := ProceduralSkyMaterial.new() + sky_mat.sky_top_color = preset["sky_top"] + sky_mat.sky_horizon_color = preset["sky_horizon"] + sky_mat.ground_bottom_color = preset["ground_bottom"] + sky_mat.ground_horizon_color = preset["ground_horizon"] + sky_mat.sky_energy_multiplier = float(preset["sky_energy"]) + sky.sky_material = sky_mat + env.background_mode = Environment.BG_SKY + env.sky = sky + # Ambient: "sky" = aus dem Himmel ableiten, "color" = feste Farbe (Client-Logik). + if str(preset.get("ambient_source", "color")) == "sky": + env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY + else: + env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + env.ambient_light_color = preset["ambient_color"] + env.ambient_light_energy = float(preset["ambient_energy"]) + # Tonemap ACES — derselbe filmische Look wie der Client; Grading bewusst + # NUR über Tonemap + Preset-Farben (kein Fullscreen-Adjustment-Pass). + env.tonemap_mode = Environment.TONE_MAPPER_ACES + env.fog_enabled = bool(preset["fog_enabled"]) + env.fog_light_color = SkyboxKatalog.fog_farbe(sky_override, + preset.get("panorama_fog_color", preset["fog_color"]), + float(preset.get("panorama_energy", 1.0))) if panorama else preset["fog_color"] + env.fog_density = float(preset["fog_density"]) + env.fog_sky_affect = float(preset["fog_sky_affect"]) + var we := WorldEnvironment.new() + we.environment = env + add_child(we) + + var sun := DirectionalLight3D.new() + var deg: Vector2 = preset["sun_deg"] + sun.rotation_degrees = Vector3(deg.x, deg.y, 0.0) + sun.light_energy = float(preset["sun_energy"]) + sun.light_color = preset["sun_color"] + sun.shadow_enabled = true + add_child(sun) + + +## Maßstabsgitter: Linien alle 100 m, gelbe Mittelachsen (X rot? — Z-Achse +## blau wäre Godot-Konvention; hier neutral gelb für gute Sichtbarkeit). +func _build_grid(size_x: float, size_z: float, ground_y: float) -> void: + var mesh := ImmediateMesh.new() + mesh.surface_begin(Mesh.PRIMITIVE_LINES) + var step := _grid_step(size_x) + var half_x := size_x / 2.0 + var half_z := size_z / 2.0 + var y := ground_y + 0.05 + var x := -half_x + while x <= half_x + 0.01: + var major: bool = absf(fmod(x, step * 5.0)) < 0.01 or absf(x) < 0.01 + _add_line(mesh, Vector3(x, y, -half_z), Vector3(x, y, half_z), + AXIS_COLOR if absf(x) < 0.01 else (GRID_COLOR if not major else GRID_COLOR)) + x += step + var z := -half_z + while z <= half_z + 0.01: + _add_line(mesh, Vector3(-half_x, y, z), Vector3(half_x, y, z), + AXIS_COLOR if absf(z) < 0.01 else GRID_COLOR) + z += step + mesh.surface_end() + var mi := MeshInstance3D.new() + mi.mesh = mesh + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.vertex_color_use_as_albedo = true + mi.material_override = mat + grid_node = mi + add_child(mi) + + +## Gitter ohne Rebuild zu-/abschalten (Toggle in der Top-Bar, Karte t_7ac70014). +func set_grid_visible(visible_now: bool) -> void: + if grid_node != null and is_instance_valid(grid_node): + grid_node.visible = visible_now + + +func grid_is_visible() -> bool: + return grid_node != null and is_instance_valid(grid_node) and grid_node.visible + + +func _grid_step(size_m: float) -> float: + # Ziel: ~10–20 Linien pro Achse, runde Werte. + var raw := size_m / 16.0 + var candidates := [10.0, 25.0, 50.0, 100.0, 250.0, 500.0] + for c in candidates: + if raw <= float(c): + return float(c) + return 1000.0 + + +func _add_line(mesh: ImmediateMesh, from: Vector3, to: Vector3, color: Color) -> void: + mesh.surface_set_color(color) + mesh.surface_add_vertex(from) + mesh.surface_set_color(color) + mesh.surface_add_vertex(to) + + +## Platzhalter je Objekt: Form aus dem Bibliotheks-Manifest (wie der Server- +## Kollider), Position = Objekt-Fußpunkt. ECHTES GLB-Mesh vorziehen +## (Karte t_9b50b231, GlbMeshStore): vorhanden = echtes Modell mit Texturen, +## fehlend = Platzhalter + Eintrag in fehlende_glbs (Statuszeilen-Hinweis). +## Marker-Root bekommt Metas für Auswahl/Anfassen: obj_index, pick_radius +## (Kugel-Approximation), half_height, glb (true = echtes Modell — keine +## Grundfarbe drüberlegen, sonst sind die Texturen weg). +func _build_objects(objects: Array, manifest: Dictionary) -> void: + var lib: Dictionary = manifest.get("objects", {}) + fehlende_glbs = [] + for i in objects.size(): + var obj = objects[i] + if not (obj is Dictionary): + object_markers.append(null) + continue + var oid := str(obj.get("id", "")) + if not lib.has(oid): + object_markers.append(null) + continue # Validierung fängt das — defensiv überspringen. + var entry: Dictionary = lib[oid] + var col: Dictionary = entry.get("collision", {}) + var scale := float(obj.get("scale", 1.0)) + var mesh := GlbMeshStore.mesh_fuer(oid) + var echt := mesh != null + if not echt: + if GlbMeshStore.fehlt(oid): + fehlende_glbs.append(oid) + mesh = MeshShapes.make_placeholder(str(col.get("shape", "box")), col, scale) + var pos: Array = obj.get("pos", [0.0, 0.0, 0.0]) + var marker := Node3D.new() + marker.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + # Karte t_e166e656: 3-Achsen-Rotation — `rot` (Euler xyz) oder Fallback + # rot_y_deg, identische Regel wie im Client/Server (SvmapIO.objekt_rotation). + marker.rotation_degrees = SvmapIO.objekt_rotation(obj) + marker.set_meta("obj_index", i) + var mi := MeshInstance3D.new() + mi.mesh = mesh + if echt: + # Normalisiertes GLB liegt schon in Fußpunkt-Koordinaten + # (Unterkante y = 0) → KEIN half_height-Lift; Objekt-Maßstab + # über Node-Scale. AABB-Metas mal scale (Node-Scale geht nicht + # in mesh.get_aabb() ein). + mi.scale = Vector3.ONE * scale + else: + mi.position.y = MeshShapes.half_height(mesh) + # U6 (t_639135dd): das Grundmaterial wird NIE MEHR durch die Auswahl + # ersetzt — echte GLB-Objekte tragen ihre Texturen weiter (override null), + # Platzhalter die Editor-Grundfarbe. Markiert wird per Kontur (unten). + mi.material_override = null if echt else _base_mat + marker.add_child(mi) + var half: Vector3 = (mesh.get_aabb().size * (scale if echt else 1.0) * 0.5) as Vector3 + marker.set_meta("pick_radius", half.length() + maxf(0.6, half.y * 0.25)) + marker.set_meta("half_height", + MeshShapes.center_height(mesh) * (scale if echt else 1.0)) + marker.set_meta("glb", echt) + object_markers.append(marker) + add_child(marker) + # Farbe je Objekt (Karte t_b0edb807): optionales Feld "tint" — nur + # Objekte MIT Feld bekommen die Überschreibung (Rückwärtskompatibel). + # Die Farbe bleibt jetzt auch bei Markierung sichtbar (U6: Auswahl = Kontur, + # Grundmaterial bleibt) — kein „Orange gewinnt“ mehr. + if SvmapTint.hat(obj): + SvmapTint.anwenden(marker, SvmapTint.von(obj)) + # Markierung (Auswahl/Hover/Partner) als Kontur-Hülle anlegen. + _outline_anwenden(marker, i) + + +## Auswahl nachträglich umfärben (ohne ganzen Rebuild) — z. B. nach Drag. +## Einzel-Variante bleibt für Rückwärtskompatibilität bestehen. +func set_selected(idx: int) -> void: + selected_index = idx + set_selected_multi([idx] if idx >= 0 else []) + + +## Mehrfachauswahl (Karte t_f6f29d86): ALLE markierten Objekte bekommen die +## Auswahl-Kontur (U6, t_639135dd) — Gruppen/Mehrfachauswahl sind auf einen +## Blick erkennbar, OHNE die Textur zu übermalen. Das Grundmaterial bleibt +## überall sichtbar: echte GLB-Objekte tragen ihre Texturen weiter (Karte +## t_9b50b231), Platzhalter die Editor-Grundfarbe — material_override wird +## von der Markierung nicht mehr angefasst. +func set_selected_multi(indices: Array) -> void: + selected_indices = indices.duplicate() + selected_index = indices.back() if not indices.is_empty() else -1 + # sym-Zwillinge (M3, t_34908425): aus dem doc ableiten — Partner der + # Auswahl, die nicht selbst ausgewählt sind, bekommen die dünnere, + # gestrichelte Partner-Kontur (U6-Abstufung statt dunkler Vollfarbe). + _sym_partners = _sym_partners_of(indices) + var objects: Array = _doc_ref.get("objects", []) + for marker in object_markers: + if marker == null or not is_instance_valid(marker): + continue + var idx := int(marker.get_meta("obj_index", -1)) + _outline_anwenden(marker, idx) + # Tint-Abgleich (Karte t_b0edb807): ALLE Objekte zeigen ihre doc-Farbe + # (oder — nach „Zurücksetzen“ — wieder die Pack-Farbe). Der Tint bleibt + # jetzt auch bei Markierung sichtbar (Auswahl = Kontur um das Modell). + if idx >= 0 and idx < objects.size() and objects[idx] is Dictionary: + var obj: Dictionary = objects[idx] + if SvmapTint.hat(obj): + SvmapTint.anwenden(marker, SvmapTint.von(obj)) + else: + SvmapTint.zuruecknehmen(marker) + + +func _is_partner(idx: int) -> bool: + return _sym_partners.has(idx) + + +## sym-Partner der Auswahl aus dem doc bestimmen (M3, t_34908425): Objekte mit +## sym-Kennung, deren Partner NICHT selbst in der Auswahl ist. Keine sym-Felder +## im doc (alte Karten, Modus Aus) → leer, Verhalten wie bisher. +func _sym_partners_of(indices: Array) -> Array: + var objects: Array = _doc_ref.get("objects", []) + var out: Array = [] + for i in indices: + if not (i is int) or i < 0 or i >= objects.size() or not (objects[i] is Dictionary): + continue + var key := SymmetryCore.sym_of(objects[i] as Dictionary) + if key.is_empty(): + continue + var t := SymmetryCore.find_partner_index(objects, key, i) + if t >= 0 and not indices.has(t) and not out.has(t): + out.append(t) + return out + + +## Tint frisch aus dem doc auf die MARKIERTEN Objekte anwenden (Inspector- +## Änderung ohne Rebuild). U6: die Farbe bleibt auch bei Markierung sichtbar +## (Auswahl = Kontur um das Modell) — kein „Orange gewinnt“-Skip mehr. +func tint_refresh(indices: Array) -> void: + var objects: Array = _doc_ref.get("objects", []) + for i in indices: + if i < 0 or i >= object_markers.size(): + continue + var marker = object_markers[i] + if marker == null or not is_instance_valid(marker): + continue + if i >= objects.size() or not (objects[i] is Dictionary): + continue + var obj: Dictionary = objects[i] + if SvmapTint.hat(obj): + SvmapTint.anwenden(marker, SvmapTint.von(obj)) + else: + SvmapTint.zuruecknehmen(marker) + + +func _is_selected(idx: int) -> bool: + return selected_indices.has(idx) + + +## Spawn-Zonen (Karte t_c4162c5d): JEDE Zone ist ein flacher, teamfarbiger +## Kreis auf dem Boden (Radius = design_notes.spawn_radius_m, Editor-eigen) +## + Label3D mit der Punkt-ID. Marker-Root bekommt Metas für das Spawn-Tool +## (spawn_team, spawn_id, spawn_radius → Anfassen/Regel). +func _rebuild_spawn_markers(data: Dictionary) -> void: + for marker in spawn_markers: + if marker != null and is_instance_valid(marker): + marker.queue_free() + spawn_markers = [] + var zones = SpawnOps.zones_of(data) # bearbeiteter Spawn-Satz (Basis oder Modus) + if not (zones is Dictionary): + return + for team_key in zones.keys(): + if not (zones[team_key] is Dictionary): + continue + var team := 0 if str(team_key) == "all" else int(team_key) + var zone: Dictionary = zones[team_key] + var points = zone.get("points", []) + if not (points is Array): + continue + for p in points: + if not (p is Dictionary): + continue + var pid: String = str(p.get("id", "?")) + var pos: Array = p.get("pos", [0.0, 0.0, 0.0]) + var marker := _spawn_marker( + Vector3(float(pos[0]), float(pos[1]), float(pos[2])), + team, pid, SpawnOps.zone_radius(data, pid)) + var yaw_col: Color = TEAM_COLORS[team] if team < TEAM_COLORS.size() else Color.WHITE + marker.set_meta("yaw_color", yaw_col) + marker.add_child(_yaw_arrow(SpawnOps.zone_radius(data, pid), + SpawnOps.effective_yaw(data, p), SpawnOps.has_yaw(p), yaw_col)) + spawn_markers.append(marker) + add_child(marker) + + +## Zonen des Spielmodus: Conquest = gelb, King of the Hill = orange (mit Nummer der +## Wanderreihenfolge). Boden-Kreis + durchscheinender Zylinder + Name. +func rebuild_zone_markers(data: Dictionary) -> void: + if zone_root != null and is_instance_valid(zone_root): + zone_root.queue_free() + zone_root = null + var mode := ModusKatalog.mode_of(data) + if not ModusKatalog.has_zones(mode): + return + zone_root = Node3D.new() + zone_root.name = "Zonen" + add_child(zone_root) + var col := Color(1.0, 0.8, 0.2) if mode == ModusKatalog.MODE_CONQUEST else Color(1.0, 0.45, 0.15) + var zlist := ModusKatalog.zones(data, mode) + for zi in zlist.size(): + var cp = zlist[zi] + if not (cp is Dictionary): + continue + var pos: Array = cp.get("pos", [0, 0, 0]) + var r := float(cp.get("radius_m", ModusKatalog.DEFAULT_RADIUS)) + var node := Node3D.new() + node.name = "CP_%s" % str(cp.get("id", "")) + node.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2])) + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.cull_mode = BaseMaterial3D.CULL_DISABLED + mat.albedo_color = Color(col.r, col.g, col.b, 0.18) + var cyl := MeshInstance3D.new() + var cm := CylinderMesh.new() + cm.top_radius = r + cm.bottom_radius = r + cm.height = 8.0 + cm.cap_top = false + cm.cap_bottom = false + cyl.mesh = cm + cyl.material_override = mat + cyl.position.y = 4.0 + node.add_child(cyl) + var ring := MeshInstance3D.new() + var tm := TorusMesh.new() + tm.inner_radius = r - 0.2 + tm.outer_radius = r + 0.2 + tm.rings = 48 + tm.ring_segments = 6 + ring.mesh = tm + var rmat := StandardMaterial3D.new() + rmat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + rmat.albedo_color = col + ring.material_override = rmat + ring.position.y = 0.15 + node.add_child(ring) + var label := Label3D.new() + label.text = ("⚑ %s" if mode == ModusKatalog.MODE_CONQUEST else "♛ %d · %%s" % (zi + 1)) % str(cp.get("label", cp.get("id", ""))) + label.position.y = 10.0 + label.billboard = BaseMaterial3D.BILLBOARD_ENABLED + label.modulate = col + label.outline_size = 12 + label.font_size = 160 + label.pixel_size = 0.01 + node.add_child(label) + if mode == ModusKatalog.MODE_CONQUEST: # Hills sind keine Spawns + node.set_meta("zone_id", str(cp.get("id", ""))) + node.set_meta("yaw_radius", r) + node.add_child(_yaw_arrow(r, SpawnOps.effective_yaw(data, cp), SpawnOps.has_yaw(cp), col)) + zone_root.add_child(node) + + +## Ein Spawn-Marker bauen (flacher Kreis + Label). radius = angezeigter Zonen-Radius. +func _spawn_marker(pos: Vector3, team: int, pid: String, radius: float) -> Node3D: + var marker := Node3D.new() + marker.position = pos + marker.set_meta("spawn_team", team) + marker.set_meta("spawn_id", pid) + marker.set_meta("spawn_radius", radius) + marker.set_meta("spawn_pick_radius", maxf(radius + 2.0, 4.0)) + marker.set_meta("spawn_selected", false) + marker.add_child(_spawn_disc(radius, team)) + var label := Label3D.new() + label.text = pid + label.position.y = 2.5 + label.billboard = BaseMaterial3D.BILLBOARD_ENABLED + label.modulate = TEAM_COLORS[team] if team < TEAM_COLORS.size() else Color.WHITE + label.outline_size = 12 + label.font_size = 160 + label.pixel_size = 0.01 + marker.add_child(label) + return marker + + +## Pfeil der Blickrichtung: flach am Boden, zeigt lokal nach -Z, rotation.y = Blickrichtung. +## Gesetzte Richtung kräftig, Standard (zur Kartenmitte) blass grau. Ohne Tiefentest, damit +## Hügel und Objekte ihn nicht verdecken. +func _yaw_arrow(radius: float, yaw_deg: float, is_set: bool, col: Color) -> MeshInstance3D: + var length := yaw_arrow_length(radius) + var half_w := maxf(0.25, length * 0.04) + var head_w := half_w * 3.0 + var head_l := minf(length * 0.4, head_w * 2.5) + var neck := -(length - head_l) + var verts := PackedVector3Array([ + Vector3(-half_w, 0, 0), Vector3(half_w, 0, 0), Vector3(half_w, 0, neck), + Vector3(-half_w, 0, 0), Vector3(half_w, 0, neck), Vector3(-half_w, 0, neck), + Vector3(-head_w, 0, neck), Vector3(head_w, 0, neck), Vector3(0, 0, -length), + ]) + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = verts + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.cull_mode = BaseMaterial3D.CULL_DISABLED + mat.no_depth_test = true + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.albedo_color = col.lightened(0.35) if is_set else Color(0.75, 0.75, 0.75, 0.55) + var arrow := MeshInstance3D.new() + arrow.name = "YawArrow" + arrow.mesh = mesh + arrow.material_override = mat + arrow.position.y = 0.4 + arrow.rotation.y = deg_to_rad(yaw_deg) + arrow.set_meta("yaw_deg", yaw_deg) + arrow.set_meta("yaw_set", is_set) + arrow.set_meta("yaw_length", length) + return arrow + + +static func yaw_arrow_length(radius: float) -> float: + return maxf(radius, 3.0) + 2.0 + + +func yaw_arrow_of(node: Node3D) -> MeshInstance3D: + return node.get_node_or_null("YawArrow") as MeshInstance3D + + +## Alle Pfeile zum Anfassen: [{kind:"spawn"|"zone", team, id, node, tip:Vector3}]. +func yaw_handles() -> Array: + var out: Array = [] + var add := func(node: Node3D, entry: Dictionary) -> void: + var arrow := yaw_arrow_of(node) + if arrow == null: + return + var yaw := deg_to_rad(float(arrow.get_meta("yaw_deg", 0.0))) + var length := float(arrow.get_meta("yaw_length", 0.0)) + entry["node"] = node + entry["tip"] = node.global_position + Vector3(-sin(yaw), 0.0, -cos(yaw)) * length + out.append(entry) + for marker in spawn_markers: + if marker != null and is_instance_valid(marker): + add.call(marker, {"kind": "spawn", "team": int(marker.get_meta("spawn_team", -1)), "id": str(marker.get_meta("spawn_id", ""))}) + if zone_root != null and is_instance_valid(zone_root): + for node in zone_root.get_children(): + if node.has_meta("zone_id"): + add.call(node, {"kind": "zone", "team": -1, "id": str(node.get_meta("zone_id"))}) + return out + + +## Kreisfläche EINES Markers (neu) bauen — Werkzeuge rufen nach Radius-Änderung +## style_spawn_marker auf. U6 (t_639135dd): die Fläche bleibt IMMER gleich +## dezent (Teamfarbe, halbtransparent) — die Auswahl zeigt sich als schmaler +## Kontur-Ring am Zonenrand (Theme-Farbe), nicht als aufgehellte Vollfläche. +func _spawn_disc(radius: float, team: int, _selected := false) -> MeshInstance3D: + var mi := MeshInstance3D.new() + var cyl := CylinderMesh.new() + cyl.top_radius = radius + cyl.bottom_radius = radius + cyl.height = 0.25 + mi.mesh = cyl + mi.position.y = 0.12 + var col: Color = TEAM_COLORS[team] if team >= 0 and team < TEAM_COLORS.size() else Color.WHITE + var mat := StandardMaterial3D.new() + mat.albedo_color = Color(col.r, col.g, col.b, 0.55) + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mi.material_override = mat + return mi + + +## Dezenter Auswahl-Ring EINES Spawn-Markers (U6): dünner Torus am Zonenrand, +## Farbe aus dem Theme. Muss NUR bei Auswahl existieren (restlos weg bei +## Abwahl) — style_spawn_marker legt ihn an/entfernt ihn. +func _spawn_ring(radius: float) -> MeshInstance3D: + var mi := MeshInstance3D.new() + var torus := TorusMesh.new() + var t := maxf(0.15, radius * 0.06) + torus.inner_radius = maxf(0.01, radius - t) + torus.outer_radius = radius + t + mi.mesh = torus + mi.position.y = 0.26 + var mat := StandardMaterial3D.new() + mat.albedo_color = _markierung_farbe("selection_outline") + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mi.material_override = mat + return mi + + +## Radius/Anfass-Optik eines Spawn-Markers aktualisieren OHNE Gesamtrebuild +## (Radius-Regel gedreht, Zone ausgewählt/abgewählt). +func style_spawn_marker(marker: Node3D, radius: float, selected: bool) -> void: + if marker == null or not is_instance_valid(marker): + return + var team := int(marker.get_meta("spawn_team", -1)) + var pid := str(marker.get_meta("spawn_id", "")) + marker.set_meta("spawn_radius", radius) + marker.set_meta("spawn_pick_radius", maxf(radius + 2.0, 4.0)) + marker.set_meta("spawn_selected", selected) + if marker.get_child_count() > 0 and marker.get_child(0) is MeshInstance3D: + (marker.get_child(0) as MeshInstance3D).queue_free() + var disc := _spawn_disc(radius, team, selected) + marker.add_child(disc) + marker.move_child(disc, 0) + # Auswahl-Kontur: alter Ring weg, bei Auswahl neuer (restloser Wechsel). + var alter_ring: Node = null + if marker.has_meta("spawn_ring"): + alter_ring = marker.get_meta("spawn_ring") + if alter_ring != null and is_instance_valid(alter_ring): + alter_ring.queue_free() + marker.remove_meta("spawn_ring") + if selected: + var ring := _spawn_ring(radius) + marker.add_child(ring) + marker.set_meta("spawn_ring", ring) + var arrow := yaw_arrow_of(marker) + if arrow != null: # Länge folgt dem Radius + var yaw_deg := float(arrow.get_meta("yaw_deg", 0.0)) + var yaw_set := bool(arrow.get_meta("yaw_set", false)) + arrow.name = "YawArrowAlt" + arrow.queue_free() + marker.add_child(_yaw_arrow(radius, yaw_deg, yaw_set, marker.get_meta("yaw_color", Color.WHITE))) + + +## Auswahl-Highlight über ALLE Spawn-Marker (Werkzeug ruft bei Änderung). +func set_spawn_selected(team: int, pid: String) -> void: + for marker in spawn_markers: + if marker == null or not is_instance_valid(marker): + continue + var is_sel := int(marker.get_meta("spawn_team", -1)) == team \ + and str(marker.get_meta("spawn_id", "")) == pid + if bool(marker.get_meta("spawn_selected", false)) == is_sel: + continue + style_spawn_marker(marker, float(marker.get_meta("spawn_radius", 6.0)), is_sel) + # Label sofort umfärben (auch ohne Disc-Neubau sichtbar) — Farbe aus + # dem Theme statt hartkodiertem Gelb (U6). + var col: Color = TEAM_COLORS[clampi(team, 0, 1)] if team >= 0 else Color.WHITE + if marker.get_child_count() > 1 and marker.get_child(1) is Label3D: + var label := marker.get_child(1) as Label3D + label.modulate = _markierung_farbe("selection_outline") if is_sel else col + + +# ------------------------------------------------------ U6-Markierung ------- +# (Karte t_639135dd — dezente Auswahl: Kontur statt Vollorange. Eine Quelle +# für Farben/Breiten ist das Theme; der Shader ist die Außenhüllen-Technik.) + +## Theme-Farben + -Breiten in die drei Kontur-Materialien überführen +## (Auswahl / Hover / Partner). Fehlende Einträge → Fallback-Farbe. +func _markierung_theme_laden() -> void: + _outline_mat_sel = _outline_material( + _markierung_farbe("selection_outline"), + _markierung_breite("selection_outline_width_mm"), 0.0) + _outline_mat_hover = _outline_material( + _markierung_farbe("selection_outline_hover"), + _markierung_breite("selection_outline_width_hover_mm"), 0.0) + _outline_mat_partner = _outline_material( + _markierung_farbe("selection_outline_partner"), + _markierung_breite("selection_outline_width_partner_mm"), 3.5) + + +## Eine Markierungs-Farbe aus dem Theme lesen (fehlt sie → Fallback). +func _markierung_farbe(name: String) -> Color: + var theme_res: Variant = load(THEME_PATH) + if theme_res is Theme: + var th := theme_res as Theme + if th.has_color(name, "Editor"): + return th.get_color(name, "Editor") + return OUTLINE_FALLBACK + + +## Eine Markierungs-Breite aus dem Theme lesen (Theme speichert Millimeter +## als Ganzzahl — Theme kennt keine Gleitkommakonstanten). +func _markierung_breite(name: String) -> float: + var theme_res: Variant = load(THEME_PATH) + if theme_res is Theme: + var th := theme_res as Theme + if th.has_constant(name, "Editor"): + return float(th.get_constant(name, "Editor")) / 1000.0 + return 0.06 + + +## Kontur-Material für einen Zustand bauen (streifen > 0 = gestrichelt). +func _outline_material(farbe: Color, breite: float, streifen: float) -> ShaderMaterial: + var mat := ShaderMaterial.new() + var shader: Variant = load(OUTLINE_SHADER) + if shader is Shader: + mat.shader = shader + mat.set_shader_parameter("linien_farbe", farbe) + mat.set_shader_parameter("breite_m", breite) + mat.set_shader_parameter("streifen_hz", streifen) + return mat + + +## Zustand eines Objekts (U6-Abstufung): Auswahl > Partner > Hover > nichts. +func _markierung_zustand(idx: int) -> String: + if _is_selected(idx): + return "sel" + if _is_partner(idx): + return "partner" + if idx >= 0 and idx == hover_index: + return "hover" + return "" + + +## Kontur-Hülle eines Objekt-Markers anlegen/umschalten/restlos entfernen. +## Die Hülle hängt als Kind UNTER dem Mesh (erbt Position + Scale — GLB- +## Skalierung bleibt korrekt) und trägt das Mesh ein zweites Mal mit dem +## Außenhüllen-Material (visuell = zweite Render-Passage um das Modell, +## das Grundmaterial mit seiner Textur bleibt unangetastet). +func _outline_anwenden(marker: Node3D, idx: int) -> void: + if marker == null or not is_instance_valid(marker): + return + var zustand := _markierung_zustand(idx) + var alt: Node = null + if marker.has_meta("outline"): + alt = marker.get_meta("outline") + if zustand.is_empty(): + if alt != null and is_instance_valid(alt): + alt.queue_free() + if marker.has_meta("outline"): + marker.remove_meta("outline") + return + var mat: ShaderMaterial = _outline_mat_sel + if zustand == "hover": + mat = _outline_mat_hover + elif zustand == "partner": + mat = _outline_mat_partner + if alt != null and is_instance_valid(alt): + (alt as MeshInstance3D).material_override = mat + return + var mi := marker.get_child(0) as MeshInstance3D + if mi == null or mi.mesh == null: + return + var huelle := MeshInstance3D.new() + huelle.name = "SelectionOutline" + huelle.mesh = mi.mesh + huelle.material_override = mat + mi.add_child(huelle) + marker.set_meta("outline", huelle) + + +## Hover-Markierung (U6-Abstufung, schwächste Kontur). idx = obj_index oder +## -1 für „kein Hover“. Der Aufrufer ist das Objekt-Werkzeug — der Anschluss +## an die Mausbewegung liegt in interaction.gd (Grenze dieser Karte) und +## kommt als Folge-Karte; die Abstufung selbst ist hier voll wirksam. +func set_hover(idx: int) -> void: + if hover_index == idx: + return + hover_index = idx + for marker in object_markers: + if marker == null or not is_instance_valid(marker): + continue + _outline_anwenden(marker, int(marker.get_meta("obj_index", -1))) \ No newline at end of file diff --git a/viewport/map_view_builder.gd.uid b/viewport/map_view_builder.gd.uid new file mode 100644 index 0000000..1761658 --- /dev/null +++ b/viewport/map_view_builder.gd.uid @@ -0,0 +1 @@ +uid://cot16hrvgdx4a diff --git a/viewport/mesh_shapes.gd b/viewport/mesh_shapes.gd new file mode 100644 index 0000000..e51ba1a --- /dev/null +++ b/viewport/mesh_shapes.gd @@ -0,0 +1,88 @@ +extends RefCounted +class_name MeshShapes +## Platzhalter-Meshes für Bibliotheks-Objekte (Form aus dem Kollider-Eintrag, +## wie der Server-Sichtbarkeit) — geteilt von Viewport-Bauer und Ghost-Preview, +## damit Objekte im Editor überall identisch aussehen. + +const SHAPE_BOX := "box" +const SHAPE_SPHERE := "sphere" +const SHAPE_CYLINDER := "cylinder" +const SHAPE_COMPOUND := "compound" +const SHAPE_NONE := "none" + + +static func make_placeholder(shape: String, col: Dictionary, scale: float) -> Mesh: + match shape: + SHAPE_NONE: + # Rein dekorativ (Natur-Update, t_7cf80244): kein Kollider — aber + # im Editor trotzdem sichtbar/wählbar: flache Fußpunkt-Scheibe. + var d := CylinderMesh.new() + d.top_radius = 0.4 * scale + d.bottom_radius = d.top_radius + d.height = maxf(0.02, 0.02 * scale) + return d + SHAPE_SPHERE: + var s := SphereMesh.new() + var r := maxf(0.01, float(col.get("radius", 1.0)) * scale) + s.radius = r + s.height = r * 2.0 + return s + SHAPE_CYLINDER: + var c := CylinderMesh.new() + c.top_radius = maxf(0.01, float(col.get("radius", 1.0)) * scale) + c.bottom_radius = c.top_radius + c.height = maxf(0.01, float(col.get("height", 1.0)) * scale) + return c + SHAPE_COMPOUND: + # Struktur-Objekte (t_d8be9ed7): EIN Mesh aus allen Verbund-Teilen, + # gebacken in Fußpunkt-Koordinaten (Teil-pos = Versatz vom Fußpunkt, + # rot_deg = Euler-Grad) — Rampe/Durchgänge also sichtbar wie die + # echte Kollision. half_height() liefert dafür 0 (schon gebacken). + var st := SurfaceTool.new() + st.begin(Mesh.PRIMITIVE_TRIANGLES) + st.set_smooth_group(-1) + for teil_v in col.get("parts", []): + var teil: Dictionary = teil_v if teil_v is Dictionary else {} + var teil_mesh := make_placeholder(str(teil.get("shape", "box")), teil, scale) + if teil_mesh == null: + continue + var off: Array = teil.get("pos", [0.0, 0.0, 0.0]) + var rot: Array = teil.get("rot_deg", [0.0, 0.0, 0.0]) + var xf := Transform3D( + Basis.from_euler(Vector3(deg_to_rad(float(rot[0])), deg_to_rad(float(rot[1])), deg_to_rad(float(rot[2])))), + Vector3(float(off[0]), float(off[1]), float(off[2])) * scale) + # PrimitiveMesh → Roht_arrays (MeshDataTool bräuchte ArrayMesh). + var arrays: Array = teil_mesh.surface_get_arrays(0) + var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] + var norms: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL] + var idx: PackedInt32Array = arrays[Mesh.ARRAY_INDEX] + for ii in idx: + st.set_normal(xf.basis * norms[ii]) + st.add_vertex(xf * verts[ii]) + return st.commit() + _: + var b := BoxMesh.new() + var size: Array = col.get("size", [1.0, 1.0, 1.0]) + b.size = Vector3(maxf(0.01, float(size[0])), maxf(0.01, float(size[1])), + maxf(0.01, float(size[2]))) * scale + return b + + +## Halbe Höhe des Meshes über dem Fußpunkt (Mesh-Ursprung = Objekt-Fuß). +## Compound-ArrayMeshes sind bereits in Fußpunkt-Koordinaten gebacken → 0. +static func half_height(mesh: Mesh) -> float: + if mesh is BoxMesh: + return (mesh as BoxMesh).size.y * 0.5 + if mesh is CylinderMesh: + return (mesh as CylinderMesh).height * 0.5 + if mesh is SphereMesh: + return (mesh as SphereMesh).radius + return 0.0 + + +## Höhe des Mesh-MITTELPUNKTS über dem Fußpunkt — für das Drehzentrum der +## Objekt-Auswahl. Bei Grundformen = half_height (Mesh ist zentriert); beim +## Compound-ArrayMesh = AABB-Mitte (schon in Fußpunkt-Koordinaten gebacken). +static func center_height(mesh: Mesh) -> float: + var aabb := mesh.get_aabb() + return half_height(mesh) + aabb.position.y + aabb.size.y * 0.5 \ No newline at end of file diff --git a/viewport/mesh_shapes.gd.uid b/viewport/mesh_shapes.gd.uid new file mode 100644 index 0000000..d7c65ea --- /dev/null +++ b/viewport/mesh_shapes.gd.uid @@ -0,0 +1 @@ +uid://cs588kkixa7rg diff --git a/viewport/orbit_camera.gd b/viewport/orbit_camera.gd new file mode 100644 index 0000000..1e6cea0 --- /dev/null +++ b/viewport/orbit_camera.gd @@ -0,0 +1,454 @@ +extends Camera3D +## Orbit-Kamera + Freelook-Flug für den Editor-Viewport. +## Steuerung: +## Mittlere Maustaste ziehen = Drehen (Orbit) +## Mittlere Maustaste + Shift = Verschieben (Pan, parallel zum Boden) +## Mausrad = Zoom — AUSSER ein Objekt/eine Platzier-Vorschau +## ist aktiv: dann dreht das Mausrad das Objekt +## (interaction.gd meldet das per wheel_filter). +## Rechte Maustaste GEDACHT halten = Freelook-Flug (Karte t_c46d31c2): Umsehen + +## W vor / S zurück / A links / D rechts, +## Leertaste hoch / Shift runter (Shooter- +## Konvention, Sly-Wunsch 17.10.), +## Strg = schneller, Alt = langsamer. +## Mausrad ändert im Flug die +## GRUNDgeschwindigkeit dauerhaft. +## Umschalt+F (main.gd) = Freelook dauerhaft an/aus (Flug bleibt auch +## ohne gedrückte rechte Maustaste aktiv). +## F (main.gd) = Auswahl ins Bild zentrieren → focus_on(). +## Orbit bleibt der Default-Zustand: Beim Verlassen des Flugs werden Orbit- +## Ziel/Winkel/Abstand aus der Flugposition/Blickrichtung zurückgerechnet. +## Die Mathe lebt in core/editor/fly_math.gd (pure, headless testbar). +## Ziel-Punkt, Winkel und Abstand bleiben intern; Position wird jede Frame +## aus ihnen berechnet (Orbit) bzw. im Flug direkt bewegt. +## Karte K1 (t_950fddaa): Beim Dreh-Start liegt der Drehpunkt UNTER DEM +## MAUSZEIGER (Unity/Blender-Verhalten) — Objekt/Bauteil-Treffer zuerst, sonst +## Terrain, sonst alter Ziel-Punkt. Kameraposition und Blickrichtung bleiben +## exakt unverändert (kein Sprung), nur Ziel/Winkel/Abstand werden neu +## gerechnet. fit_to_bounds() ersetzt frame_map(): ganze Map ins Bild. + +const FlyMath := preload("res://core/editor/fly_math.gd") +const TerrainField := preload("res://core/svmap/terrain_field.gd") +const SvmapIO := preload("res://core/svmap/svmap_io.gd") + +const ORBIT_DEG_PER_PX := 0.3 # Karte K1: Drehgeschwindigkeit gekapselt — kein Nachlaufen +const DISTANCE_MIN := 5.0 +const FIT_FLOOR_MIN := 30.0 # fit_to_bounds: nie näher als 30 m an die Map ran +## Karte K1d (t_6c1027c1): Startabstand nach Laden/Neu — Slys Wahl („Nah dran: +## startet ~80 m über der Kartenmitte, Objekte sind sofort groß und greifbar"). +const START_DISTANCE_M := 80.0 +const START_YAW_DEG := 45.0 +const START_PITCH_DEG := 35.0 + +var target := Vector3.ZERO +var yaw_deg := 45.0 +var pitch_deg := 35.0 +var distance := 300.0 + +## Von main.gd gesetzt: Callable → true, wenn das Mausrad gerade ein Objekt +## drehen soll (Platzier-Modus oder Auswahl) statt zu zoomen. +var wheel_filter := Callable() +## Von main.gd gesetzt: Callable für Klartext-Status-Meldungen. +var status_cb := Callable() + +## Grundgeschwindigkeit des Flugs (Mausrad ändert sie dauerhaft, Karte 1/3). +var base_speed_mps := FlyMath.BASE_SPEED_MPS + +var _dragging := false +var _panning := false +var _fly_held := false # Rechte Maustaste gehalten → Flug temporär aktiv +var _fly_locked := false # Umschalt+F: Flug dauerhaft (auch ohne RMT) +var _flying := false # Ergebnis-Flag: gerade im Flugmodus? +var _fly_yaw := 0.0 +var _fly_pitch := 0.0 +var _fit_bounds_mn := Vector3.ZERO # zuletzt gesetzte Map-Bounds (fit_to_bounds) +var _fit_bounds_mx := Vector3.ZERO +var _fit_bounds_valid := false + + +func _ready() -> void: + _apply() + + +func _process(delta: float) -> void: + if not _flying or not InputMap.has_action("editor_fly_forward"): + return + var f := Input.get_action_strength("editor_fly_forward") - Input.get_action_strength("editor_fly_back") + var s := Input.get_action_strength("editor_fly_right") - Input.get_action_strength("editor_fly_left") + var v := Input.get_action_strength("editor_fly_up") - Input.get_action_strength("editor_fly_down") + if f == 0.0 and s == 0.0 and v == 0.0: + return + var speed := FlyMath.speed_for(base_speed_mps, + Input.is_action_pressed("editor_fly_fast"), + Input.is_action_pressed("editor_fly_slow")) + var forward := FlyMath.forward_from_euler(_fly_yaw, _fly_pitch) + var right := FlyMath.forward_from_euler(_fly_yaw, 0.0).cross(Vector3.UP).normalized() + position += FlyMath.move_delta(forward, right, f, s, v, speed, delta) + _apply_fly_orientation() + + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventKey: + # Karte K1: Pos1/Home = ganze Karte ins Bild (fit_to_bounds). + _handle_home_key(event as InputEventKey) + if event is InputEventMouseButton: + var mb := event as InputEventMouseButton + if mb.button_index == MOUSE_BUTTON_RIGHT: + # Karte t_2470de89: RMB = Flug mit ABSOLUTEM Vorrang — kein Filter, + # keine Heuristik. Werkzeuge behandeln RMB nicht mehr, also startet + # der Flug in JEDEM Werkzeugzustand (auch Terrain/Platzieren/Bauen). + if mb.pressed: + _enter_fly() + _fly_held = true + elif _fly_held: + _fly_held = false + if not _fly_locked: + _exit_fly() + elif mb.button_index == MOUSE_BUTTON_MIDDLE: + if _flying: + return # Im Flug: MMT-Ziehen bleibt unangetastet (kein Orbit-Sprung) + if mb.pressed: + # Karte K1: Drehpunkt unter den Mauszeiger legen — VOR dem + # ersten Ziehen, damit kein Sprung passiert. + _pivot_to_cursor(mb.position) + _dragging = mb.pressed and not mb.shift_pressed + _panning = mb.pressed and mb.shift_pressed + elif mb.button_index == MOUSE_BUTTON_WHEEL_UP and mb.pressed: + if _flying: + base_speed_mps = FlyMath.wheel_speed(base_speed_mps, true) + _status("Flug-Tempo: %.0f m/s (Grundgeschwindigkeit, bleibt dauerhaft)." % base_speed_mps) + return + if _wheel_blocked(): + return + distance = maxf(DISTANCE_MIN, distance * 0.9) + _apply() + elif mb.button_index == MOUSE_BUTTON_WHEEL_DOWN and mb.pressed: + if _flying: + base_speed_mps = FlyMath.wheel_speed(base_speed_mps, false) + _status("Flug-Tempo: %.0f m/s (Grundgeschwindigkeit, bleibt dauerhaft)." % base_speed_mps) + return + if _wheel_blocked(): + return + distance = minf(distance * 1.1, distance_max()) + _apply() + elif event is InputEventMouseMotion: + var mm := event as InputEventMouseMotion + if _flying: + if _fly_held: + # Umsehen: volle 360° Yaw, Pitch −85…+85 (auch von unten hoch). + _fly_yaw = wrapf(_fly_yaw - mm.relative.x * 0.25, -180.0, 180.0) + _fly_pitch = clampf(_fly_pitch + mm.relative.y * 0.25, + -FlyMath.PITCH_MAX_DEG, FlyMath.PITCH_MAX_DEG) + _apply_fly_orientation() + return + if _dragging: + yaw_deg = wrapf(yaw_deg - mm.relative.x * ORBIT_DEG_PER_PX, -180.0, 180.0) + pitch_deg = clampf(pitch_deg + mm.relative.y * ORBIT_DEG_PER_PX, 5.0, 85.0) + _apply() + elif _panning: + var right := transform.basis.x + var fwd := -transform.basis.z + fwd.y = 0.0 + fwd = fwd.normalized() + var scale := distance * 0.0015 + target -= right * mm.relative.x * scale + target += fwd * mm.relative.y * scale + _apply() + + +func _wheel_blocked() -> bool: + return wheel_filter.is_valid() and bool(wheel_filter.call()) + + +func distance_max() -> float: + return 6000.0 + + +## Freelook dauerhaft an/aus (Umschalt+F, main.gd ruft an). +func toggle_fly_lock() -> void: + if _flying: + _fly_locked = false + if not _fly_held: + _exit_fly() + _status("Freelook-Flug aus — zurück zum Orbit (Mittlere Maustaste ziehen = drehen).") + else: + _fly_locked = true + _enter_fly() + _status("Freelook-Flug DAUERHAFT an: W/S/A/D fliegen, Leertaste hoch / Shift runter, Strg schneller, Alt langsamer — Umschalt+F zurück zum Orbit.") + + +## Im Flugmodus? (interaction.gd blockt z. B. Mausrad-Objektdrehung dann ab.) +func fly_active() -> bool: + return _flying + + +## Aktueller Gierwinkel der BLICKRICHTUNG (Karte t_55257792): im Flug der +## Flug-Yaw, im Orbit der Orbit-Yaw. Für die Pfeiltasten-Richtung (MoveMath +## rastet sie auf die nächste Weltachse). +func view_yaw_deg() -> float: + return _fly_yaw if _flying else yaw_deg + + +## Auswahl ins Bild zentrieren (F): Orbit → Ziel-Punkt auf den Auswahl-Mittelpunkt; +## Flug → Blickrichtung drehen, Position bleibt. +func focus_on(center: Vector3) -> void: + if _flying: + var to := center - position + if to.length_squared() < 0.01: + return + var ep := FlyMath.euler_from_forward(to.normalized()) + _fly_yaw = ep.x + _fly_pitch = clampf(ep.y, -FlyMath.PITCH_MAX_DEG, FlyMath.PITCH_MAX_DEG) + _apply_fly_orientation() + else: + target = center + _apply() + + +func _enter_fly() -> void: + if _flying: + return + _fly_yaw = yaw_deg + _fly_pitch = pitch_deg + _flying = true + # Konflikt-Check (Sly-Wunsch 17.10.): Leertaste ist jetzt „hoch fliegen" — + # ein fokussierter Button würde Space/Enter als Klick interpretieren + # (Bibliothek-Button = Platzierung starten). Im Flug gehört die Leertaste + # der Kamera, also GUI-Fokus freigeben. + var vp := get_viewport() + if vp != null and vp.gui_get_focus_owner() != null: + vp.gui_release_focus() + _status("Freelook-Flug an: W/S/A/D fliegen, Leertaste hoch / Shift runter, Strg = schnell, Alt = langsam, Mausrad = Grund-Tempo. Rechte Maustaste loslassen = zurück zum Orbit.") + + +func _exit_fly() -> void: + if not _flying: + return + _flying = false + # Orbit-Zustand aus Flugposition/Blickrichtung zurückrechnen — kein Sprung. + var fwd := -transform.basis.z + if fwd.length_squared() < 0.0000001: + fwd = FlyMath.forward_from_euler(yaw_deg, pitch_deg) + var ep := FlyMath.euler_from_forward(fwd) + yaw_deg = ep.x + pitch_deg = clampf(ep.y, 5.0, 85.0) + distance = clampf(distance, 30.0, distance_max()) + target = position + fwd.normalized() * distance + _apply() + + +func _apply_fly_orientation() -> void: + var fwd := FlyMath.forward_from_euler(_fly_yaw, _fly_pitch) + look_at_from_position(position, position + fwd, Vector3.UP) + + +## Karte K1 (t_950fddaa): Drehpunkt unter den Mauszeiger — aus der unver- +## änderten Kameraposition werden Ziel/Winkel/Abstand neu gerechnet. +func set_pivot(hit: Vector3) -> void: + var pos := position + var fwd := -transform.basis.z + if fwd.length_squared() < 0.0000001: + fwd = FlyMath.forward_from_euler(yaw_deg, pitch_deg) + var ep := FlyMath.euler_from_forward(fwd.normalized()) + target = hit + distance = pos.distance_to(hit) + yaw_deg = ep.x + pitch_deg = ep.y + # Bewusst KEIN _apply(): Position und Basis bleiben exakt wie vorher — + # kein Sprung. Der nächste Dreh-/Zoom-Schritt rechnet aus dem neuen + # Zustand dieselbe Position zurück (Offset-Konvention konsistent). + + +## MMT-Druck: Punkt unter dem Cursor bestimmen — Objekt/Bauteil zuerst, sonst +## Terrain/Boden, sonst aktuellen Ziel-Punkt behalten (kein Sprung). +func _pivot_to_cursor(viewport_pos: Vector2) -> void: + var inter := _find_sibling("_pick_object") + if inter != null: + var idx := int(inter.call("_pick_object", viewport_pos)) + if idx >= 0: + var pt: Variant = _object_pivot_point(idx) + if pt != null: + set_pivot(pt) + return + var g: Variant = inter.call("_ground_point", viewport_pos) + if g != null: + set_pivot(g) + return + # Kein Treffer: Ziel-Punkt unverändert lassen — Ansicht darf nicht springen. + + +## Mittelpunkt des Objekt-Markers (Fußpunkt + halbe Höhe — gleiche Konvention +## wie interaction.gd beim F-Tasten-Zentrieren). +func _object_pivot_point(idx: int) -> Variant: + var builder := _find_builder() + if builder == null: + return null + var markers: Array = builder.get("object_markers") + if markers == null or idx < 0 or idx >= (markers as Array).size(): + return null + var marker = (markers as Array)[idx] + if marker == null or not is_instance_valid(marker): + return null + var half_h := float(marker.get_meta("half_height", 0.0)) + return (marker as Node3D).global_position + Vector3.UP * half_h + + +func _find_sibling(has_method_name: String) -> Node: + var p := get_parent() + if p == null: + return null + for child in p.get_children(): + var c := child as Node + if c != self and c.has_method(has_method_name): + return c + return null + + +func _find_builder() -> Node: + var p := get_parent() + if p == null: + return null + for child in p.get_children(): + var c := child as Node + if c != self and c.get("object_markers") != null: + return c + return null + + +## Fit: target = Mitte der bounds; Abstand so, dass die GANZE Map im Frustum +## liegt; Pos1 ruft das mit den zuletzt gesetzten Bounds erneut auf. Ersetzt +## das tote frame_map(). +func fit_to_bounds(mn: Vector3, mx: Vector3) -> void: + _fit_bounds_mn = Vector3(minf(mn.x, mx.x), minf(mn.y, mx.y), minf(mn.z, mx.z)) + _fit_bounds_mx = Vector3(maxf(mn.x, mx.x), maxf(mn.y, mx.y), maxf(mn.z, mx.z)) + _fit_bounds_valid = true + _fly_held = false + _fly_locked = false + _flying = false + var span := _fit_bounds_mx - _fit_bounds_mn + target = _fit_bounds_mn + span * 0.5 + # Karte K1: Abstand über Umkugel-Formel — ALLE 8 Ecken sicher im Bild. + # (Reiner Faktor der Kantenlänge reicht bei flachen Maps nicht: die nahe + # Oberecke liegt dann fast seitlich zur Blickachse.) + distance = clampf(_fit_distance(span.length() * 0.5), + FIT_FLOOR_MIN, distance_max()) + yaw_deg = 45.0 + pitch_deg = 35.0 + _apply() + + +## Karte K1d (t_6c1027c1, Neuauflage von K1c): Startansicht NAH am Karteninhalt. +## Laden/Neu setzt die Kamera auf die KARTENMITTE (XZ-Mitte der Map-Bounds, +## Höhe = Gelände dort) mit festem Abstand START_DISTANCE_M (80 m), yaw 45°, +## pitch 35° — Objekte sind +## sofort groß und greifbar (Slys Wahl), statt die 1-km-Fläche von weitem zu +## zeigen. Die Regel ist bewusst UNABHÄNGIG von Größe/Lage des Inhalts: immer +## 80 m. Pos1 (Home) bleibt die GANZE Karte — dafür werden hier die MAP-Bounds +## gemerkt (NICHT die Content-Bounds), sodass fit_to_bounds danach die Karte +## einpasst; Startansicht und Pos1 sind also zwei verschiedene Ansichten. +func fit_to_document(doc: Dictionary, map_mn: Vector3, map_mx: Vector3) -> void: + var mn := Vector3(minf(map_mn.x, map_mx.x), minf(map_mn.y, map_mx.y), + minf(map_mn.z, map_mx.z)) + var mx := Vector3(maxf(map_mn.x, map_mx.x), maxf(map_mn.y, map_mx.y), + maxf(map_mn.z, map_mx.z)) + _fly_held = false + _fly_locked = false + _flying = false + # Pos1 = ganze Karte: MAP-Bounds merken (NICHT die Content-Bounds). + _fit_bounds_mn = mn + _fit_bounds_mx = mx + _fit_bounds_valid = true + # Kartenmitte in XZ — in y aber die GELÄNDE-Oberfläche, nicht die Mitte der + # Bounds: die Bounds reichen bei neuen Maps von −10 bis 80 m, ihre Mitte + # (35 m) liegt 35 m über dem Boden. Damit hätte die Ansicht 35 m über den + # Boden gezielt und die Objekte stünden am unteren Bildrand. + var mitte := mn + (mx - mn) * 0.5 + target = Vector3(mitte.x, SvmapDocument.terrain_height_at(doc, mitte.x, mitte.z), mitte.z) + distance = clampf(START_DISTANCE_M, DISTANCE_MIN, distance_max()) + yaw_deg = START_YAW_DEG + pitch_deg = START_PITCH_DEG + _apply() + var count := int(content_bounds(doc)["count"]) + if count == 0: + _status("Ansicht: Kartenmitte, Abstand %d m (kein Inhalt — Pos1 = ganze Karte)." % + int(START_DISTANCE_M)) + else: + _status("Ansicht: Kartenmitte, Abstand %d m (Pos1 = ganze Karte)." % + int(START_DISTANCE_M)) + + +## Bounding-Box des Dokument-INHALTS: Objekte (inkl. Bauteile) und +## Spawnpunkte — Terrain und Map-Bounds zählen NICHT (Karte K1b). +## Rückgabe: {mn: Vector3, mx: Vector3, count: int}; count 0 = leeres Dokument. +static func content_bounds(doc: Dictionary) -> Dictionary: + var mn := Vector3(INF, INF, INF) + var mx := Vector3(-INF, -INF, -INF) + var count := 0 + for p: Vector3 in _content_points(doc.get("objects", []) as Array): + mn = mn.min(p) + mx = mx.max(p) + count += 1 + var zones: Dictionary = doc.get("spawn_zones", {}) as Dictionary + for team_key in zones: + var zone: Dictionary = zones[team_key] if zones[team_key] is Dictionary else {} + for p: Vector3 in _content_points(zone.get("points", []) as Array): + mn = mn.min(p) + mx = mx.max(p) + count += 1 + if count == 0: + return {"mn": Vector3.ZERO, "mx": Vector3.ZERO, "count": 0} + return {"mn": mn, "mx": mx, "count": count} + + +## pos-Felder [x, y, z] aus einer Dokument-Liste lesen; defekte Einträge +## überspringen (stillschweigend, nie ein harter Fehler beim Laden). +static func _content_points(items: Array) -> Array: + var pts: Array = [] + for item in items: + if not (item is Dictionary): + continue + var pos: Variant = (item as Dictionary).get("pos") + if pos is Array and (pos as Array).size() >= 3: + pts.append(Vector3(float(pos[0]), float(pos[1]), float(pos[2]))) + return pts + + +## Abstand, damit die Umkugel (Radius um den Ziel-Punkt) komplett sichtbar +## bleibt: d = R / sin(halber FOV der SCHMALEREN Bildachse) + 2 % Reserve. +func _fit_distance(radius: float) -> float: + var vfov_half := deg_to_rad(fov * 0.5) # fov ist vertikal (KEEP_HEIGHT) + var aspect := 16.0 / 9.0 + var vp := get_viewport() + if vp != null: + var vs := vp.get_visible_rect().size + if vs.y > 0.0: + aspect = vs.x / vs.y + var hfov_half := atan(aspect * tan(vfov_half)) + var half := minf(vfov_half, hfov_half) + if half <= 0.0 or half >= PI * 0.5: + half = deg_to_rad(37.5) + return maxf(radius, 1.0) / sin(half) * 1.02 + + +func _handle_home_key(key: InputEventKey) -> void: + if key.pressed and not key.echo and key.keycode == KEY_HOME: + if _fit_bounds_valid: + fit_to_bounds(_fit_bounds_mn, _fit_bounds_mx) + _status("Pos1: ganze Karte ins Bild.") + + +func _apply() -> void: + var pitch := deg_to_rad(pitch_deg) + var yaw := deg_to_rad(yaw_deg) + var offset := Vector3( + sin(yaw) * cos(pitch), + sin(pitch), + cos(yaw) * cos(pitch) + ) * distance + position = target + offset + look_at(target, Vector3.UP) + + +func _status(text: String) -> void: + if status_cb.is_valid(): + status_cb.call(text) \ No newline at end of file diff --git a/viewport/orbit_camera.gd.uid b/viewport/orbit_camera.gd.uid new file mode 100644 index 0000000..d848fc0 --- /dev/null +++ b/viewport/orbit_camera.gd.uid @@ -0,0 +1 @@ +uid://bhv55tde24sxg diff --git a/viewport/terrain_contour.gdshader b/viewport/terrain_contour.gdshader new file mode 100644 index 0000000..225a6f7 --- /dev/null +++ b/viewport/terrain_contour.gdshader @@ -0,0 +1,237 @@ +shader_type spatial; +// Terrain-Konturlinien (EDITOR-ONLY, Karte t_3ebe4086): Höhenlinien alle 1 m +// plus kräftigere 5-m-Indexlinie, per-pixel aus UV2.x (= Vertex-Höhe in m). +// Der Quest-3-Perf-Vertrag (kein Custom-Shader) gilt fürs SPIEL — der Editor +// darf teurer sein (DECISIONS.md-Konvention). +// +// Karte t_ab408127 (Terrain-Texturierung): der Editor rendert DIESELBEN +// Texturen wie der Match-Client — Albedo/Normal-Arrays über die Vertex-Alpha +// (Material-ID), Worldspace-Tiling. Konturlinien liegen OBENDRAUF (dunkle +// Multiplikation auf der texturierten Fläche). Ohne Arrays (uniforms nicht +// gesetzt, Fallback-Klon) bleibt es beim reinen Vertex-Farb-Look. +// +// Farbquelle = COLOR: RGB = gebackenes Relief-Shading (Höhen-Kurve × +// Pseudo-Licht), A = Material-ID (m/N gepackt). Unshaded: das gebackene +// Shading ist exakt sichtbar (deterministisch für die Render-Probe). +render_mode unshaded, cull_back; + +// Linienstärken (0 = aus). Terrain-Mesh setzt linien_an auf 0, wenn die +// Map flacher als 2 m ist (sonst wäre ALLES auf der 0-m-Linie dunkel). +uniform float linien_an : hint_range(0.0, 1.0) = 1.0; +uniform float linien_staerke : hint_range(0.0, 1.0) = 0.5; +uniform float index_staerke : hint_range(0.0, 1.0) = 0.75; + +// Textur-Arrays (t_ab408127) — identische Semantik wie terrain_splat.gdshader +// im Client. Nicht gesetzt → ausschalten (vertex_only-Modus). +uniform sampler2DArray albedo_array : source_color, filter_linear_mipmap, repeat_enable; +uniform float layer_count = 20.0; +uniform float tile_size = 3.0; +uniform float texturen_an = 0.0; // 1.0 = Texturen aktiv (vom Mesh-Code gesetzt) + +// Splat-Gewichtstextur (Karte t_7a0a6b79 — weiche Übergänge): je Texel die +// Top-2-Zell-IDs + bilineare Gewichte, von TerrainArrays.build_splat_bytes +// abgeleitet (Spiegel-Datei, identische Formel im Client). R=id0, G=w0, +// B=id1, A=w1 (w0+w1=1). FILTER_NEAREST: IDs dürfen NICHT bilinear vermisch +// werden — die Weichheit steckt IN den Gewichten, nicht in der Filterung. +// Ohne source_color-Hint: raw-Bytes, id = round(kanal × 255). +// Karte 25.10. (Sly: „immer noch harte Kanten“): statt der Splat-Textur (ein +// Texel je Materialpunkt → beim feinen 1-m-Raster harte Pixelstufen) liest der +// Shader die MATERIAL-ID-KARTE direkt (R8, ein Texel je Materialpunkt, +// material_res²) und mischt PRO PIXEL bilinear zwischen den 4 umliegenden +// Punkten — weiche Übergänge über genau einen Rasterabstand, egal wie fein. +// 4 Albedo- + 4 Normal-Fetches + 4 texelFetch, keine Branches. +uniform sampler2D material_map : filter_nearest, repeat_disable; +// Misch-Schicht (Karte 25.10., weiche Pinselränder): je Punkt ein zweites +// Material (b) und dessen Anteil (w, 0–255). Ohne Mischung: alles 0. +uniform sampler2D material_b_map : filter_nearest, repeat_disable; +uniform sampler2D material_w_map : filter_nearest, repeat_disable; +uniform float material_res = 2.0; // Kantenlänge der Material-Karte (Punkte) +uniform float map_size_x = 1.0; // Terrain-Kantenlänge in Weltmetern +uniform float map_size_z = 1.0; + +varying vec3 world_pos; +varying vec2 map_uv; + +void vertex() { + world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + // UV 0..1 über die Map (Index 0 = −X/−Z-Ecke, konventionstreu zum Mesh-Bau). + map_uv = vec2((world_pos.x + 0.5 * map_size_x) / max(map_size_x, 0.001), + (world_pos.z + 0.5 * map_size_z) / max(map_size_z, 0.001)); +} + +// Organische Kanten (Karte 25.10.): die Materialpunkte liegen auf einem +// 1-m-Raster — ohne Störung zeichnet die bilineare Mischung dessen Treppen +// nach. Ein leichter Domain-Warp (Value-Noise in Rasterzellen, ±0,45 Zelle) +// verwackelt die Abtaststelle, die Kante wird unregelmäßig wie echter Boden. +// Ganzzahl-Hash statt sin()-Hash (Karte 25.10.): sin() großer Argumente ist +// auf der GPU ungenau — die Noise bekam gerade, rasterförmige Sprünge, die +// als harte Kanten durch die Verläufe liefen (Sly-Befund, nah gezoomt). +float hash2(vec2 p) { + uvec2 q = uvec2(ivec2(p) + ivec2(4096)); + uint h = q.x * 1597334677u ^ q.y * 3812015801u; + h = (h ^ (h >> 16u)) * 2246822519u; + h = h ^ (h >> 13u); + return float(h & 65535u) / 65535.0; +} + +float vnoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash2(i), hash2(i + vec2(1.0, 0.0)), f.x), + mix(hash2(i + vec2(0.0, 1.0)), hash2(i + vec2(1.0, 1.0)), f.x), f.y); +} + +vec2 warp(vec2 g) { + vec2 o = vec2(vnoise(g * 0.9), vnoise(g * 0.9 + vec2(17.3, 5.1))) - 0.5; + return g + o * 0.9; // eine Oktave: Quest-Budget (8 Hash statt 16) +} + +// Anti-Wiederholung v2 (Karte 25.10., Sly: Büschel sitzen im Raster, diagonale +// Streifen): Hex-Tiling (Mikkelsen/Heitz-Neyret): die Ebene wird in ein +// Dreiecksgitter geteilt, jede Ecke bekommt zufälligen Versatz + Drehung, die +// 3 Abtastungen werden mit weichen Gewichten überblendet — kein Raster, keine +// Achsen mehr, keine harten Nähte. Quest-Kosten: 3 Albedo-Fetches je Material +// (nur 1 Material in ~allen Pixeln, die Mischzone nimmt 2). Danach Makro- +// Variation: Helligkeit (~35 m) + Farbstich (~120 m), je 1 Value-Noise. +uniform float detail_kontrast = 1.3; // >1 = Büschel/Steine kräftiger (Hex-Mischung glättet sonst) + +vec3 alb_hex(float id, vec2 wp, vec2 gx, vec2 gy) { + float T = max(tile_size, 0.05); + vec2 st = wp / T * 1.7320508; // sqrt(3): Dreieckskante ≈ 0,58 Kachel (größer = weniger Mischzone) + vec2 sk = vec2(st.x - 0.57735027 * st.y, 1.15470054 * st.y); + vec2 base = floor(sk); + vec2 fr = fract(sk); + float tz = 1.0 - fr.x - fr.y; + float up = step(0.0, tz); // 1: unteres Dreieck, 0: oberes + vec3 w = mix(vec3(-tz, 1.0 - fr.y, 1.0 - fr.x), vec3(tz, fr.y, fr.x), up); + vec2 v1 = mix(base + vec2(1.0), base, up); + vec2 v2 = mix(base + vec2(1.0, 0.0), base + vec2(0.0, 1.0), up); + vec2 v3 = mix(base + vec2(0.0, 1.0), base + vec2(1.0, 0.0), up); + w = w * w * w; // Mitte stärker, Nähte weicher + w /= (w.x + w.y + w.z); + float wn = sqrt(dot(w, w)); // für Kontrast-Erhalt (Varianz der Mischung) + vec2 uv = wp / T; + vec3 acc = vec3(0.0); + for (int k = 0; k < 3; k++) { + vec2 v = k == 0 ? v1 : (k == 1 ? v2 : v3); + float wk = k == 0 ? w.x : (k == 1 ? w.y : w.z); + float a = hash2(v + vec2(31.0, 7.0)) * 6.2831853; + vec2 off = vec2(hash2(v), hash2(v + vec2(53.0, 11.0))); + float c = cos(a); + float s = sin(a); + vec2 r = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y) + off; + acc += wk * textureGrad(albedo_array, vec3(r, id), gx, gy).rgb; + } + // Kontrast-Erhalt: Mischen dreier Abtastungen glättet auf den Mittelwert → + // Abweichung vom Mittelwert (grober Mip) durch ||w|| teilen. + vec3 mean_c = textureLod(albedo_array, vec3(0.5, 0.5, id), 8.0).rgb; + acc = mean_c + (acc - mean_c) * (detail_kontrast / max(wn, 0.577)); + float bright = mix(0.86, 1.12, vnoise(wp * 0.028 + vec2(17.0, 3.0))); + float tint = vnoise(wp * 0.0085 + vec2(5.0, 41.0)) - 0.5; + vec3 col = acc * bright; + return col * vec3(1.0 + 0.10 * tint, 1.0 + 0.03 * tint, 1.0 - 0.10 * tint); +} + +float mat_id(ivec2 p) { + return clamp(floor(texelFetch(material_map, p, 0).r * 255.0 + 0.5), 0.0, layer_count - 1.0); +} + +float mat_b(ivec2 p) { + return clamp(floor(texelFetch(material_b_map, p, 0).r * 255.0 + 0.5), 0.0, layer_count - 1.0); +} + +// Top-2-Mischung (Karte 25.10.): 4 Nachbarpunkte × (a, b) = 8 Kandidaten mit +// Gewicht (bilinear × Punkt-Anteil). Gleiche IDs werden aufsummiert, die +// zwei stärksten Materialien gewinnen. Rückgabe: (id1, id2, Anteil id1). +// Danach nur 2 Albedo- + 2 Normal-Fetches. +vec3 top2(vec2 g) { + vec2 gi = min(floor(g), vec2(max(material_res - 2.0, 0.0))); + vec2 f = clamp(g - gi, 0.0, 1.0); + ivec2 p = ivec2(gi); + ivec2 q[4] = ivec2[4](p, p + ivec2(1, 0), p + ivec2(0, 1), p + ivec2(1, 1)); + float cw[4] = float[4]((1.0 - f.x) * (1.0 - f.y), f.x * (1.0 - f.y), (1.0 - f.x) * f.y, f.x * f.y); + float ids[8]; + float ws[8]; + for (int k = 0; k < 4; k++) { + float wb = texelFetch(material_w_map, q[k], 0).r; + ids[2 * k] = mat_id(q[k]); + ws[2 * k] = cw[k] * (1.0 - wb); + ids[2 * k + 1] = mat_b(q[k]); + ws[2 * k + 1] = cw[k] * wb; + } + float id1 = ids[0]; + float s1 = -1.0; + for (int i = 0; i < 8; i++) { + float s = 0.0; + for (int j = 0; j < 8; j++) { + s += ids[j] == ids[i] ? ws[j] : 0.0; + } + if (s > s1) { + s1 = s; + id1 = ids[i]; + } + } + float id2 = id1; + float s2 = 0.0; + for (int i = 0; i < 8; i++) { + if (ids[i] == id1) { + continue; + } + float s = 0.0; + for (int j = 0; j < 8; j++) { + s += ids[j] == ids[i] ? ws[j] : 0.0; + } + if (s > s2) { + s2 = s; + id2 = ids[i]; + } + } + return vec3(id1, id2, s1 / max(s1 + s2, 0.00001)); +} + +void fragment() { + vec3 col = COLOR.rgb; + // --- Textur (t_ab408127): Material-ID aus Vertex-Alpha, Worldspace-UV --- + // Editor-Vorschau ALBEDO-ONLY (der Editor-Viewport ist unshaded; eine + // Normal-Map würde ohne Licht ohnehin nichts zeigen — die Relief-Optik + // kommt aus dem gebackenen Hillshade in COLOR.rgb). + if (texturen_an > 0.5) { + // Top-2-Splat-Blend (t_7a0a6b79): die Weichheit steckt in den Gewich- + // ten der Splat-Textur — an Zellgrenzen rampen die IDs weich (bilinear + // über die 4 umliegenden Zellen), im Flachbereich w0=1 (reines id0). + // Karte 25.10.: Top-2 aus 4 Nachbarpunkten inkl. Misch-Schicht + // (Spiegel zum Client-Shader terrain_splat.gdshader). + float m1 = max(material_res - 1.0, 1.0); + vec2 g = clamp(warp(clamp(map_uv, 0.0, 1.0) * m1), vec2(0.0), vec2(m1)); + vec3 t2 = top2(g); + // Ableitungen VOR dem Branch (Mip-Wahl bleibt gültig, textureGrad). + vec2 guv = world_pos.xz / max(tile_size, 0.05); + vec2 gx = dFdx(guv); + vec2 gy = dFdy(guv); + // Reiner Boden (Anteil ≥ 0,999, Normalfall): nur 3 statt 6 Fetches. + vec3 alb = t2.z > 0.999 ? alb_hex(t2.x, world_pos.xz, gx, gy) + : mix(alb_hex(t2.y, world_pos.xz, gx, gy), alb_hex(t2.x, world_pos.xz, gx, gy), t2.z); + col = alb * col; // Tönung/Shading multipliziert (wie Client) + } + // --- Konturlinien (unverändert, Karte t_3ebe4086) --- + float h = UV2.x; + // Flachflächen-Schutz: liegt eine GANZE Platte exakt auf einer Meter-Marke + // (z. B. Tal = 0.0 m, Plateau = 5.0 m), wäre jedes Pixel „auf der Linie“ + // → die Fläche würde komplett dunkel übermalt. Deshalb: Linienstärke an + // der lokalen Steigung dämpfen — exakt flach (dh = 0) → keine Linie, + // sanfte bis steile Hänge → volle Linie. + float dh = max(abs(dFdx(h)), abs(dFdy(h))); + float relief = smoothstep(0.0, 0.0005, dh); + // Abstand (in m) zur nächsten ganzen Meter-Marke: + float d1 = abs(fract(h + 0.5) - 0.5); + float w1 = fwidth(h) * 1.2; // Linie ~1–2 px breit, auflösungsstabil + float linie = 1.0 - smoothstep(w1 * 0.5, w1 * 1.5, d1); + // 5-m-Indexlinie (Abstand in m zur nächsten 5er-Marke): + float d5 = abs(fract(h / 5.0 + 0.5) - 0.5) * 5.0; + float w5 = fwidth(h) * 2.2; + float linie5 = 1.0 - smoothstep(w5 * 0.5, w5 * 1.5, d5); + col = mix(col, col * 0.55, linie * linien_an * linien_staerke * relief); + col = mix(col, col * 0.32, linie5 * linien_an * index_staerke * relief); + ALBEDO = col; +} diff --git a/viewport/terrain_contour.gdshader.uid b/viewport/terrain_contour.gdshader.uid new file mode 100644 index 0000000..c6426a4 --- /dev/null +++ b/viewport/terrain_contour.gdshader.uid @@ -0,0 +1 @@ +uid://clp2k02n02ruk diff --git a/viewport/terrain_mesh.gd b/viewport/terrain_mesh.gd new file mode 100644 index 0000000..9f63186 --- /dev/null +++ b/viewport/terrain_mesh.gd @@ -0,0 +1,446 @@ +extends Node3D +## Terrain-Ansicht: baut das Höhenfeld (svmap terrain-Block) als Mesh im +## Editor-Viewport (M2, Karte t_9aba04f1). +## +## Zwei Wege: +## rebuild_from_doc(data) — nach Laden/Neu/Undo: Base64 dekodieren + Mesh neu. +## update_heights(...) — während eines Pinsel-Strichs: nur Vertices neu +## schreiben (SOFT-war das Mesh live — kein Rebuild, +## kein Flackern, res 129 ≈ 16,6k Punkte = flott). +## +## Farben (Karte t_603ec1a8): Vertex-Farben aus der Material-Schicht +## (terrain.materials_r8_b64, v2) — Gras/Sand/Fels, plus Höhen-Faktor. +## +## Relief-Lesbarkeit (Karte t_3ebe4086, Sly-Befund „Erhöhungen/Vertiefungen +## kaum erkennbar"): TWO-PUNCH im Vertex-Farben-Pfad: +## 1. Höhen-Faktor deutlich kontrastreicher: steilere S-Kurve statt linear +## und breitere Faktor-Spanne (Senken dunkler, Hügel heller). +## 2. Pseudo-Licht: Terrain-Normale × feste Sonnenrichtung (Hillshade) — +## Sonnenabgewandte Hänge werden dunkler, zugewandte heller. Das ist der +## günstigste echte Relief-Eindruck für die mobile Vertex-Farb-Pipeline +## und identisch zum Match-Client (svmap_terrain.gd, gleiche Konstanten). +## +## Editor-only Extra (gleiche Karte): Höhenlinien alle 1 m + 5-m-Indexlinie +## per Custom-Shader (terrain_contour.gdshader, UV2.x = Höhe). Quest-Vertrag +## „kein Custom-Shader" gilt fürs Spiel — der EDITOR darf teurer sein +## (DECISIONS.md-Konvention). Der Match-Client bleibt StandardMaterial3D. +## Editor-Viewport material ist UNSHADED: das gebackene Shading ist exakt +## sichtbar (kein Doppel-Shading mit dem Viewport-Sonnenlicht, deterministisch +## für die Render-Probe). + +const MATERIAL_COLORS: Array[Color] = [ + Color(1.0, 1.0, 1.0), # 0 = Gras — Karte t_ab408127: Textur liefert die + Color(1.0, 1.0, 1.0), # 1 = Sand — Farbe; Vertex-RGB ist nur noch Shading- + Color(1.0, 1.0, 1.0), # 2 = Fels — Multiplikator (Höhe × Hillshade), + # 20-Slot-Palette: Weiß = neutral, Albedo-Textur trägt die Färbung. + Color(1.0, 1.0, 1.0), # 3 + Color(1.0, 1.0, 1.0), # 4 + Color(1.0, 1.0, 1.0), # 5 + Color(1.0, 1.0, 1.0), # 6 + Color(1.0, 1.0, 1.0), # 7 + Color(1.0, 1.0, 1.0), # 8 + Color(1.0, 1.0, 1.0), # 9 + Color(1.0, 1.0, 1.0), # 10 + Color(1.0, 1.0, 1.0), # 11 + Color(1.0, 1.0, 1.0), # 12 + Color(1.0, 1.0, 1.0), # 13 + Color(1.0, 1.0, 1.0), # 14 + Color(1.0, 1.0, 1.0), # 15 + Color(1.0, 1.0, 1.0), # 16 + Color(1.0, 1.0, 1.0), # 17 + Color(1.0, 1.0, 1.0), # 18 + Color(1.0, 1.0, 1.0), # 19 +] +# Höhen-Faktor (Material-Farbe × Faktor) — KARTE t_3ebe4086: bewusst breiter +# und mit S-Kurve (t*t*(3-2t)) kalibriert als die alte Linear-Version +# (0.72 … 1.15): Senken deutlich dunkler, Hügel deutlich heller. +const SHADING_LOW := 0.58 # Tal-Faktor +const SHADING_HIGH := 1.28 # Hügel-Faktor +# Pseudo-Licht (Hillshade): feste Sonnenrichtung, editor-seitig so gewählt, +# dass beim Default-Blick (Kamera südlich der Map, Blick Richtung −Z) die +# Formen seitlich beleuchtet wirken (Licht von rechts-hinten +X/+Z). +const RELIEF_SUN := Vector3(0.45, 0.75, 0.5) +const LIGHT_LOW := 0.62 # Hang abgewandt von der Sonne +const LIGHT_HIGH := 1.22 # Hang zugewandt +const LIGHT_T0 := 0.15 # Smoothstep-Fenster der NDOT-Kurve +const LIGHT_T1 := 0.95 +const SHADE_MIN := 0.40 # Gesamtklemme (Höhen × Licht), Schutz vor Schwarz +const SHADE_MAX := 1.35 +# Höhenlinien (editor-only) nur ab 2 m Gesamtrelief — auf einer flachen Map +# läge sonst ALLES auf der 0-m-Linie und die Fläche würde dunkel übermalt. +const CONTOUR_MIN_SPAN := 2.0 + +const CONTOUR_SHADER := "res://core/viewport/terrain_contour.gdshader" + +var _mi: MeshInstance3D +var _res := 0 +var _size_x := 0.0 +var _size_z := 0.0 +var _mats_cache := PackedByteArray() # zuletzt gesehene Material-Schicht + # (res²) — Live-Pfad-Fallback, Karte + # t_55ed041a. +var _wasser_level := INF # Karte t_28fdef03: Wasserhöhe aus dem doc + # (INF = kein Wasser — Matsch-Ring aus). +var _triangles := 0 +var _contours_pref := true # UI-Toggle „Höhenlinien“ (Default AN) +var _shader: Shader + +# --- Splat-Gewichtstextur (Karte t_7a0a6b79 — weiche Übergänge) ------------- +# CPU-Image + GPU-Textur der Top-2-Splat-Gewichte (RGBA8: R=id0 G=w0 B=id1 +# A=w1, Formel TerrainArrays.build_splat_bytes — Spiegel-Datei). Beim Malen +# wird NUR die betroffene Region neu abgeleitet (Sub-Rect-Update, siehe +# _refresh_splat_region) — kein Voll-Rebuild pro Mal-Frame. +var _splat_image: Image +var _splat_tex: ImageTexture +var _splat_side := 0 # Texel-Kantenlänge (Zweierpotenz) +var _splat_res := 0 # Zellraster-Auflösung, für die die Splat gilt +# Misch-Schicht (Karte 25.10.): zweites Material + Anteil je Punkt — eigene +# R8-Texturen neben der Material-Karte (gleiche Größe, Shader liest alle drei). +var _mix_b_cache := PackedByteArray() +var _mix_w_cache := PackedByteArray() +var _mix_b_image: Image +var _mix_w_image: Image +var _mix_b_tex: ImageTexture +var _mix_w_tex: ImageTexture + + +func _ready() -> void: + _ensure_mesh_instance() + + +func _ensure_mesh_instance() -> void: + if _mi != null and is_instance_valid(_mi): + return + _mi = MeshInstance3D.new() + _mi.material_override = _make_material() + add_child(_mi) + + +## Material fürs Editor-Terrain: unshaded Kontur-Shader (Vertex-Farbe als +## Albedo + Höhenlinien). Karte t_ab408127: zusätzlich die Terrain-Texturen +## (TerrainArrays.res — spiegelgleich zum Client-Pfad, aber EDITOR-EIGENE +## Kopie der Resource-Klasse, Repos sind entkoppelt). Fällt auf unshaded +## StandardMaterial3D zurück, wenn der Shader nicht ladbar ist (Robustheit — +## Terrain bleibt immer sichtbar). +func _make_material() -> Material: + _shader = load(CONTOUR_SHADER) + if _shader != null: + var mat := ShaderMaterial.new() + mat.shader = _shader + _apply_texture_uniforms(mat) + return mat + push_warning("TerrainMesh: Kontur-Shader fehlt — Fallback StandardMaterial3D (unshaded).") + var std := StandardMaterial3D.new() + std.vertex_color_use_as_albedo = true + std.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + std.roughness = 1.0 + return std + + +## Terrain-Texturen an den Kontur-Shader binden (t_ab408127). Quelle = +## res://assets/textures/terrain/terrain_arrays.res — vom Sync-Skript +## (tools/sync_terrain_tex.sh) aus dem Client-Klon gespiegelt, analog +## tools/sync_models.sh (models/-Ordner-Muster). Fehlt die Resource +## (frischer Editor-Klon ohne Sync), bleibt texturen_an=0 → reiner +## Vertex-Farb-Look (Fallback, kein Crash). +func _apply_texture_uniforms(mat: ShaderMaterial) -> void: + const ARRAYS_PATH := "res://assets/textures/terrain/terrain_arrays.res" + if not ResourceLoader.exists(ARRAYS_PATH): + return # Fallback-Klon: texturen_an bleibt 0 (Vertex-Farb-Look) + var arrays = load(ARRAYS_PATH) + if arrays == null or not ("albedo_images" in arrays): + return + var imgs: Array = arrays.albedo_images + if imgs.size() < 3: + return + var albedo := Texture2DArray.new() + albedo.create_from_images(imgs) + mat.set_shader_parameter("albedo_array", albedo) + mat.set_shader_parameter("layer_count", float(imgs.size())) + mat.set_shader_parameter("tile_size", 3.0) # Spiegel Client TILE_SIZE (Karte 25.10.) + mat.set_shader_parameter("texturen_an", 1.0) + + +## UI-Toggle „Höhenlinien“ (main.gd): ohne Rebuild an/ab. +func set_contours(on: bool) -> void: + _contours_pref = on + _apply_contour_state(_last_span >= CONTOUR_MIN_SPAN) + + +func contours_enabled() -> bool: + return _contours_pref + + +var _last_span := 0.0 + + +## Kompletter Neuaufbau aus dem svmap-terrain-Block (inkl. Material-Schicht). +func rebuild_from_doc(data: Dictionary) -> void: + var terrain: Dictionary = data.get("terrain", {}) + if terrain.is_empty(): + _triangles = 0 + if _mi != null: + _mi.mesh = null + return + # Splat-Textur verwerfen — sie wird im update_heights frisch aus der + # (nach Undo/Load evtl. geänderten) Zell-Schicht abgeleitet. + _splat_tex = null + var mix := SvmapIO.decode_mix(data.get("terrain", {})) + _mix_b_cache = mix[0] + _mix_w_cache = mix[1] + # Wasser (Karte t_28fdef03): Höhe merken — der Matsch-Ring (Ufer-Automatik) + # mischt sich in relief_color ein. INF = kein Wasser (Legacy unverändert). + _wasser_level = SvmapWasser.level(data) + var heights := SvmapIO.decode_heights(terrain) + var mats := SvmapIO.decode_materials(terrain) + update_heights(heights, int(terrain.get("resolution", 0)), + float(terrain.get("size_x_m", 1.0)), float(terrain.get("size_z_m", 1.0)), mats) + + +## Mesh aus einem (neuen) Höhenfeld bauen — auch live während des Strichs. +## res/size 0 = zuletzt verwendete Werte weiterverwenden (Live-Pfad). +## mats = Material-Schicht (res² Bytes, 0=Gras/1=Sand/2=Fels); leer → die +## zuletzt gesehene Schicht (Cache) wird weiterverwendet, damit Live-Updates +## ohne explizite Schicht (z. B. Heben/Senken-Stroke) die Material-Farben +## BEHALTEN statt live auf alles-Gras zurückzufallen (Karte t_55ed041a). +## Nur wenn NIE eine Schicht gesehen wurde (Legacy-v1-Map) gilt alles Gras. +func update_heights(heights: PackedFloat32Array, res: int = 0, size_x: float = 0.0, + size_z: float = 0.0, mats: PackedByteArray = PackedByteArray(), + splat_rect_cells: Rect2i = Rect2i()) -> void: + if res < 2: + res = _res + if size_x <= 0.0: + size_x = _size_x + if size_z <= 0.0: + size_z = _size_z + if _mi == null: + _ensure_mesh_instance() + if res < 2 or heights.size() < res * res or size_x <= 0.0 or size_z <= 0.0: + return + _res = res + _size_x = size_x + _size_z = size_z + # Material-Schicht hat ihr eigenes (feines) Raster (Karte 24.10.) — mres + # aus der Länge (quadratisch); alte Karten: mres == res. + if mats.is_empty() and not _mats_cache.is_empty(): + mats = _mats_cache + elif not mats.is_empty(): + _mats_cache = mats + var mres := mat_res_of(mats, res) + var last := float(res - 1) + var positions := PackedVector3Array() + var normals := PackedVector3Array() + var colors := PackedColorArray() + var heights_uv := PackedVector2Array() + var indices := PackedInt32Array() + positions.resize(res * res) + normals.resize(res * res) + colors.resize(res * res) + heights_uv.resize(res * res) + + var hmin := INF + var hmax := -INF + for v in heights: + if v < hmin: + hmin = v + if v > hmax: + hmax = v + var span := maxf(hmax - hmin, 0.0001) + _last_span = span + var cell_x := size_x / last + var cell_z := size_z / last + _apply_contour_state(span >= CONTOUR_MIN_SPAN) + + var z := 0 + while z < res: + var x := 0 + while x < res: + var idx := z * res + x + # Index→Welt-Konvention (Karte t_234490cf): Index 0 = −X/−Z-Ecke, + # x-Index läuft MIT +WeltX, z-Index MIT +WeltZ — IDENTISCH zu + # SvmapDocument.terrain_height_at, TerrainField.height_at und dem + # Match-Client (svmap_terrain.gd: −size*0.5 + index*cell). + # Vor b7b160d…+Fix stand hier eine SPIGELUNG (+0.5 − index·cell in + # beiden Achsen) — der Renderer war damit der einzige Verbraucher + # dieser Konvention: der Pinsel-Strahl traf das Mesh an der + # punktgespiegelten Stelle (Sly-Befund „Pinsel wirkt woanders“). + positions[idx] = Vector3( + -size_x * 0.5 + float(x) * cell_x, + heights[idx], + -size_z * 0.5 + float(z) * cell_z) + # Normale aus zentralen Differenzen (Rand: einseitig geklemmt): + # n ∝ (−∂h/∂x, 1, −∂h/∂z), skaliert mit 2·cell_x·cell_z. + var hx_l := heights[z * res + maxi(x - 1, 0)] + var hx_r := heights[z * res + mini(x + 1, res - 1)] + var hz_d := heights[maxi(z - 1, 0) * res + x] + var hz_u := heights[mini(z + 1, res - 1) * res + x] + normals[idx] = Vector3( + (hx_l - hx_r) * cell_z, + 2.0 * cell_x * cell_z, + (hz_d - hz_u) * cell_x).normalized() + var mat_id := clampi(SvmapIO.material_at_height_point(mats, mres, res, x, z), + 0, MATERIAL_COLORS.size() - 1) + # Material-Farbe × Höhen-Kurve × Pseudo-Licht (Relief lesbar). + var farbe := relief_color(mat_id, heights[idx], hmin, hmax, normals[idx]) + # Ufer-Automatik (Karte t_28fdef03): Zellen knapp unter der Wasser- + # höhe bekommen den Matsch-Ring — IDENTISCHE Formel wie der Client + # (SvmapWasser.ufer_faktor/matsch_anwenden — dieselbe Spiegel-Datei). + # Alpha (gepackte Material-ID) bleibt unangetastet (Splat-Vertrag). + if _wasser_level < INF: + farbe = SvmapWasser.matsch_anwenden(farbe, + SvmapWasser.ufer_faktor(heights[idx], _wasser_level)) + colors[idx] = farbe + heights_uv[idx] = Vector2(heights[idx], 0.0) + x += 1 + z += 1 + + z = 0 + while z < res - 1: + var x := 0 + while x < res - 1: + var i := z * res + x + # Winding-Order: Godot betrachtet Clockwise (von der Vorderseite + # betrachtet) als Vorderseite. Von oben gelesen muss jedes Dreieck + # IM Uhrzeigersinn laufen, sonst cullt der Renderer die komplette + # Deckfläche als Backface (Karte t_7ac70014: „dunkle Platte“). + indices.append_array([i, i + 1, i + res, i + 1, i + res + 1, i + res]) + x += 1 + z += 1 + + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = positions + arrays[Mesh.ARRAY_NORMAL] = normals + arrays[Mesh.ARRAY_COLOR] = colors + arrays[Mesh.ARRAY_TEX_UV2] = heights_uv # Höhe je Vertex → Kontur-Shader + arrays[Mesh.ARRAY_INDEX] = indices + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + _mi.mesh = mesh + _triangles = indices.size() / 3 + + # --- Splat-Gewichtstextur (t_7a0a6b79): map_size + Sub-Rect-Update -------- + # map_size_x/z in Weltmetern (Shader rechnet map_uv = Weltkoord. über Map). + # Mal-Stroke: splat_rect_cells = betroffenes Zellrechteck → NUR die Region + # neu ableiten. Voll-Rebuild (Load/Undo/Stroke-Ende): Textur frisch bauen. + var mat := _mi.material_override + if mat is ShaderMaterial: + var shm := mat as ShaderMaterial + shm.set_shader_parameter("map_size_x", size_x) + shm.set_shader_parameter("map_size_z", size_z) + # Material-ID-Karte (Karte 25.10.): Malen = gleiche Größe → nur Daten + # tauschen (1 Upload); sonst neu anlegen. splat_rect_cells bleibt als + # Parameter (Aufrufer-Vertrag), wird aber nicht mehr gebraucht. + if _splat_tex != null and _splat_res == mres and mats.size() == mres * mres \ + and _mix_b_tex != null and _mix_b_cache.size() == mres * mres: + _splat_image.set_data(mres, mres, false, Image.FORMAT_R8, mats) + _splat_tex.update(_splat_image) + _mix_b_image.set_data(mres, mres, false, Image.FORMAT_R8, _mix_b_cache) + _mix_b_tex.update(_mix_b_image) + _mix_w_image.set_data(mres, mres, false, Image.FORMAT_R8, _mix_w_cache) + _mix_w_tex.update(_mix_w_image) + else: + _build_splat(shm, mats, mres, size_x, size_z) + + +## Misch-Schicht fürs nächste update_heights (Live-Malen, Karte 25.10.). +func set_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void: + _mix_b_cache = mix_b + _mix_w_cache = mix_w + + +## Raster einer Material-Schicht aus ihrer Länge (quadratisch). Unpassend/leer +## → Höhenraster (alte Karten, Legacy ohne Schicht). +static func mat_res_of(mats: PackedByteArray, res: int) -> int: + if mats.is_empty(): + return res + var r := int(roundf(sqrt(float(mats.size())))) + return r if r >= 2 and r * r == mats.size() else res + + +## Höhenlinien-Uniform an die aktuelle Situation anpassen (Pref ∧ Relief ≥ 2 m). +func _apply_contour_state(span_ok: bool) -> void: + if _mi == null or not is_instance_valid(_mi): + return + var mat := _mi.material_override + if mat is ShaderMaterial: + mat.set_shader_parameter("linien_an", 1.0 if (_contours_pref and span_ok) else 0.0) + + +## Vertex-Farbe EINER Zelle: Material × Höhen-Kurve × Pseudo-Licht. +## Static (statt inline), damit Tests die Mathematik direkt prüfen können und +## Editor/Client die IDENTISCHE Formel teilen (Konstanten spiegeln — gleiche +## Kommentare/Konstanten wie scripts/world/svmap_terrain.gd im Client-Repo). +## Alpha = Material-ID (Karte t_ab408127): m/N gepackt — der Shader (Client +## UND Editor-Vorschau) sampelt die Texture2DArray-Layer über die Alpha. +## Der Shading-Faktor wirkt NUR auf RGB (Color × float skaliert auch Alpha — +## deshalb wird die ID erst im Return gezielt gesetzt). +static func relief_color(mat_id: int, h: float, hmin: float, hmax: float, + normal: Vector3) -> Color: + var m := clampi(mat_id, 0, MATERIAL_COLORS.size() - 1) + var span := maxf(hmax - hmin, 0.0001) + var t := clampf((h - hmin) / span, 0.0, 1.0) + # Steilere Kurve statt linear: S-Kurve hebt die Enden, senkt die Mitte — + # Hügel/Senken trennen sich stärker als bei t direkt. + var t_s := t * t * (3.0 - 2.0 * t) + var hf := lerpf(SHADING_LOW, SHADING_HIGH, t_s) + # Pseudo-Licht: Normale × feste Sonnenrichtung → Hillshade. + var nd := clampf(normal.normalized().dot(RELIEF_SUN.normalized()), 0.0, 1.0) + var x := clampf((nd - LIGHT_T0) / (LIGHT_T1 - LIGHT_T0), 0.0, 1.0) + var lf := lerpf(LIGHT_LOW, LIGHT_HIGH, x * x * (3.0 - 2.0 * x)) + # Ausbrennen-Fix (t_7a0a6b79): der Faktor wird auf 1.0 normalisiert. Vorher + # lief hf·lf bis 1.35 und RGBA8 klemmte auf 1.0 — ALLE Relief-Unterschiede + # oberhalb 1.0 kollabierten zu flachem Weiß (Slys „ausgebrannte Weißflächen“ + # auf Sand hell/Schnee). Relative Spanne bleibt identisch (0.40…1.35 → + # 0.296…1.0), Kontrast-Verhältnis unverändert, Shading bleibt sichtbar. + var f := clampf(hf * lf, SHADE_MIN, SHADE_MAX) / SHADE_MAX + var base := MATERIAL_COLORS[m] as Color + # Kanäle auf 0..1 klemmen: der Mesh-Speicher (ARRAY_COLOR, RGBA8) clamp't + # genauso — der Helfer muss die RENDER-Wahrheit liefern (Probe-Vergleich), + # nicht die unbehandelte Mathematik. + return Color(clampf(base.r * f, 0.0, 1.0), clampf(base.g * f, 0.0, 1.0), + clampf(base.b * f, 0.0, 1.0), float(m) / float(MATERIAL_COLORS.size())) + + +func triangle_count() -> int: + return _triangles + + +func resolution() -> int: + return _res + + +## Splat-Gewichtstextur komplett ableiten (t_7a0a6b79). Die Zell-Schicht +## (mats, res²) bleibt die Quelle der Wahrheit — die Textur ist reine +## Ableitung (Datenformat unverändert). Unbrauchbare Schicht → alte Textur +## bleibt (Robustheit, kein Crash). +## size_x/size_z (t_5c94d098): Karten-Kantenlänge in Metern → Texel-Dichte der +## Splat (1 m/Texel bei 1024 m; vorher 2 m/Texel = sichtbare Stufen). +func _build_splat(mat: ShaderMaterial, mats: PackedByteArray, res: int, + _size_x: float = 0.0, _size_z: float = 0.0) -> void: + if res < 2: + _splat_tex = null + return + # Karte 25.10.: Material-ID-Karte statt Splat-Gewichte (weiche Übergänge + # pro Pixel im Shader, Spiegel zum Client). + _splat_image = TerrainArrays.material_image(mats, res) + _splat_side = _splat_image.get_width() + _splat_res = res + _splat_tex = ImageTexture.create_from_image(_splat_image) + # Misch-Schicht: passt sie nicht zum Raster (Legacy/leer) → Nullen. + if _mix_b_cache.size() != res * res or _mix_w_cache.size() != res * res: + _mix_b_cache = PackedByteArray() + _mix_b_cache.resize(res * res) + _mix_w_cache = PackedByteArray() + _mix_w_cache.resize(res * res) + _mix_b_image = TerrainArrays.material_image(_mix_b_cache, res) + _mix_w_image = TerrainArrays.material_image(_mix_w_cache, res) + _mix_b_tex = ImageTexture.create_from_image(_mix_b_image) + _mix_w_tex = ImageTexture.create_from_image(_mix_w_image) + if mat != null: + mat.set_shader_parameter("material_map", _splat_tex) + mat.set_shader_parameter("material_b_map", _mix_b_tex) + mat.set_shader_parameter("material_w_map", _mix_w_tex) + mat.set_shader_parameter("material_res", float(_splat_side)) + + diff --git a/viewport/terrain_mesh.gd.uid b/viewport/terrain_mesh.gd.uid new file mode 100644 index 0000000..157ab7a --- /dev/null +++ b/viewport/terrain_mesh.gd.uid @@ -0,0 +1 @@ +uid://bwwwoxt0cxfj diff --git a/viewport/wasser.gdshader b/viewport/wasser.gdshader new file mode 100644 index 0000000..1504512 --- /dev/null +++ b/viewport/wasser.gdshader @@ -0,0 +1,92 @@ +shader_type spatial; +// Wasser-Oberfläche pro Map (Karte t_28fdef03) — SPIEGELGLEICHE Datei: +// scopedvr-client → res://shaders/wasser.gdshader +// scopedvr-map-editor → res://src/viewport/wasser.gdshader (byteidentischer +// Spiegel, sync via tools/sync_wasser.sh — wie terrain_splat/terrain_arrays). +// +// Quest-3-Perf-Vertrag (bewusste Absagen der Spec): KEINE echte Spiegelung, +// KEINE Brechung, KEIN SSR. Spiegel-Impression = Skybox-Farbreflex (uniform) +// + Fresnel-Glanz. Wellen NUR im Fragment-Shader (analytische 2-Layer-Sinus- +// Normalen — keine Wellen-Texturen, keine Geometrie-Bewegung). Ufer-Schaum + +// Flachwasser-Transparenz aus der Tiefe-Textur (ufer_tex, R8 = Tiefe/TIEFE_MAX, +// siehe SvmapWasser.ufer_bild). Ferne Fragmente vereinfachen sich selbst +// (Wasserdistanz-Regel): nur Grundfarbe, keine Wellen/Schaum-Mathematik. +// +// Eine Fläche, ein Draw-Call — Overdraw ist die einzige echte Kostenquelle. + +render_mode blend_mix, depth_draw_never, cull_back, unshaded, shadows_disabled; + +uniform vec4 farbe_flach : source_color = vec4(0.10, 0.30, 0.34, 1.0); +uniform vec4 farbe_tief : source_color = vec4(0.02, 0.10, 0.15, 1.0); +uniform vec3 reflex : source_color = vec3(0.72, 0.83, 0.92); // Skybox-Farbreflex (Wetter) +uniform float glanz : hint_range(0.0, 2.0) = 1.0; // Glint-Stärke (Wetter) +uniform sampler2D ufer_tex : filter_linear; // R = Tiefe 0..1 (0..TIEFE_MAX) +uniform float tiefe_max = 4.0; // SvmapWasser.TIEFE_MAX (Spiegel-Konstante) +uniform float schaum_band = 0.35; // SvmapWasser.SCHAUM_BAND (Meter) +uniform float wellen_scale = 0.55; // Wellenfrequenz (1/m) +uniform float wellen_staerke = 0.16; // Normal-Neigung (flach = Spiegel-Optik) + +varying vec3 welt_pos; // Welt-Position des Fragments (Fresnel + Distanz-Regel) + +void vertex() { + welt_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; +} + +// Analytische Wellen-Normale aus 2 Scroll-Ebenen (kleine Skala, verschiedene +// Richtungen/Geschwindigkeiten — läuft nie synchron): h = Σ A·sin(dot(p,d)·f + t·s), +// n ∝ (−∂h/∂x, 1, −∂h/∂z) mit ∂h/∂p = Σ A·f·d·cos(…). +vec3 wellen_normal(vec2 p, float t) { + vec2 d1 = vec2(0.94, 0.34); + vec2 d2 = vec2(-0.42, 0.91); + float f1 = wellen_scale * 2.1; + float f2 = wellen_scale * 3.7; + float s1 = 1.25; + float s2 = 1.75; + float a1 = wellen_staerke; + float a2 = wellen_staerke * 0.6; + vec2 g1 = d1 * (f1 * a1 * cos(dot(p, d1) * f1 + t * s1)); + vec2 g2 = d2 * (f2 * a2 * cos(dot(p, d2) * f2 + t * s2)); + vec2 g = g1 + g2; + return normalize(vec3(-g.x, 1.0, -g.y)); +} + +void fragment() { + // Tiefe am Fragment (ufer_tex, UV deckt die Map 1:1 ab — SvmapWasser.ufer_bild). + float tiefe01 = texture(ufer_tex, UV).r; + float tiefe = tiefe01 * tiefe_max; + + // Wasserdistanz-Regel: ferne Fragmente = vereinfachter Modus (Grundfarbe, + // flache Normale, kein Schaum). Weiches Fenster 45→90 m, pro Fragment. + float fern = smoothstep(45.0, 90.0, length(welt_pos - CAMERA_POSITION_WORLD)); + float detail = 1.0 - fern; + + vec3 n = wellen_normal(welt_pos.xz, TIME); + n = normalize(mix(vec3(0.0, 1.0, 0.0), n, detail)); + + vec3 v = normalize(CAMERA_POSITION_WORLD - welt_pos); + float fresnel = pow(1.0 - clamp(dot(n, v), 0.0, 1.0), 5.0); + + // Grundfarbe: seicht heller/durchsichtiger, tief dunkler. + vec3 grund = mix(farbe_flach.rgb, farbe_tief.rgb, clamp(tiefe01 * 1.4, 0.0, 1.0)); + + // Spiegel-Impression: Skybox-Farbreflex via Fresnel + Sonnen-Glint. + vec3 reflex_f = reflex * mix(0.25, 1.0, fresnel); + vec3 l = normalize(vec3(-0.45, 0.75, 0.5)); // SvmapTerrain.RELIEF_SUN (Spiegel) + float glint = pow(max(dot(reflect(-v, n), l), 0.0), 48.0) * glanz; + + // Ufer-Schaum: schmales Band an der Wasserlinie, Kante leicht wellig + // (Wellen-Phase pulsiert das Band ±20 %). + float schaum_grenze = schaum_band * (0.9 + 0.2 * sin(welt_pos.x * 1.7 + welt_pos.z * 1.3 + TIME * 1.2)); + float schaum = (1.0 - smoothstep(0.0, max(schaum_grenze, 0.02), tiefe)) * detail; + + vec3 farbe = grund + reflex_f + vec3(glint) + vec3(schaum * 0.85); + // Transparenz: Ufer fast klar, tiefes Wasser dicht. Schaum deckt voll. + float alpha = mix(0.38, 0.88, clamp(tiefe01 * 1.6, 0.0, 1.0)); + alpha = max(alpha, schaum * 0.9); + + // spatial-Vertrag: Farbe über ALBEDO, Transparenz über ALPHA (COLOR gibt + // es nur im canvas_item-Shader — schreibend dort = „Constants cannot be + // modified“). + ALBEDO = farbe; + ALPHA = alpha; +} diff --git a/viewport/wasser.gdshader.uid b/viewport/wasser.gdshader.uid new file mode 100644 index 0000000..71814f5 --- /dev/null +++ b/viewport/wasser.gdshader.uid @@ -0,0 +1 @@ +uid://c7s4uhqs208da