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