scopedvr-map-core/editor/action_history.gd
slyphax 17a8927fec Map-Kern aus dem Editor übernommen (Stand e8ab954)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:30:18 +01:00

132 lines
No EOL
4.2 KiB
GDScript

extends RefCounted
class_name ActionHistory
## Zentrale Undo/Redo-Historie des Map-Editors (M2, Karte t_40d855d0).
##
## Die Map IST das svmap-Dictionary (1:1-Format, keine Kopie). Jede Aktion
## sichert die betroffenen OBER-Blöcke (z. B. "objects", "terrain",
## "spawn_zones") als tiefe Kopie VOR und NACH der Änderung. Undo setze die
## Blöcke zurück, Redo wendet "after" erneut an.
##
## So melden spätere Werkzeuge ihre Aktionen an (Terrain-Strokes, Spawn-Zonen):
## var before := history.snapshot(["terrain"]) # VOR der Änderung
## doc["terrain"][...] = ... # Änderung machen
## history.commit("Hügel geformt", before, history.snapshot(["terrain"]))
##
## Ober-Blöcke sind klein (Objekt-Liste/Spawn-Punkte) oder Base64-Strings
## (heightmap) — tiefe Kopien sind hier billig und bombenfest gegen
## Alias-Verwechslung, weil Godot-Arrays/Dicts sonst gegenseitig referenzieren.
const MAX_STEPS := 100
var doc: Dictionary = {}
var entries: Array = [] # Eintrag = {desc:String, merge:String, before:{Blk:Variante}, after:{Blk:Variante}}
var index := -1 # zuletzt angewandter Eintrag; -1 = nichts
func begin(document: Dictionary) -> void:
doc = document # Referenz — wir ändern dieselbe Map, keine Kopie
clear()
func clear() -> void:
entries.clear()
index = -1
## Tiefe Kopie der angegebenen Blöcke (VOR oder NACH einer Änderung).
## Skalare Ober-Keys (format_version, weather, …) werden als Wert kopiert —
## so kann ein Snapshot auch den GANZEN doc abdecken (Resize, R3).
func snapshot(keys: Array) -> Dictionary:
var out := {}
for key in keys:
if doc.has(key):
var value: Variant = doc[key]
if value is Array or value is Dictionary:
out[key] = value.duplicate(true)
else:
out[key] = value
return out
func can_undo() -> bool:
return index >= 0
func can_redo() -> bool:
return index + 1 < entries.size()
func undo_desc() -> String:
if not can_undo():
return ""
return str(entries[index].get("desc", ""))
func redo_desc() -> String:
if not can_redo():
return ""
return str(entries[index + 1].get("desc", ""))
## Eine fertige Änderung in die History einhängen.
## merge_tag: ungleich "" → identisch getagte Folge-Aufrufe (z. B. Drehen
## per Mausrad in schnellen Ticks) überschreiben den letzten Eintrag,
## statt die History zu fluten. commit immer direkt NACH der Doc-Änderung aufrufen.
func commit(desc: String, before: Dictionary, after: Dictionary, merge_tag: String = "") -> void:
if merge_tag != "" and index >= 0 and str(entries[index].get("merge", "")) == merge_tag:
entries[index]["after"] = after
return
entries.resize(index + 1) # redo-Zweig verwerfen (Standard-Undo-Semantik)
entries.append({"desc": desc, "merge": merge_tag, "before": before, "after": after})
if entries.size() > MAX_STEPS:
entries.remove_at(0) # älteste Aktion verdrängen
index = entries.size() - 1
## Letzte Aktion zurücknehmen. Rückgabe: Beschreibung ("" = nichts da).
## Restore läuft IN-PLACE: doc["objects"] etc. behalten ihre Identität (Array/
## Dict-Objekt wird geleert und neu befüllt) — bezogene Referenzen (Tests,
## Ansichten) werden nicht zu Stale-Schnappschüssen.
func undo() -> String:
if not can_undo():
return ""
var entry: Dictionary = entries[index]
for key in entry["before"]:
_restore_block(key, entry["before"][key])
index -= 1
return str(entry.get("desc", ""))
## Zurückgenommenes wiederherstellen. Rückgabe: Beschreibung ("" = nichts da).
func redo() -> String:
if not can_redo():
return ""
index += 1
var entry: Dictionary = entries[index]
for key in entry["after"]:
_restore_block(key, entry["after"][key])
return str(entry.get("desc", ""))
func _restore_block(key: String, value: Variant) -> void:
if value == null:
doc.erase(key)
return
# Ganzen Block (Array/Dict) in-place neu befüllen — Identität bleibt erhalten.
# Skalare (format_version, weather, …) werden als Wert gesetzt.
var source: Variant = value
if source is Array or source is Dictionary:
source = value.duplicate(true)
if source is Array:
var arr: Array = doc.get(key, [])
arr.clear()
arr.append_array(source)
doc[key] = arr
elif source is Dictionary:
var dict: Dictionary = doc.get(key, {})
dict.clear()
dict.merge(source)
doc[key] = dict
else:
doc[key] = source