2026-10-09 12:49:07 +02:00

111 lines
4.3 KiB
GDScript

@tool
extends Node
## UndoRedo apply/revert methods, invoked by name on undo/redo — possibly long
## after the originating command has returned.
##
## These live on a persistent, server-owned Node (reached via server.undo_helpers)
## because EditorUndoRedoManager.add_do_method / add_undo_method take an
## (object, method) pair and route undo by that object's editor context — unlike
## the base UndoRedo class, which takes a Callable. MCPToolkitUndoRedoAction
## unpacks each Callable into (object, method) to feed the editor-manager form, so
## the bound object must be a real, named method host. Because the call is
## deferred, that host must outlive the command — hence this dedicated Node.
##
## NOT a pre-Callable workaround. Do NOT inline these into command files or bind
## them on an ad-hoc / transient instance: a freed host means a broken undo.
func _write_file_silent(path: String, content: String) -> void:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
push_warning("[MCPServer] UndoRedo write of %s failed (err %d)" % [path, FileAccess.get_open_error()])
return
file.store_string(content)
file.close()
func _delete_file_silent(path: String) -> void:
if not FileAccess.file_exists(path):
return
var error := DirAccess.remove_absolute(path)
if error != OK:
push_warning("[MCPServer] UndoRedo delete of %s failed (err %d)" % [path, error])
func _set_owner_recursive(node: Node, owner: Node) -> void:
node.set_owner(owner)
for child in node.get_children():
_set_owner_recursive(child, owner)
func _animation_remove_key_at(animation: Animation, track_index: int, time: float) -> void:
var key_index := animation.track_find_key(track_index, time, Animation.FIND_MODE_EXACT)
if key_index != -1:
animation.track_remove_key(track_index, key_index)
func _animation_insert_key_silent(animation: Animation, track_index: int, time: float, value) -> void:
animation.track_insert_key(track_index, time, value)
func _sm_remove_transition_by_endpoints(sm: AnimationNodeStateMachine, from: String, to: String) -> void:
for i in range(sm.get_transition_count()):
if str(sm.get_transition_from(i)) == from and str(sm.get_transition_to(i)) == to:
sm.remove_transition_by_index(i)
return
func _tilemap_apply_batch(node: Node, layer: int, cells: Array) -> void:
var is_layer := node.is_class("TileMapLayer") # dynamic — avoids parse error on < 4.3
for cell in cells:
var coord := Vector2i(int(cell["x"]), int(cell["y"]))
var source_id := int(cell["source_id"])
var atlas := Vector2i(int(cell["atlas_x"]), int(cell["atlas_y"]))
var alternative := int(cell.get("alternative_tile", 0))
if is_layer:
node.set_cell(coord, source_id, atlas, alternative)
else:
(node as TileMap).set_cell(layer, coord, source_id, atlas, alternative)
## Set a compound-path property (colon-chain or slash) on a node.
## Used by UndoRedo for compound path undo/redo — navigates sub-resources
## the same way set_property_compound does, but without coercion or readback.
func compound_set(node: Object, property_name: String, value: Variant) -> void:
if ":" not in property_name:
node.set(property_name, value)
return
var parts := property_name.split(":")
# Single-colon: try slash-path first.
if parts.size() == 2:
var slash_path := parts[0] + "/" + parts[1]
node.set(slash_path, value)
if node.get(slash_path) != null:
return
# Navigate to sub-resource and set directly.
var target: Object = node
for i in range(parts.size() - 1):
var sub = target.get(parts[i])
if sub == null or not (sub is Object):
return
target = sub
var final_prop := parts[-1]
if final_prop.begins_with("shader_parameter/") and target is ShaderMaterial:
(target as ShaderMaterial).set_shader_parameter(
final_prop.trim_prefix("shader_parameter/"), value)
else:
target.set(final_prop, value)
func _tilemap_restore_batch(node: Node, layer: int, before_state: Array) -> void:
var is_layer := node.is_class("TileMapLayer") # dynamic — avoids parse error on < 4.3
for state in before_state:
var coord: Vector2i = state["coord"]
var source_id := int(state["source_id"])
var atlas: Vector2i = state["atlas"]
var alternative := int(state["alternative_tile"])
if is_layer:
node.set_cell(coord, source_id, atlas, alternative)
else:
(node as TileMap).set_cell(layer, coord, source_id, atlas, alternative)