Map-Editor: RMB = Flug mit absolutem Vorrang (t_2470de89) — Terrain-Senken auf Strg+LMB, Hand-Objekt-Abwurf auf Esc.
- Terrain-Pinsel: Rechtsklick startet keinen Strich mehr; Strg+LMB invertiert (Heben↔Senken, Blender/Photoshop-Konvention; Malen: Strg+Klick = Gras zurück). - Platzieren: RMB bricht nicht mehr ab — Objekt bleibt in der Hand, Abwurf nur über Esc. build_tool/spawn_tool: RMB-Deselect/Werkzeugwechsel entfernt (Auswahl-Deselect = Esc / LMB auf Leere). - orbit_camera: rmb_filter entfernt — Flug startet in JEDEM Werkzeugzustand. - docs/tasten.md + H-Overlay + Status-/Paneltexte aktualisiert. - Neuer Test tests/test_rmb_vorrang.gd (32 Checks, echte Main-UI headless); in tools/run_tests.sh aufgenommen. Suite grün (test_glb_view: 12/22 FAIL auch auf origin/main — fehlende GLB-Quellen im Scratch-Workspace, umgebungsbedingt).
This commit is contained in:
parent
cda6f48b01
commit
7521db7f8d
12 changed files with 376 additions and 73 deletions
|
|
@ -58,7 +58,8 @@ Boden/Wetter-Stimmung — siehe Commit 84bc081 und `svmap_io.gd`.)*
|
||||||
- **Objekt-Platzierung mit Ghost-Preview (`src/editor/interaction.gd`):**
|
- **Objekt-Platzierung mit Ghost-Preview (`src/editor/interaction.gd`):**
|
||||||
Bibliothek links (deutsche Namen + Poly-Zahl), Klick startet den
|
Bibliothek links (deutsche Namen + Poly-Zahl), Klick startet den
|
||||||
Platzier-Modus — eine transparente Vorschau schwebt am Cursor auf dem Boden
|
Platzier-Modus — eine transparente Vorschau schwebt am Cursor auf dem Boden
|
||||||
(Höhe kommt aus dem Terrain), Klick setzt, Rechtsklick/ESC bricht ab,
|
(Höhe kommt aus dem Terrain), Klick setzt, ESC bricht ab (Rechte
|
||||||
|
Maustaste = Flug, das Objekt bleibt in der Hand — Karte t_2470de89),
|
||||||
Mausrad dreht die Vorschau VOR dem Setzen (15°-Raster). Modus bleibt an
|
Mausrad dreht die Vorschau VOR dem Setzen (15°-Raster). Modus bleibt an
|
||||||
für schnelles Mehrfachsetzen. Poly-Budget wird VOR dem Platzieren geprüft.
|
für schnelles Mehrfachsetzen. Poly-Budget wird VOR dem Platzieren geprüft.
|
||||||
- **Auswahl-Anfassen:** Klick wählt ein Objekt (orange + Emission), Ziehen
|
- **Auswahl-Anfassen:** Klick wählt ein Objekt (orange + Emission), Ziehen
|
||||||
|
|
@ -309,8 +310,10 @@ Shortcut-Karten — Karten 2/3 ergänzen dort.
|
||||||
beim Flug-Exit werden Ziel/Winkel/Abstand aus Flugposition/Blickrichtung
|
beim Flug-Exit werden Ziel/Winkel/Abstand aus Flugposition/Blickrichtung
|
||||||
zurückgerechnet (kein Kamera-Sprung, getestet). Mathe pur in
|
zurückgerechnet (kein Kamera-Sprung, getestet). Mathe pur in
|
||||||
`src/editor/fly_math.gd`, Kamera dünn (`src/viewport/orbit_camera.gd`).
|
`src/editor/fly_math.gd`, Kamera dünn (`src/viewport/orbit_camera.gd`).
|
||||||
Konflikt-Auflösung: RMT beim Platzieren gehört dem Ghost-Abbruch
|
Konflikt-Auflösung: RMB hat ABSOLUTEN Vorrang für den Flug — in JEDEM
|
||||||
(`rmb_filter`), Mausrad im Flug gehört dem Tempo (interaction blockt).
|
Werkzeugzustand (Terrain, Bauen, Spawn, Platzieren; Karte t_2470de89,
|
||||||
|
kein `rmb_filter` mehr), Mausrad im Flug gehört dem Tempo (interaction
|
||||||
|
blockt).
|
||||||
- **F** = Auswahl ins Bild zentrieren — Orbit-Ziel wandert auf den Auswahl-
|
- **F** = Auswahl ins Bild zentrieren — Orbit-Ziel wandert auf den Auswahl-
|
||||||
Mittelpunkt (Anker + halbe Mesh-Höhe, `SelectionMath.focus_center`); im
|
Mittelpunkt (Anker + halbe Mesh-Höhe, `SelectionMath.focus_center`); im
|
||||||
Flug dreht sich nur die Blickrichtung.
|
Flug dreht sich nur die Blickrichtung.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Konventionen (alle Karten):
|
||||||
| Mittlere Maustaste ziehen | Orbit: Um die Karte drehen |
|
| Mittlere Maustaste ziehen | Orbit: Um die Karte drehen |
|
||||||
| Mittlere Maustaste + Umschalt ziehen | Pan: Karte verschieben |
|
| Mittlere Maustaste + Umschalt ziehen | Pan: Karte verschieben |
|
||||||
| Mausrad | Zoom (aus, wenn Mausrad gerade ein Objekt dreht) |
|
| Mausrad | Zoom (aus, wenn Mausrad gerade ein Objekt dreht) |
|
||||||
| **Rechte Maustaste GEDACHT halten** | **Freelook-Flug:** Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter |
|
| **Rechte Maustaste GEDACHT halten** | **Freelook-Flug:** Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter. **Funktioniert in JEDEM Werkzeug** (Terrain, Bauen, Spawn-Zonen, Platzieren) — die rechte Maustaste hat ABSOLUTEN Vorrang, kein Werkzeug belegt sie (Karte t_2470de89) |
|
||||||
| Umschalt (im Flug) | schneller fliegen (×3) |
|
| Umschalt (im Flug) | schneller fliegen (×3) |
|
||||||
| Alt (im Flug) | langsamer fliegen (×0,3) |
|
| Alt (im Flug) | langsamer fliegen (×0,3) |
|
||||||
| Mausrad (im Flug) | Grundgeschwindigkeit dauerhaft ändern (2–300 m/s) |
|
| Mausrad (im Flug) | Grundgeschwindigkeit dauerhaft ändern (2–300 m/s) |
|
||||||
|
|
@ -91,6 +91,9 @@ nichts geht versehentlich verloren.
|
||||||
| **2** | Pinselmodus Senken |
|
| **2** | Pinselmodus Senken |
|
||||||
| **3** | Pinselmodus Glätten |
|
| **3** | Pinselmodus Glätten |
|
||||||
| **4** | Pinselmodus Malen (mit der zuletzt gewählten Materialfarbe) |
|
| **4** | Pinselmodus Malen (mit der zuletzt gewählten Materialfarbe) |
|
||||||
|
| **Linksklick-Ziehen** | Boden formen bzw. malen (je nach Pinselmodus) |
|
||||||
|
| **Strg + Linksklick-Ziehen** | **Gegenteil:** bei Heben senkt er, bei Senken hebt er (Invert-Konvention wie Blender/Photoshop); im Malen-Modus malt Strg+Klick Gras zurück |
|
||||||
|
| Rechte Maustaste | **Kein Terrain-Strich mehr** — gehört dem Freelook-Flug (absoluter Vorrang, siehe Navigation) |
|
||||||
| Strg halten | temporärer Snap-Zwang beim Objekt-Bewegen/Platzieren (gilt editorweit, nicht nur hier) |
|
| Strg halten | temporärer Snap-Zwang beim Objekt-Bewegen/Platzieren (gilt editorweit, nicht nur hier) |
|
||||||
|
|
||||||
Konfliktfreiheit: Die Ziffern 1–4 sind NUR im Terrain-Werkzeug belegt — im
|
Konfliktfreiheit: Die Ziffern 1–4 sind NUR im Terrain-Werkzeug belegt — im
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ extends Node3D
|
||||||
## Bild↑/Bild↓ → Auswahl anheben/absenken (0,2 m — Wände aufs
|
## Bild↑/Bild↓ → Auswahl anheben/absenken (0,2 m — Wände aufs
|
||||||
## Fundament setzen)
|
## Fundament setzen)
|
||||||
## Entf → Auswahl löschen
|
## Entf → Auswahl löschen
|
||||||
## Rechtsklick/ESC → Auswahl weg, nochmal = Werkzeug beenden
|
## ESC → Auswahl weg, nochmal = Werkzeug beenden
|
||||||
|
## (Rechte Maustaste = immer Flug, t_2470de89)
|
||||||
##
|
##
|
||||||
## UNDO: EIN Rückgängig-Schritt PRO Bauteil (ActionHistory, Block "gebaeude").
|
## UNDO: EIN Rückgängig-Schritt PRO Bauteil (ActionHistory, Block "gebaeude").
|
||||||
|
|
||||||
|
|
@ -143,14 +144,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
else:
|
else:
|
||||||
_place_piece()
|
_place_piece()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
elif mb.button_index == MOUSE_BUTTON_RIGHT and mb.pressed:
|
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
|
||||||
if selected_index >= 0:
|
# sie gehört mit absolutem Vorrang dem Freelook-Flug. Deselect/Werkzeug
|
||||||
selected_index = -1
|
# beenden läuft über ESC (main.gd), die Auswahl bleibt im Flug erhalten.
|
||||||
view_builder.call("set_gebaeude_selected", -1)
|
|
||||||
main.call("_set_status", "Auswahl aufgehoben — Linksklick setzt weiter Bauteile.")
|
|
||||||
else:
|
|
||||||
main.call("_set_tool_mode", "objects")
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
|
|
||||||
|
|
||||||
func _update_cursor(viewport_pos: Vector2) -> void:
|
func _update_cursor(viewport_pos: Vector2) -> void:
|
||||||
|
|
@ -223,7 +219,7 @@ func _select_at(viewport_pos: Vector2) -> void:
|
||||||
selected_index = int(pick["piece"])
|
selected_index = int(pick["piece"])
|
||||||
view_builder.call("set_gebaeude_selected", selected_index)
|
view_builder.call("set_gebaeude_selected", selected_index)
|
||||||
var label := _piece_label(selected_index)
|
var label := _piece_label(selected_index)
|
||||||
main.call("_set_status", "%s ausgewählt — R drehen · Bild↑/↓ anheben · Entf löschen · Rechtsklick: Auswahl weg." % label)
|
main.call("_set_status", "%s ausgewählt — R drehen · Bild↑/↓ anheben · Entf löschen · Esc: Auswahl weg." % label)
|
||||||
|
|
||||||
|
|
||||||
func _piece_label(idx: int) -> String:
|
func _piece_label(idx: int) -> String:
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ func start_placement(oid: String) -> void:
|
||||||
var label: String = main.object_label(oid)
|
var label: String = main.object_label(oid)
|
||||||
var hinweis := "" if not GlbMeshStore.fehlt(oid) \
|
var hinweis := "" if not GlbMeshStore.fehlt(oid) \
|
||||||
else " (Platzhalter — echte GLB fehlt für %s)" % 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, Rechtsklick/ESC bricht ab." % [label, hinweis])
|
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)
|
main.call("_highlight_library", oid)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -314,7 +314,7 @@ func _place_object(viewport_pos: Vector2) -> void:
|
||||||
main.call("_after_structure_change")
|
main.call("_after_structure_change")
|
||||||
main.call("_set_status", "%s platziert. Tipp: anklicken zum Verschieben (Ziehen) und Drehen (R oder Mausrad) — Entf löscht es." % main.object_label(place_oid))
|
main.call("_set_status", "%s platziert. Tipp: anklicken zum Verschieben (Ziehen) und Drehen (R oder Mausrad) — Entf löscht es." % main.object_label(place_oid))
|
||||||
# Platzier-Modus bleibt aktiv (Mehrfach-Platzieren ohne erneutes Klicken),
|
# Platzier-Modus bleibt aktiv (Mehrfach-Platzieren ohne erneutes Klicken),
|
||||||
# Auswahl auf das frische Objekt legen? Nein — Ghost bleibt Vorrang; ESC/Rechtsklick beendet.
|
# Auswahl auf das frische Objekt legen? Nein — Ghost bleibt Vorrang; ESC beendet.
|
||||||
main.call("_mark_tree_selection", new_index)
|
main.call("_mark_tree_selection", new_index)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -877,11 +877,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
||||||
_on_left_release(mb)
|
_on_left_release(mb)
|
||||||
elif mb.button_index == MOUSE_BUTTON_RIGHT and mb.pressed:
|
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
|
||||||
if placing:
|
# sie gehört mit absolutem Vorrang dem Freelook-Flug (orbit_camera.gd).
|
||||||
cancel_placement()
|
# Beim Platzieren bleibt das Objekt IN der Hand, Abwurf nur über Esc.
|
||||||
main.call("_set_status", "Platzieren abgebrochen.")
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
elif mb.button_index == MOUSE_BUTTON_WHEEL_UP and mb.pressed:
|
elif mb.button_index == MOUSE_BUTTON_WHEEL_UP and mb.pressed:
|
||||||
if _camera_flying():
|
if _camera_flying():
|
||||||
return # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
|
return # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
|
||||||
|
|
@ -1114,7 +1112,7 @@ func demo_park_ghost(world_pos: Vector3, oid: String) -> void:
|
||||||
_ghost.visible = true
|
_ghost.visible = true
|
||||||
_ghost_active = true
|
_ghost_active = true
|
||||||
main.call("_highlight_library", oid)
|
main.call("_highlight_library", oid)
|
||||||
main.call("_set_status", "%s: führe die Maus über die Karte — Klick setzt es. Mausrad dreht vorher, Rechtsklick/ESC bricht ab." % main.object_label(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
|
## Demo-Hook (Gruppen-Smoke, Karte t_f6f29d86): die Auswahl EXAKT so ziehen
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ extends Node3D
|
||||||
## Linksklick auf Zone = Zone auswählen; ZIEHEN verschiebt sie (Terrain-Höhe)
|
## Linksklick auf Zone = Zone auswählen; ZIEHEN verschiebt sie (Terrain-Höhe)
|
||||||
## Regel rechts = Radius der neuen Zonen; mit Auswahl: Radius DER Zone
|
## Regel rechts = Radius der neuen Zonen; mit Auswahl: Radius DER Zone
|
||||||
## Entf = ausgewählte Zone löschen
|
## Entf = ausgewählte Zone löschen
|
||||||
## Rechtsklick/ESC = Auswahl weg / Werkzeug beendet (ESC in main.gd)
|
## 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
|
## Jede Operation ist EIN Undo-Eintrag über die gemeinsame ActionHistory
|
||||||
## (Blöcke spawn_zones + design_notes, SpawnOps.snapshot_blocks).
|
## (Blöcke spawn_zones + design_notes, SpawnOps.snapshot_blocks).
|
||||||
|
|
@ -171,13 +172,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
||||||
_on_left_release()
|
_on_left_release()
|
||||||
elif mb.button_index == MOUSE_BUTTON_RIGHT and mb.pressed:
|
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
|
||||||
if selected_team >= 0:
|
# sie gehört mit absolutem Vorrang dem Freelook-Flug. Deselect/Werkzeug
|
||||||
_clear_selection()
|
# beenden läuft über ESC (main.gd), die Auswahl bleibt im Flug erhalten.
|
||||||
main.call("_set_status", "Auswahl aufgehoben — klicke auf den Boden, um eine neue Zone zu malen.")
|
|
||||||
else:
|
|
||||||
main.call("_set_tool_mode", "objects")
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
|
|
||||||
|
|
||||||
func _update_cursor(viewport_pos: Vector2) -> void:
|
func _update_cursor(viewport_pos: Vector2) -> void:
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ extends Node3D
|
||||||
##
|
##
|
||||||
## Bedienung:
|
## Bedienung:
|
||||||
## Linksklick-Ziehen = gewählter Pinsel-Modus (Heben / Senken / Glätten)
|
## Linksklick-Ziehen = gewählter Pinsel-Modus (Heben / Senken / Glätten)
|
||||||
## Rechtsklick-Ziehen = Gegenteil (bei Heben senkt er, bei Senken hebt er —
|
## Strg+Linksklick = das Gegenteil (bei Heben senkt er, bei Senken hebt
|
||||||
## kein Umschalten nötig; bei Glätten senkt er)
|
## 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
|
## Der ganze Strich = EIN Undo-Eintrag (Strg+Z stellt die Form VOR dem
|
||||||
## Strich wieder her, nicht nur eine Zwischenstufe).
|
## Strich wieder her, nicht nur eine Zwischenstufe).
|
||||||
##
|
##
|
||||||
|
|
@ -151,26 +154,26 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
var mb := event as InputEventMouseButton
|
var mb := event as InputEventMouseButton
|
||||||
if mb.button_index == MOUSE_BUTTON_LEFT:
|
if mb.button_index == MOUSE_BUTTON_LEFT:
|
||||||
if mb.pressed:
|
if mb.pressed:
|
||||||
_begin_stroke(brush_mode)
|
# 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 mb.ctrl_pressed:
|
||||||
|
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)
|
||||||
else:
|
else:
|
||||||
_end_stroke()
|
_end_stroke()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
elif mb.button_index == MOUSE_BUTTON_RIGHT and mb.pressed:
|
# Rechte Maustaste: absichtlich NICHT behandelt — sie gehört mit
|
||||||
# Rechtsklick = das Gegenteil zum Modell: bei Heben senken,
|
# absolutem Vorrang dem Freelook-Flug (orbit_camera.gd, Karte
|
||||||
# bei Senken heben, bei Glätten senken (kein Umschalten nötig).
|
# t_2470de89), in jedem Werkzeugzustand.
|
||||||
# Im Malen-Modus malt Rechtsklick Gras zurück (Radierer-Semantik).
|
|
||||||
if brush_mode == TerrainField.Mode.PAINT:
|
|
||||||
_begin_stroke(TerrainField.Mode.PAINT, 0)
|
|
||||||
else:
|
|
||||||
var opposite: int = TerrainField.Mode.LOWER
|
|
||||||
if brush_mode == TerrainField.Mode.LOWER:
|
|
||||||
opposite = TerrainField.Mode.RAISE
|
|
||||||
_begin_stroke(opposite)
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
elif mb.button_index == MOUSE_BUTTON_RIGHT and not mb.pressed:
|
|
||||||
if _stroking:
|
|
||||||
_end_stroke()
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
|
|
||||||
|
|
||||||
func _update_cursor(viewport_pos: Vector2) -> void:
|
func _update_cursor(viewport_pos: Vector2) -> void:
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ static func text() -> String:
|
||||||
["Mittlere Maustaste ziehen", "Orbit: um die Karte drehen"],
|
["Mittlere Maustaste ziehen", "Orbit: um die Karte drehen"],
|
||||||
["Mittlere Maustaste + Umschalt ziehen", "Pan: Karte verschieben"],
|
["Mittlere Maustaste + Umschalt ziehen", "Pan: Karte verschieben"],
|
||||||
["Mausrad", "Zoom (aus, während ein Objekt gedreht wird)"],
|
["Mausrad", "Zoom (aus, während ein Objekt gedreht wird)"],
|
||||||
["Rechte Maustaste GEDACHT halten", "Freelook-Flug: Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter"],
|
["Rechte Maustaste GEDACHT halten", "Freelook-Flug: Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter — funktioniert in JEDEM Werkzeug (Terrain, Bauen, Platzieren), die rechte Maustaste hat immer Vorrang"],
|
||||||
["Umschalt (im Flug)", "schneller fliegen (×3)"],
|
["Umschalt (im Flug)", "schneller fliegen (×3)"],
|
||||||
["Alt (im Flug)", "langsamer fliegen (×0,3)"],
|
["Alt (im Flug)", "langsamer fliegen (×0,3)"],
|
||||||
["Mausrad (im Flug)", "Grundgeschwindigkeit dauerhaft ändern (2–300 m/s)"],
|
["Mausrad (im Flug)", "Grundgeschwindigkeit dauerhaft ändern (2–300 m/s)"],
|
||||||
|
|
@ -47,7 +47,7 @@ static func text() -> String:
|
||||||
["1 / 2 / 3 / 4", "Pinselmodus: Heben / Senken / Glätten / Malen"],
|
["1 / 2 / 3 / 4", "Pinselmodus: Heben / Senken / Glätten / Malen"],
|
||||||
["[ / ]", "Pinselgröße: Klein (4 m) / Mittel (12 m) / Groß (32 m) zyklisch"],
|
["[ / ]", "Pinselgröße: Klein (4 m) / Mittel (12 m) / Groß (32 m) zyklisch"],
|
||||||
["Linksklick-Ziehen", "Boden formen bzw. malen"],
|
["Linksklick-Ziehen", "Boden formen bzw. malen"],
|
||||||
["Rechtsklick-Ziehen", "Gegenteil formen / Gras zurückmalen"],
|
["Strg+Linksklick-Ziehen", "Gegenteil: senken (bei Heben) / heben (bei Senken) / Gras zurückmalen"],
|
||||||
]))
|
]))
|
||||||
out += _header("Datei & Undo")
|
out += _header("Datei & Undo")
|
||||||
out += _table(_rows([
|
out += _table(_rows([
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,7 @@ func _build_left_panel() -> PanelContainer:
|
||||||
panel.add_child(vbox)
|
panel.add_child(vbox)
|
||||||
|
|
||||||
vbox.add_child(_header_label("Objekt-Bibliothek"))
|
vbox.add_child(_header_label("Objekt-Bibliothek"))
|
||||||
vbox.add_child(_muted_label("Objekt anklicken, dann in die Karte klicken — der Modus bleibt an bis Rechtsklick/ESC."))
|
vbox.add_child(_muted_label("Objekt anklicken, dann in die Karte klicken — der Modus bleibt an bis ESC (Rechte Maustaste = Flug)."))
|
||||||
|
|
||||||
var scroll := ScrollContainer.new()
|
var scroll := ScrollContainer.new()
|
||||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
|
@ -503,9 +503,10 @@ func _build_center_viewport() -> SubViewportContainer:
|
||||||
world.add_child(interaction)
|
world.add_child(interaction)
|
||||||
interaction.setup(self, camera, view_builder)
|
interaction.setup(self, camera, view_builder)
|
||||||
camera.set("wheel_filter", Callable(interaction, "wheel_blocked"))
|
camera.set("wheel_filter", Callable(interaction, "wheel_blocked"))
|
||||||
# Freelook-Flug-Verdrahtung (Karte t_c46d31c2): RMT gehört beim Platzieren
|
# Freelook-Flug-Verdrahtung (Karte t_c46d31c2): Status-Meldungen der
|
||||||
# dem Ghost-Abbruch, Status-Meldungen der Kamera laufen ins Statusfeld.
|
# Kamera laufen ins Statusfeld. Karte t_2470de89: die rechte Maustaste
|
||||||
camera.set("rmb_filter", Callable(self, "_fly_rmb_blocked"))
|
# gehört IMMER dem Flug — kein Filter mehr, die Werkzeuge fassen RMB
|
||||||
|
# nicht mehr an.
|
||||||
camera.set("status_cb", Callable(self, "_set_status"))
|
camera.set("status_cb", Callable(self, "_set_status"))
|
||||||
|
|
||||||
# Terrain-Pinsel (Karte t_9aba04f1) — eigener Eingabe-Knoten im Weltraum.
|
# Terrain-Pinsel (Karte t_9aba04f1) — eigener Eingabe-Knoten im Weltraum.
|
||||||
|
|
@ -789,8 +790,8 @@ func _build_right_panel() -> PanelContainer:
|
||||||
terrain_panel.add_child(terrain_strength_slider)
|
terrain_panel.add_child(terrain_strength_slider)
|
||||||
|
|
||||||
terrain_panel.add_child(_muted_label(
|
terrain_panel.add_child(_muted_label(
|
||||||
"Linksklick-Ziehen formt bzw. malt (je nach Werkzeug), Rechtsklick-Ziehen macht das Gegenteil — im Malen-Modus malt Rechtsklick Gras zurück. "
|
"Linksklick-Ziehen formt bzw. malt (je nach Werkzeug), Strg+Linksklick macht das Gegenteil — im Malen-Modus malt Strg+Klick Gras zurück. "
|
||||||
+ "Jeder Strich ist EIN Rückgängig-Schritt (Strg+Z). ESC beendet den Terrain-Modus."))
|
+ "Jeder Strich ist EIN Rückgängig-Schritt (Strg+Z). Rechte Maustaste = Flug, ESC beendet den Terrain-Modus."))
|
||||||
|
|
||||||
# --- Spawn-Zonen (Karte t_c4162c5d): Live-Status IMMER sichtbar ---
|
# --- Spawn-Zonen (Karte t_c4162c5d): Live-Status IMMER sichtbar ---
|
||||||
vbox.add_child(_separator())
|
vbox.add_child(_separator())
|
||||||
|
|
@ -1177,10 +1178,8 @@ func _focus_selection() -> void:
|
||||||
_set_status("%d Objekt(e) ins Bild zentriert — Strg+Klick/Strg+LMB markiert weitere dazu, Entf löscht die ganze Auswahl." % indices.size())
|
_set_status("%d Objekt(e) ins Bild zentriert — Strg+Klick/Strg+LMB markiert weitere dazu, Entf löscht die ganze Auswahl." % indices.size())
|
||||||
|
|
||||||
|
|
||||||
## Rechtsklick-Filter der Kamera: während des Platzierens gehört die rechte
|
## Karte t_2470de89: Der alte Rechtsklick-Filter (_fly_rmb_blocked) ist weg —
|
||||||
## Maustaste dem Ghost-Abbruch (interaction) — der Flug startet dann nicht.
|
## RMB startet den Freelook-Flug in JEDEM Werkzeugzustand (absoluter Vorrang).
|
||||||
func _fly_rmb_blocked() -> bool:
|
|
||||||
return interaction.placing
|
|
||||||
|
|
||||||
|
|
||||||
func _refresh_history_buttons() -> void:
|
func _refresh_history_buttons() -> void:
|
||||||
|
|
@ -1523,7 +1522,7 @@ func _enter_terrain_mode() -> void:
|
||||||
return
|
return
|
||||||
tool_mode = "terrain"
|
tool_mode = "terrain"
|
||||||
terrain_panel.visible = true
|
terrain_panel.visible = true
|
||||||
help_label.text = "Linksklick-Ziehen: Boden formen oder malen · Rechtsklick-Ziehen: Gegenteil / Gras zurück ·\n1/2/3/4: Heben/Senken/Glätten/Malen · [ / ]: Pinselgröße Klein/Mittel/Groß ·\nPinselgröße & Material rechts · Mittlere Maustaste: Kamera · Strg+Z: letzter Strich weg"
|
help_label.text = "Linksklick-Ziehen: Boden formen oder malen · Strg+Linksklick: Gegenteil / Gras zurück ·\n1/2/3/4: Heben/Senken/Glätten/Malen · [ / ]: Pinselgröße Klein/Mittel/Groß ·\nPinselgröße & Material rechts · Rechte Maustaste: Flug · Strg+Z: letzter Strich weg"
|
||||||
terrain_edit_btn.button_pressed = true
|
terrain_edit_btn.button_pressed = true
|
||||||
terrain_tool.call("set_brush_mode", _selected_terrain_mode())
|
terrain_tool.call("set_brush_mode", _selected_terrain_mode())
|
||||||
_set_status("Terrain-Modus an: LINKSKLICK-Ziehen formt den Boden, im Malen-Modus färbt er das Gelände (Gras/Sand/Fels). Regler rechts stellen Pinsel und Stärke ein.")
|
_set_status("Terrain-Modus an: LINKSKLICK-Ziehen formt den Boden, im Malen-Modus färbt er das Gelände (Gras/Sand/Fels). Regler rechts stellen Pinsel und Stärke ein.")
|
||||||
|
|
@ -2296,7 +2295,7 @@ func _start_terrain_demo(args: PackedStringArray) -> void:
|
||||||
terrain_tool.call("demo_stroke", 90.0, 50.0, TerrainField.Mode.LOWER, 1.2)
|
terrain_tool.call("demo_stroke", 90.0, 50.0, TerrainField.Mode.LOWER, 1.2)
|
||||||
terrain_tool.call("demo_park_brush", 90.0, 50.0)
|
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)
|
_demo_camera(Vector3(90.0, 2.0, 50.0), 150.0, 32.0, -50.0)
|
||||||
_set_status("Demo 2/5: Senke GESSENKT (Rechtsklick-Ziehen senkt, ohne umzuschalten).")
|
_set_status("Demo 2/5: Senke GESSENKT (Strg+Linksklick senkt, ohne umzuschalten).")
|
||||||
await _demo_shot(out_dir + "/terrain_lower.png")
|
await _demo_shot(out_dir + "/terrain_lower.png")
|
||||||
|
|
||||||
# 3) Hügelkuppe glätten (blauer Ring).
|
# 3) Hügelkuppe glätten (blauer Ring).
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,6 @@ var distance := 300.0
|
||||||
## Von main.gd gesetzt: Callable → true, wenn das Mausrad gerade ein Objekt
|
## Von main.gd gesetzt: Callable → true, wenn das Mausrad gerade ein Objekt
|
||||||
## drehen soll (Platzier-Modus oder Auswahl) statt zu zoomen.
|
## drehen soll (Platzier-Modus oder Auswahl) statt zu zoomen.
|
||||||
var wheel_filter := Callable()
|
var wheel_filter := Callable()
|
||||||
## Von main.gd gesetzt: Callable → true, wenn Rechtsklick belegt ist (z. B.
|
|
||||||
## Platzier-Abbruch) — dann startet der Freelook-Flug nicht.
|
|
||||||
var rmb_filter := Callable()
|
|
||||||
## Von main.gd gesetzt: Callable für Klartext-Status-Meldungen.
|
## Von main.gd gesetzt: Callable für Klartext-Status-Meldungen.
|
||||||
var status_cb := Callable()
|
var status_cb := Callable()
|
||||||
|
|
||||||
|
|
@ -73,9 +70,10 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
if event is InputEventMouseButton:
|
if event is InputEventMouseButton:
|
||||||
var mb := event as InputEventMouseButton
|
var mb := event as InputEventMouseButton
|
||||||
if mb.button_index == MOUSE_BUTTON_RIGHT:
|
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:
|
if mb.pressed:
|
||||||
if _rmb_blocked():
|
|
||||||
return
|
|
||||||
_enter_fly()
|
_enter_fly()
|
||||||
_fly_held = true
|
_fly_held = true
|
||||||
elif _fly_held:
|
elif _fly_held:
|
||||||
|
|
@ -134,10 +132,6 @@ func _wheel_blocked() -> bool:
|
||||||
return wheel_filter.is_valid() and bool(wheel_filter.call())
|
return wheel_filter.is_valid() and bool(wheel_filter.call())
|
||||||
|
|
||||||
|
|
||||||
func _rmb_blocked() -> bool:
|
|
||||||
return rmb_filter.is_valid() and bool(rmb_filter.call())
|
|
||||||
|
|
||||||
|
|
||||||
func distance_max() -> float:
|
func distance_max() -> float:
|
||||||
return 6000.0
|
return 6000.0
|
||||||
|
|
||||||
|
|
|
||||||
309
tests/test_rmb_vorrang.gd
Normal file
309
tests/test_rmb_vorrang.gd
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
extends SceneTree
|
||||||
|
## Editor-Test (headless): Karte t_2470de89 — RMB = Flug mit ABSOLUTEM Vorrang.
|
||||||
|
## Ausführen: godot4 --headless --path . --script res://tests/test_rmb_vorrang.gd
|
||||||
|
## Exit 0 = alles grün.
|
||||||
|
##
|
||||||
|
## Instanziert die ECHTE Main-UI (wie test_gui_smoke) und schickt echte
|
||||||
|
## Maus-/Tastatur-Events durch den Viewport. Geprüft wird:
|
||||||
|
## 1. Terrain-Werkzeug aktiv + rechte Maustaste → Flug startet, KEIN
|
||||||
|
## Terrain-Strich (Höhenfeld byte-identisch, keine History-Zeile).
|
||||||
|
## 2. Strg+Linksklick im Terrain-Werkzeug SENKT (bei Pinsel-Modus Heben),
|
||||||
|
## linker Klick hebt — Invert-Konvention.
|
||||||
|
## 3. Objekt „in der Hand“ (Platzier-Modus) + rechte Maustaste → Flug,
|
||||||
|
## das Objekt BLEIBT in der Hand nach dem Flug; Esc wirft ab.
|
||||||
|
## 4. Bauen-Werkzeug: rechte Maustaste fliegt, wechselt NICHT mehr das
|
||||||
|
## Werkzeug und wirft die Bauteil-Auswahl nicht weg.
|
||||||
|
|
||||||
|
var failures := 0
|
||||||
|
var checks := 0
|
||||||
|
var main: Control
|
||||||
|
var camera: Camera3D
|
||||||
|
var container: SubViewportContainer
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
_run_all.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _run_all() -> void:
|
||||||
|
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||||
|
_check(scene != null, "scenes/main.tscn laedt")
|
||||||
|
if scene == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
main = scene.instantiate()
|
||||||
|
root.add_child(main)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
container = _find_viewport_container(main)
|
||||||
|
_check(container != null, "SubViewportContainer gefunden")
|
||||||
|
camera = main.get("camera")
|
||||||
|
_check(camera != null, "Kamera-Referenz gefunden")
|
||||||
|
if container == null or camera == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
subvp = container.get_child(0) as SubViewport
|
||||||
|
_check(subvp != null, "SubViewport gefunden")
|
||||||
|
|
||||||
|
await _test_terrain_rmb_flies()
|
||||||
|
await _test_terrain_ctrl_lmb()
|
||||||
|
await _test_placement_rmb_keeps_hand()
|
||||||
|
await _test_build_rmb_flies()
|
||||||
|
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures == 0:
|
||||||
|
print("ALL OK: %d checks" % checks)
|
||||||
|
quit(0)
|
||||||
|
else:
|
||||||
|
print("FAILED: %d/%d checks" % [failures, checks])
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _check(cond: bool, label: String) -> void:
|
||||||
|
checks += 1
|
||||||
|
if cond:
|
||||||
|
print(" ok: %s" % label)
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
print(" FAIL: %s" % label)
|
||||||
|
|
||||||
|
|
||||||
|
func _find_viewport_container(node: Node) -> SubViewportContainer:
|
||||||
|
if node is SubViewportContainer:
|
||||||
|
return node as SubViewportContainer
|
||||||
|
for child in node.get_children():
|
||||||
|
var found := _find_viewport_container(child)
|
||||||
|
if found != null:
|
||||||
|
return found
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
## Headless-Besonderheit: das Fenster ist nur 64×64 groß — root.push_input
|
||||||
|
## leitet NICHT in den SubViewportContainer weiter (GUI-Delivery braucht das
|
||||||
|
## Event im Fenster-Rect). Deshalb pushen wir direkt in den SubViewport mit
|
||||||
|
## LOKALEN Koordinaten — die Werkzeug-/Kamera-Handler laufen exakt wie echt.
|
||||||
|
var subvp: SubViewport
|
||||||
|
|
||||||
|
|
||||||
|
## Event-Position: Mitte des 3D-Viewports (in SubViewport-Koordinaten).
|
||||||
|
func _vp_center() -> Vector2:
|
||||||
|
return subvp.size * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
func _mouse(button: MouseButton, pressed: bool, pos: Vector2, ctrl := false, shift := false) -> void:
|
||||||
|
var mb := InputEventMouseButton.new()
|
||||||
|
mb.button_index = button
|
||||||
|
mb.pressed = pressed
|
||||||
|
mb.position = pos
|
||||||
|
mb.global_position = pos
|
||||||
|
mb.ctrl_pressed = ctrl
|
||||||
|
mb.shift_pressed = shift
|
||||||
|
subvp.push_input(mb)
|
||||||
|
|
||||||
|
|
||||||
|
func _motion(pos: Vector2) -> void:
|
||||||
|
var mm := InputEventMouseMotion.new()
|
||||||
|
mm.position = pos
|
||||||
|
mm.global_position = pos
|
||||||
|
mm.relative = Vector2.ZERO
|
||||||
|
subvp.push_input(mm)
|
||||||
|
|
||||||
|
|
||||||
|
func _press_key(keycode: Key) -> InputEventKey:
|
||||||
|
var ev := InputEventKey.new()
|
||||||
|
ev.keycode = keycode
|
||||||
|
ev.physical_keycode = keycode
|
||||||
|
ev.pressed = true
|
||||||
|
return ev
|
||||||
|
|
||||||
|
|
||||||
|
func _heights() -> PackedFloat32Array:
|
||||||
|
return SvmapIO.decode_heights((main.doc.get("terrain", {}) as Dictionary))
|
||||||
|
|
||||||
|
|
||||||
|
func _height_at(heights: PackedFloat32Array, x: float, z: float) -> float:
|
||||||
|
var terrain: Dictionary = main.doc.get("terrain", {})
|
||||||
|
return TerrainField.height_at(heights,
|
||||||
|
int(terrain.get("resolution", 0)),
|
||||||
|
float(terrain.get("size_x_m", 0.0)),
|
||||||
|
float(terrain.get("size_z_m", 0.0)), x, z)
|
||||||
|
|
||||||
|
|
||||||
|
## Abnahme 1: Terrain-Werkzeug aktiv + RMB → Flug an, KEIN Terrain-Change,
|
||||||
|
## RMB loslassen → Flug aus. Auch im Flug bewegt die Kamera mit W.
|
||||||
|
func _test_terrain_rmb_flies() -> void:
|
||||||
|
print("Testgruppe: Terrain-Werkzeug + RMB = Flug (kein Strich)")
|
||||||
|
main.call("_set_tool_mode", "terrain")
|
||||||
|
_check(str(main.get("tool_mode")) == "terrain", "Terrain-Werkzeug aktiv")
|
||||||
|
var before := _heights()
|
||||||
|
var pos := _vp_center()
|
||||||
|
_motion(pos)
|
||||||
|
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, true, pos)
|
||||||
|
await process_frame
|
||||||
|
_check(camera.call("fly_active"), "RMB gedrückt → Flug aktiv (im Terrain-Werkzeug)")
|
||||||
|
_check(not bool(terrain_stroking()), "RMB startet KEINEN Terrain-Strich")
|
||||||
|
|
||||||
|
# Im Flug bewegt W die Kamera (echter Bewegungsnachweis).
|
||||||
|
var pos0 := camera.position
|
||||||
|
Input.action_press("editor_fly_forward")
|
||||||
|
await create_timer(0.25).timeout
|
||||||
|
await process_frame
|
||||||
|
Input.action_release("editor_fly_forward")
|
||||||
|
_check(camera.position.distance_to(pos0) > 0.5, "Im Flug bewegt die Kamera (W)")
|
||||||
|
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, false, pos)
|
||||||
|
await process_frame
|
||||||
|
_check(not bool(camera.call("fly_active")), "RMB losgelassen → Flug aus")
|
||||||
|
|
||||||
|
var after := _heights()
|
||||||
|
_check(before == after, "Höhenfeld byte-identisch (kein Terrain-Change)")
|
||||||
|
_check(not str(main.history.undo_desc()).contains("Boden geformt"),
|
||||||
|
"Keine History-Zeile 'Boden geformt' durch RMB")
|
||||||
|
main.call("_set_tool_mode", "objects")
|
||||||
|
|
||||||
|
|
||||||
|
func terrain_stroking() -> bool:
|
||||||
|
return bool(main.get("terrain_tool").get("_stroking"))
|
||||||
|
|
||||||
|
|
||||||
|
## Abnahme 2: Strg+LMB senkt (bei Pinsel-Modus Heben), LMB ohne Strg hebt.
|
||||||
|
func _test_terrain_ctrl_lmb() -> void:
|
||||||
|
print("Testgruppe: Terrain Strg+LMB senkt / LMB hebt")
|
||||||
|
main.call("_set_tool_mode", "terrain")
|
||||||
|
var tool: Node = main.get("terrain_tool")
|
||||||
|
tool.call("set_brush_mode", TerrainField.Mode.RAISE)
|
||||||
|
main.terrain_strength_slider.value = 20.0
|
||||||
|
var pos := _vp_center()
|
||||||
|
_motion(pos)
|
||||||
|
_check(bool(tool.get("_cursor_valid")), "Pinsel-Cursor trifft das Gelände")
|
||||||
|
var cursor: Vector3 = tool.get("_cursor")
|
||||||
|
var heights0 := _heights()
|
||||||
|
var h0 := _height_at(heights0, cursor.x, cursor.z)
|
||||||
|
|
||||||
|
# Strg+LMB-Strich → senken.
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, true, pos, true)
|
||||||
|
await process_frame
|
||||||
|
_check(bool(tool.get("_stroking")), "Strg+LMB startet einen Strich")
|
||||||
|
_check(int(tool.get("_stroke_mode")) == TerrainField.Mode.LOWER,
|
||||||
|
"Strg+LMB bei Modus Heben → Strich-Modus SENKEN")
|
||||||
|
await create_timer(0.3).timeout
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, false, pos, true)
|
||||||
|
await process_frame
|
||||||
|
var heights1 := _heights()
|
||||||
|
var h1 := _height_at(heights1, cursor.x, cursor.z)
|
||||||
|
_check(h1 < h0 - 0.5, "Strg+LMB hat DEN BODEN GESSENKT (%.2f → %.2f m)" % [h0, h1])
|
||||||
|
_check(str(main.history.undo_desc()).contains("Senken"),
|
||||||
|
"History: Strg+LMB-Strich läuft als 'Senken'")
|
||||||
|
|
||||||
|
# LMB ohne Strg → heben (unverändert). Nach dem Senk-Strich trifft der
|
||||||
|
# Cursorstrahl eine NEUE Geländestelle (Grube) — Cursor frisch holen und
|
||||||
|
# dort vor/nach dem Strich messen.
|
||||||
|
_motion(pos)
|
||||||
|
var tool_fresh: Node = main.get("terrain_tool")
|
||||||
|
var cursor2: Vector3 = tool_fresh.get("_cursor")
|
||||||
|
var h1b := _height_at(_heights(), cursor2.x, cursor2.z)
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, true, pos)
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.2).timeout
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, false, pos)
|
||||||
|
await process_frame
|
||||||
|
var h2 := _height_at(_heights(), cursor2.x, cursor2.z)
|
||||||
|
_check(h2 > h1b + 0.3, "LMB ohne Strg HEBT weiter (%.2f → %.2f m)" % [h1b, h2])
|
||||||
|
_check(str(main.history.undo_desc()).contains("Heben"),
|
||||||
|
"History: LMB-Strich läuft als 'Heben'")
|
||||||
|
main.call("_set_tool_mode", "objects")
|
||||||
|
|
||||||
|
|
||||||
|
## Abnahme 3: Objekt in der Hand + RMB → Flug, Objekt bleibt in der Hand,
|
||||||
|
## Esc wirft ab.
|
||||||
|
func _test_placement_rmb_keeps_hand() -> void:
|
||||||
|
print("Testgruppe: Platzieren + RMB = Flug (Objekt bleibt in der Hand)")
|
||||||
|
main.call("_set_tool_mode", "objects")
|
||||||
|
var oid := ""
|
||||||
|
for key in (main.manifest.get("objects", {}) as Dictionary):
|
||||||
|
oid = str(key)
|
||||||
|
break
|
||||||
|
_check(oid != "", "Manifest hat ein Objekt zum Platzieren")
|
||||||
|
if oid == "":
|
||||||
|
return
|
||||||
|
var interaction: Node = main.get("interaction")
|
||||||
|
interaction.call("start_placement", oid)
|
||||||
|
_check(bool(interaction.get("placing")), "Platzier-Modus an (Objekt in der Hand)")
|
||||||
|
|
||||||
|
var pos := _vp_center()
|
||||||
|
_motion(pos)
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, true, pos)
|
||||||
|
await process_frame
|
||||||
|
_check(camera.call("fly_active"), "RMB → Flug aktiv, WIEDERHOLTES Abwurf-RMB ist weg")
|
||||||
|
_check(bool(interaction.get("placing")), "Objekt ist WÄHREND des Flugs noch in der Hand")
|
||||||
|
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, false, pos)
|
||||||
|
await process_frame
|
||||||
|
_check(not bool(camera.call("fly_active")), "RMB losgelassen → Flug aus")
|
||||||
|
_check(bool(interaction.get("placing")), "Objekt bleibt NACH dem Flug in der Hand (kein Abwurf)")
|
||||||
|
|
||||||
|
# Esc wirft ab (zwei Frames, sonst reagiert der Viewport-Frame nicht).
|
||||||
|
main._unhandled_key_input(_press_key(KEY_ESCAPE))
|
||||||
|
await process_frame
|
||||||
|
_check(not bool(interaction.get("placing")), "Esc wirft das Objekt ab (Platzieren aus)")
|
||||||
|
|
||||||
|
|
||||||
|
## Abnahme 4: Bauen-Werkzeug — RMB fliegt, wechselt NICHT das Werkzeug und
|
||||||
|
## behält die Bauteil-Auswahl.
|
||||||
|
func _test_build_rmb_flies() -> void:
|
||||||
|
print("Testgruppe: Bauen-Werkzeug + RMB = Flug (Werkzeug/Auswahl bleiben)")
|
||||||
|
main.call("_set_tool_mode", "bauen")
|
||||||
|
_check(str(main.get("tool_mode")) == "bauen", "Bauen-Werkzeug aktiv")
|
||||||
|
var build: Node = main.get("build_tool")
|
||||||
|
var pos := _vp_center()
|
||||||
|
_motion(pos)
|
||||||
|
|
||||||
|
# Ein Bauteil setzen, dann per Umschalt+Klick auswählen — Klickpunkt ist
|
||||||
|
# die UNPROJIZIERTE Bauteil-Mitte (der gerastete Fußpunkt liegt sonst exakt
|
||||||
|
# auf der Kante des Center-Strahls).
|
||||||
|
_motion(pos)
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, true, pos)
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, false, pos)
|
||||||
|
await process_frame
|
||||||
|
var doc: Dictionary = main.doc
|
||||||
|
var gebaeude: Array = doc.get("gebaeude", [])
|
||||||
|
_check(gebaeude.size() > 0, "Bauteil gesetzt")
|
||||||
|
if gebaeude.is_empty():
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
var ppos: Array = (gebaeude[0] as Dictionary).get("pos", [0.0, 0.0, 0.0])
|
||||||
|
var piece_center := Vector3(float(ppos[0]), float(ppos[1]) + 0.3, float(ppos[2]))
|
||||||
|
# Bauen-Pick-Radius: BuildOps.ray_pick hat max_dist=500 m — beim Default-
|
||||||
|
# Framing (1024-m-Map, Kamera ~614 m weg) würde der Pick leer bleiben.
|
||||||
|
# Deshalb für den Selektions-Klick nah heranzoomen (vorheriges Verhalten,
|
||||||
|
# kein Teil dieser Karte —siehe Task-Kommentar/Follow-up).
|
||||||
|
camera.set("distance", 120.0)
|
||||||
|
camera.call("focus_on", piece_center)
|
||||||
|
var on_piece := camera.unproject_position(piece_center)
|
||||||
|
_motion(on_piece)
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, true, on_piece, false, true)
|
||||||
|
_mouse(MOUSE_BUTTON_LEFT, false, on_piece, false, true)
|
||||||
|
await process_frame
|
||||||
|
var sel := int(build.get("selected_index"))
|
||||||
|
_check(sel >= 0, "Bauteil per Umschalt+Klick ausgewählt")
|
||||||
|
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, true, on_piece)
|
||||||
|
await process_frame
|
||||||
|
_check(camera.call("fly_active"), "RMB → Flug aktiv (im Bauen-Werkzeug)")
|
||||||
|
_check(str(main.get("tool_mode")) == "bauen", "RMB wechselt NICHT mehr das Werkzeug")
|
||||||
|
_check(int(build.get("selected_index")) == sel, "RMB wirft die Bauteil-Auswahl NICHT weg")
|
||||||
|
_mouse(MOUSE_BUTTON_RIGHT, false, pos)
|
||||||
|
await process_frame
|
||||||
|
_check(not bool(camera.call("fly_active")), "RMB losgelassen → Flug aus")
|
||||||
|
|
||||||
|
main.call("_set_tool_mode", "objects")
|
||||||
|
|
||||||
|
|
||||||
|
func _build_piece_count() -> int:
|
||||||
|
var doc: Dictionary = main.doc
|
||||||
|
return int((doc.get("gebaeude", []) as Array).size())
|
||||||
1
tests/test_rmb_vorrang.gd.uid
Normal file
1
tests/test_rmb_vorrang.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dp0xxhx7kf2l5
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
GODOT=~/bin/godot4
|
GODOT=~/bin/godot4
|
||||||
rc=0
|
rc=0
|
||||||
for t in test_ux test_shortcuts test_editor_svmap test_terrain test_library test_glb_view test_spawns test_testrun test_bauen test_gui_smoke; do
|
for t in test_ux test_shortcuts test_editor_svmap test_terrain test_library test_glb_view test_spawns test_testrun test_bauen test_gui_smoke test_rmb_vorrang; do
|
||||||
"$GODOT" --headless --path . --script "res://tests/$t.gd" > "/tmp/$t.log" 2>&1
|
"$GODOT" --headless --path . --script "res://tests/$t.gd" > "/tmp/$t.log" 2>&1
|
||||||
code=$?
|
code=$?
|
||||||
line=$(grep -E "ALL OK|FAILED" "/tmp/$t.log" | tail -1)
|
line=$(grep -E "ALL OK|FAILED" "/tmp/$t.log" | tail -1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue