scopedvr-map-core/ui/panels/map_panel.gd
2026-11-01 19:31:33 +01:00

264 lines
10 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends "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