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