refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack

将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-29 01:11:20 +08:00
parent e8693dad2a
commit dd3eb24d0f
488 changed files with 33927 additions and 0 deletions

View File

@@ -0,0 +1,601 @@
@tool
extends "res://addons/godot_mcp/commands/base_command.gd"
func get_commands() -> Dictionary:
return {
"create_animation_tree": _create_animation_tree,
"get_animation_tree_structure": _get_animation_tree_structure,
"add_state_machine_state": _add_state_machine_state,
"remove_state_machine_state": _remove_state_machine_state,
"add_state_machine_transition": _add_state_machine_transition,
"remove_state_machine_transition": _remove_state_machine_transition,
"set_blend_tree_node": _set_blend_tree_node,
"set_tree_parameter": _set_tree_parameter,
}
## Find AnimationTree on a node or return null
func _find_animation_tree(node_path: String) -> AnimationTree:
var node := find_node_by_path(node_path)
if node is AnimationTree:
return node as AnimationTree
return null
## Navigate to a nested state machine by slash-separated path (e.g. "Run/SubState")
## Returns [state_machine, error_or_null]
func _resolve_state_machine(tree: AnimationTree, sm_path: String) -> Array:
var root := tree.tree_root
if not root is AnimationNodeStateMachine:
return [null, error_invalid_params("AnimationTree root is not an AnimationNodeStateMachine")]
if sm_path.is_empty() or sm_path == ".":
return [root as AnimationNodeStateMachine, null]
var current: AnimationNodeStateMachine = root as AnimationNodeStateMachine
var parts := sm_path.split("/")
for part in parts:
if not current.has_node(StringName(part)):
return [null, error_not_found("State machine node '%s' in path '%s'" % [part, sm_path])]
var child := current.get_node(StringName(part))
if not child is AnimationNodeStateMachine:
return [null, error_invalid_params("Node '%s' is not a StateMachine" % part)]
current = child as AnimationNodeStateMachine
return [current, null]
## Resolve a BlendTree inside the tree. bt_path can be a state name inside a state machine,
## or a slash-separated path. The last segment is the BlendTree node name.
## Returns [blend_tree, error_or_null]
func _resolve_blend_tree(tree: AnimationTree, sm_path: String, bt_name: String) -> Array:
var result := _resolve_state_machine(tree, sm_path)
if result[1] != null:
return result
var sm: AnimationNodeStateMachine = result[0]
if not sm.has_node(StringName(bt_name)):
return [null, error_not_found("BlendTree node '%s'" % bt_name)]
var node := sm.get_node(StringName(bt_name))
if not node is AnimationNodeBlendTree:
return [null, error_invalid_params("Node '%s' is not an AnimationNodeBlendTree" % bt_name)]
return [node as AnimationNodeBlendTree, null]
func _create_animation_tree(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var root := get_edited_root()
if root == null:
return error_no_scene()
var parent := find_node_by_path(node_path)
if parent == null:
return error_not_found("Node at '%s'" % node_path)
var anim_player_path: String = optional_string(params, "anim_player", "")
var tree_name: String = optional_string(params, "name", "AnimationTree")
# Create the AnimationTree
var tree := AnimationTree.new()
tree.name = tree_name
# Set root to AnimationNodeStateMachine
var state_machine := AnimationNodeStateMachine.new()
tree.tree_root = state_machine
# Link to AnimationPlayer if provided
if not anim_player_path.is_empty():
tree.anim_player = NodePath(anim_player_path)
add_child_with_undo(parent, tree, root, "MCP: Create AnimationTree")
return success({
"name": tree.name,
"node_path": str(root.get_path_to(tree)),
"root_type": "AnimationNodeStateMachine",
"anim_player": anim_player_path,
"created": true,
})
func _get_animation_tree_structure(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var root := tree.tree_root
if root == null:
return success({"node_path": node_path, "root": null})
var structure := _read_node_structure(root)
structure["active"] = tree.active
structure["anim_player"] = str(tree.anim_player)
structure["node_path"] = node_path
return success(structure)
func _read_node_structure(node: AnimationNode) -> Dictionary:
if node is AnimationNodeStateMachine:
return _read_state_machine_structure(node as AnimationNodeStateMachine)
elif node is AnimationNodeBlendTree:
return _read_blend_tree_structure(node as AnimationNodeBlendTree)
elif node is AnimationNodeAnimation:
var anim_node := node as AnimationNodeAnimation
return {"type": "AnimationNodeAnimation", "animation": str(anim_node.animation)}
else:
return {"type": node.get_class()}
func _read_state_machine_structure(sm: AnimationNodeStateMachine) -> Dictionary:
var states: Array = []
# Iterate through graph nodes via get_node_name
# AnimationNodeStateMachine doesn't have get_node_list in 4.x, iterate using _get_child_nodes
var node_list := _get_sm_node_names(sm)
for state_name in node_list:
var child := sm.get_node(StringName(state_name))
var state_info := {
"name": state_name,
"position": {"x": sm.get_node_position(StringName(state_name)).x, "y": sm.get_node_position(StringName(state_name)).y},
}
state_info.merge(_read_node_structure(child))
states.append(state_info)
var transitions: Array = []
for i in sm.get_transition_count():
var from_node := sm.get_transition_from(i)
var to_node := sm.get_transition_to(i)
var trans := sm.get_transition(i)
var trans_info := {
"from": str(from_node),
"to": str(to_node),
"switch_mode": trans.switch_mode,
"advance_mode": trans.advance_mode,
}
if not trans.advance_expression.is_empty():
trans_info["advance_expression"] = trans.advance_expression
if trans.advance_mode == AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO:
trans_info["auto"] = true
transitions.append(trans_info)
return {
"type": "AnimationNodeStateMachine",
"states": states,
"transitions": transitions,
}
func _get_sm_node_names(sm: AnimationNodeStateMachine) -> Array:
# Use the internal _get_child_nodes or iterate known patterns
# AnimationNodeStateMachine doesn't expose a simple list method,
# but we can use get_graph_offset and iterate via has_node with common checks.
# Actually in Godot 4.x we can get the node list by checking property list
# or using the script resource approach. The most reliable is iterating through
# the resource properties.
var names: Array = []
var prop_list := sm.get_property_list()
for prop in prop_list:
var pname: String = prop["name"]
# State machine stores nodes as "states/<name>/node"
if pname.begins_with("states/") and pname.ends_with("/node"):
var state_name := pname.get_slice("/", 1)
if state_name != "Start" and state_name != "End":
names.append(state_name)
return names
func _read_blend_tree_structure(bt: AnimationNodeBlendTree) -> Dictionary:
var nodes_info: Array = []
var prop_list := bt.get_property_list()
var node_names: Array = []
for prop in prop_list:
var pname: String = prop["name"]
if pname.begins_with("nodes/") and pname.ends_with("/node"):
var n := pname.get_slice("/", 1)
if n != "output":
node_names.append(n)
for n_name in node_names:
var child: AnimationNode = bt.get_node(StringName(n_name))
var node_info := {
"name": n_name,
"type": child.get_class(),
"position": {"x": bt.get_node_position(StringName(n_name)).x, "y": bt.get_node_position(StringName(n_name)).y},
}
if child is AnimationNodeAnimation:
node_info["animation"] = str((child as AnimationNodeAnimation).animation)
nodes_info.append(node_info)
# Read connections
# BlendTree connections are stored as "node_connections" in properties
# We can read them from the resource property list
for prop in prop_list:
var pname: String = prop["name"]
if pname.begins_with("nodes/") and pname.ends_with("/node"):
continue
if pname.begins_with("nodes/") and pname.ends_with("/position"):
continue
# Connection format: "node_connection/<idx>/<input_node>/<input_port>"
# Actually connections are stored differently - let's skip for now
return {
"type": "AnimationNodeBlendTree",
"nodes": nodes_info,
}
func _add_state_machine_state(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "state_name")
if result2[1] != null:
return result2[1]
var state_name: String = result2[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var sm_path: String = optional_string(params, "state_machine_path", "")
var sm_result := _resolve_state_machine(tree, sm_path)
if sm_result[1] != null:
return sm_result[1]
var sm: AnimationNodeStateMachine = sm_result[0]
if sm.has_node(StringName(state_name)):
return error_invalid_params("State '%s' already exists" % state_name)
var state_type: String = optional_string(params, "state_type", "animation")
var position_x: float = float(params.get("position_x", 0.0))
var position_y: float = float(params.get("position_y", 0.0))
var position := Vector2(position_x, position_y)
var node: AnimationNode
match state_type:
"animation":
var anim_node := AnimationNodeAnimation.new()
var anim_name: String = optional_string(params, "animation", "")
if not anim_name.is_empty():
anim_node.animation = StringName(anim_name)
node = anim_node
"blend_tree":
node = AnimationNodeBlendTree.new()
"state_machine":
node = AnimationNodeStateMachine.new()
_:
return error_invalid_params("Unknown state_type: '%s'. Use 'animation', 'blend_tree', or 'state_machine'" % state_type)
var undo_redo := get_undo_redo()
undo_redo.create_action("MCP: Add state machine state")
undo_redo.add_do_method(sm, "add_node", StringName(state_name), node, position)
undo_redo.add_do_reference(node)
undo_redo.add_undo_method(sm, "remove_node", StringName(state_name))
undo_redo.commit_action()
return success({
"state_name": state_name,
"state_type": state_type,
"position": {"x": position_x, "y": position_y},
"added": true,
})
func _remove_state_machine_state(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "state_name")
if result2[1] != null:
return result2[1]
var state_name: String = result2[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var sm_path: String = optional_string(params, "state_machine_path", "")
var sm_result := _resolve_state_machine(tree, sm_path)
if sm_result[1] != null:
return sm_result[1]
var sm: AnimationNodeStateMachine = sm_result[0]
if not sm.has_node(StringName(state_name)):
return error_not_found("State '%s'" % state_name)
var old_node := sm.get_node(StringName(state_name))
var old_position := sm.get_node_position(StringName(state_name))
var undo_redo := get_undo_redo()
undo_redo.create_action("MCP: Remove state machine state")
undo_redo.add_do_method(sm, "remove_node", StringName(state_name))
undo_redo.add_undo_method(sm, "add_node", StringName(state_name), old_node, old_position)
undo_redo.add_undo_reference(old_node)
undo_redo.commit_action()
return success({"state_name": state_name, "removed": true})
func _add_state_machine_transition(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "from_state")
if result2[1] != null:
return result2[1]
var from_state: String = result2[0]
var result3 := require_string(params, "to_state")
if result3[1] != null:
return result3[1]
var to_state: String = result3[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var sm_path: String = optional_string(params, "state_machine_path", "")
var sm_result := _resolve_state_machine(tree, sm_path)
if sm_result[1] != null:
return sm_result[1]
var sm: AnimationNodeStateMachine = sm_result[0]
# Validate states exist (Start and End are special built-in nodes)
if from_state != "Start" and from_state != "End" and not sm.has_node(StringName(from_state)):
return error_not_found("State '%s'" % from_state)
if to_state != "Start" and to_state != "End" and not sm.has_node(StringName(to_state)):
return error_not_found("State '%s'" % to_state)
var transition := AnimationNodeStateMachineTransition.new()
# switch_mode: AT_END=0, IMMEDIATE=1, SYNC=2
var switch_mode_str: String = optional_string(params, "switch_mode", "immediate")
match switch_mode_str:
"at_end": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
"immediate": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
"sync": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END # SYNC maps similarly
_: transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
# advance_mode: DISABLED=0, ENABLED=1, AUTO=2
var advance_mode_str: String = optional_string(params, "advance_mode", "enabled")
match advance_mode_str:
"disabled": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_DISABLED
"enabled": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED
"auto": transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
_: transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED
# advance_expression
var expression: String = optional_string(params, "advance_expression", "")
if not expression.is_empty():
transition.advance_expression = expression
# xfade_time
if params.has("xfade_time"):
transition.xfade_time = float(params["xfade_time"])
var undo_redo := get_undo_redo()
undo_redo.create_action("MCP: Add state machine transition")
undo_redo.add_do_method(sm, "add_transition", StringName(from_state), StringName(to_state), transition)
undo_redo.add_do_reference(transition)
undo_redo.add_undo_method(sm, "remove_transition", StringName(from_state), StringName(to_state))
undo_redo.commit_action()
return success({
"from": from_state,
"to": to_state,
"switch_mode": switch_mode_str,
"advance_mode": advance_mode_str,
"advance_expression": expression,
"added": true,
})
func _remove_state_machine_transition(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "from_state")
if result2[1] != null:
return result2[1]
var from_state: String = result2[0]
var result3 := require_string(params, "to_state")
if result3[1] != null:
return result3[1]
var to_state: String = result3[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var sm_path: String = optional_string(params, "state_machine_path", "")
var sm_result := _resolve_state_machine(tree, sm_path)
if sm_result[1] != null:
return sm_result[1]
var sm: AnimationNodeStateMachine = sm_result[0]
# Check if transition exists
var found := false
for i in sm.get_transition_count():
if str(sm.get_transition_from(i)) == from_state and str(sm.get_transition_to(i)) == to_state:
found = true
break
if not found:
return error_not_found("Transition from '%s' to '%s'" % [from_state, to_state])
var transition: AnimationNodeStateMachineTransition = null
for i in sm.get_transition_count():
if str(sm.get_transition_from(i)) == from_state and str(sm.get_transition_to(i)) == to_state:
transition = sm.get_transition(i)
break
var undo_redo := get_undo_redo()
undo_redo.create_action("MCP: Remove state machine transition")
undo_redo.add_do_method(sm, "remove_transition", StringName(from_state), StringName(to_state))
undo_redo.add_undo_method(sm, "add_transition", StringName(from_state), StringName(to_state), transition)
undo_redo.add_undo_reference(transition)
undo_redo.commit_action()
return success({"from": from_state, "to": to_state, "removed": true})
func _set_blend_tree_node(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "blend_tree_state")
if result2[1] != null:
return result2[1]
var bt_state: String = result2[0]
var result3 := require_string(params, "bt_node_name")
if result3[1] != null:
return result3[1]
var bt_node_name: String = result3[0]
var result4 := require_string(params, "bt_node_type")
if result4[1] != null:
return result4[1]
var bt_node_type: String = result4[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
var sm_path: String = optional_string(params, "state_machine_path", "")
var bt_result := _resolve_blend_tree(tree, sm_path, bt_state)
if bt_result[1] != null:
return bt_result[1]
var bt: AnimationNodeBlendTree = bt_result[0]
var position_x: float = float(params.get("position_x", 0.0))
var position_y: float = float(params.get("position_y", 0.0))
var position := Vector2(position_x, position_y)
var had_old_node := bt.has_node(StringName(bt_node_name))
var old_node: AnimationNode = bt.get_node(StringName(bt_node_name)) if had_old_node else null
var old_position := bt.get_node_position(StringName(bt_node_name)) if had_old_node else Vector2.ZERO
var node: AnimationNode
match bt_node_type:
"Animation":
var anim_node := AnimationNodeAnimation.new()
var anim_name: String = optional_string(params, "animation", "")
if not anim_name.is_empty():
anim_node.animation = StringName(anim_name)
node = anim_node
"Add2":
node = AnimationNodeAdd2.new()
"Blend2":
node = AnimationNodeBlend2.new()
"Add3":
node = AnimationNodeAdd3.new()
"Blend3":
node = AnimationNodeBlend3.new()
"TimeScale":
node = AnimationNodeTimeScale.new()
"TimeSeek":
node = AnimationNodeTimeSeek.new()
"Transition":
node = AnimationNodeTransition.new()
"OneShot":
node = AnimationNodeOneShot.new()
"Sub2":
node = AnimationNodeSub2.new()
_:
return error_invalid_params("Unknown bt_node_type: '%s'. Use: Animation, Add2, Blend2, Add3, Blend3, TimeScale, TimeSeek, Transition, OneShot, Sub2" % bt_node_type)
var undo_redo := get_undo_redo()
undo_redo.create_action("MCP: Set blend tree node")
if had_old_node:
undo_redo.add_do_method(bt, "remove_node", StringName(bt_node_name))
undo_redo.add_undo_method(bt, "add_node", StringName(bt_node_name), old_node, old_position)
undo_redo.add_undo_reference(old_node)
undo_redo.add_do_method(bt, "add_node", StringName(bt_node_name), node, position)
undo_redo.add_do_reference(node)
undo_redo.add_undo_method(bt, "remove_node", StringName(bt_node_name))
# Connect to another node if specified
var connect_to: String = optional_string(params, "connect_to", "")
var connect_port: int = optional_int(params, "connect_port", 0)
if not connect_to.is_empty():
undo_redo.add_do_method(bt, "connect_node", StringName(connect_to), connect_port, StringName(bt_node_name))
undo_redo.commit_action()
var connected_to_value: Variant = null
if not connect_to.is_empty():
connected_to_value = connect_to
return success({
"blend_tree_state": bt_state,
"bt_node_name": bt_node_name,
"bt_node_type": bt_node_type,
"position": {"x": position_x, "y": position_y},
"connected_to": connected_to_value,
"added": true,
})
func _set_tree_parameter(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var result2 := require_string(params, "parameter")
if result2[1] != null:
return result2[1]
var parameter: String = result2[0]
var tree := _find_animation_tree(node_path)
if tree == null:
return error_not_found("AnimationTree at '%s'" % node_path)
if not params.has("value"):
return error_invalid_params("Missing required parameter: value")
var value = params["value"]
# Prefix with "parameters/" if not already
if not parameter.begins_with("parameters/"):
parameter = "parameters/" + parameter
# Parse string values for common types
if value is String:
var s: String = value
var expr := Expression.new()
if expr.parse(s) == OK:
var parsed = expr.execute()
if parsed != null:
value = parsed
set_property_with_undo(tree, parameter, value, "MCP: Set AnimationTree parameter")
# Read back to confirm
var actual = tree.get(parameter)
return success({
"parameter": parameter,
"value": str(actual),
"set": true,
})