scopedvr-map-editor/src/ui/resize_map_dialog.gd

202 lines
No EOL
7.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

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

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://src/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 _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)
var vbox := VBoxContainer.new()
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
vbox.add_theme_constant_override("separation", UIScale.px(12))
add_child(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)
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 (Gras).")
hint.modulate = Color(1, 1, 1, 0.7)
vbox.add_child(hint)
preview_label = _make_label("")
preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
preview_label.custom_minimum_size = UIScale.v2(0, 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
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
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 flaches Gras.")
# 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)
## 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