Files
server-deploy/claude-dev-stack/godot-mcp-pro-v1.14.1/addons/godot_mcp/commands/navigation_commands.gd

473 lines
16 KiB
GDScript

@tool
extends "res://addons/godot_mcp/commands/base_command.gd"
func get_commands() -> Dictionary:
return {
"setup_navigation_region": _setup_navigation_region,
"bake_navigation_mesh": _bake_navigation_mesh,
"setup_navigation_agent": _setup_navigation_agent,
"set_navigation_layers": _set_navigation_layers,
"get_navigation_info": _get_navigation_info,
}
func _is_3d_context(node: Node) -> bool:
if node is Node3D:
return true
if node is Node2D:
return false
# Walk up to detect context
var parent := node.get_parent()
while parent != null:
if parent is Node3D:
return true
if parent is Node2D:
return false
parent = parent.get_parent()
return false
func _setup_navigation_region(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var node := find_node_by_path(node_path)
if node == null:
return error_not_found("Node at '%s'" % node_path)
var root := get_edited_root()
if root == null:
return error_no_scene()
var force_mode: String = optional_string(params, "mode", "auto")
var is_3d: bool
match force_mode:
"2d": is_3d = false
"3d": is_3d = true
_: is_3d = _is_3d_context(node)
if is_3d:
var region := NavigationRegion3D.new()
region.name = optional_string(params, "name", "NavigationRegion3D")
var nav_mesh := NavigationMesh.new()
nav_mesh.agent_radius = float(params.get("agent_radius", 0.5))
nav_mesh.agent_height = float(params.get("agent_height", 1.5))
nav_mesh.agent_max_climb = float(params.get("agent_max_climb", 0.25))
nav_mesh.agent_max_slope = float(params.get("agent_max_slope", 45.0))
nav_mesh.cell_size = float(params.get("cell_size", 0.25))
nav_mesh.cell_height = float(params.get("cell_height", 0.25))
region.navigation_mesh = nav_mesh
if params.has("navigation_layers"):
region.navigation_layers = int(params["navigation_layers"])
add_child_with_undo(node, region, root, "MCP: Add NavigationRegion3D")
return success({
"node_path": str(region.get_path()),
"type": "NavigationRegion3D",
"agent_radius": nav_mesh.agent_radius,
"agent_height": nav_mesh.agent_height,
"cell_size": nav_mesh.cell_size,
"created": true,
})
else:
var region := NavigationRegion2D.new()
region.name = optional_string(params, "name", "NavigationRegion2D")
var nav_poly := NavigationPolygon.new()
# Set parsed geometry source if available
if params.has("source_geometry_mode"):
var mode_str: String = str(params["source_geometry_mode"])
match mode_str:
"root_node": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_ROOT_NODE_CHILDREN
"groups_with_children": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN
"groups_explicit": nav_poly.source_geometry_mode = NavigationPolygon.SOURCE_GEOMETRY_GROUPS_EXPLICIT
if params.has("cell_size"):
nav_poly.cell_size = float(params["cell_size"])
if params.has("agent_radius"):
nav_poly.agent_radius = float(params["agent_radius"])
region.navigation_polygon = nav_poly
if params.has("navigation_layers"):
region.navigation_layers = int(params["navigation_layers"])
add_child_with_undo(node, region, root, "MCP: Add NavigationRegion2D")
return success({
"node_path": str(region.get_path()),
"type": "NavigationRegion2D",
"cell_size": nav_poly.cell_size,
"created": true,
})
func _bake_navigation_mesh(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var node := find_node_by_path(node_path)
if node == null:
return error_not_found("Node at '%s'" % node_path)
var root := get_edited_root()
if root == null:
return error_no_scene()
if node is NavigationRegion3D:
var region: NavigationRegion3D = node as NavigationRegion3D
if region.navigation_mesh == null:
return error_invalid_params("NavigationRegion3D has no NavigationMesh resource")
region.bake_navigation_mesh()
mark_current_scene_unsaved()
return success({
"node_path": node_path,
"type": "NavigationRegion3D",
"baked": true,
})
elif node is NavigationRegion2D:
var region: NavigationRegion2D = node as NavigationRegion2D
if region.navigation_polygon == null:
var nav_poly := NavigationPolygon.new()
region.navigation_polygon = nav_poly
# Set outline vertices from params
if params.has("outline"):
var outline_data: Array = params["outline"]
var outline := PackedVector2Array()
for point in outline_data:
if point is Array and point.size() >= 2:
outline.append(Vector2(float(point[0]), float(point[1])))
elif point is Dictionary:
outline.append(Vector2(float(point.get("x", 0)), float(point.get("y", 0))))
if outline.size() >= 3:
# Clear existing outlines
while region.navigation_polygon.get_outline_count() > 0:
region.navigation_polygon.remove_outline(0)
region.navigation_polygon.add_outline(outline)
region.navigation_polygon.make_polygons_from_outlines()
mark_current_scene_unsaved()
return success({
"node_path": node_path,
"type": "NavigationRegion2D",
"outline_vertices": outline.size(),
"baked": true,
})
else:
return error_invalid_params("Outline must have at least 3 vertices")
else:
# Try baking from source geometry
region.bake_navigation_polygon()
mark_current_scene_unsaved()
return success({
"node_path": node_path,
"type": "NavigationRegion2D",
"baked": true,
})
return error_invalid_params("Node '%s' is not a NavigationRegion2D or NavigationRegion3D" % node_path)
func _setup_navigation_agent(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var node := find_node_by_path(node_path)
if node == null:
return error_not_found("Node at '%s'" % node_path)
var root := get_edited_root()
if root == null:
return error_no_scene()
var force_mode: String = optional_string(params, "mode", "auto")
var is_3d: bool
match force_mode:
"2d": is_3d = false
"3d": is_3d = true
_: is_3d = _is_3d_context(node)
var agent_name: String = optional_string(params, "name", "NavigationAgent3D" if is_3d else "NavigationAgent2D")
if is_3d:
var agent := NavigationAgent3D.new()
agent.name = agent_name
if params.has("path_desired_distance"):
agent.path_desired_distance = float(params["path_desired_distance"])
if params.has("target_desired_distance"):
agent.target_desired_distance = float(params["target_desired_distance"])
if params.has("radius"):
agent.radius = float(params["radius"])
if params.has("neighbor_distance"):
agent.neighbor_distance = float(params["neighbor_distance"])
if params.has("max_neighbors"):
agent.max_neighbors = int(params["max_neighbors"])
if params.has("max_speed"):
agent.max_speed = float(params["max_speed"])
if params.has("avoidance_enabled"):
agent.avoidance_enabled = bool(params["avoidance_enabled"])
if params.has("navigation_layers"):
agent.navigation_layers = int(params["navigation_layers"])
add_child_with_undo(node, agent, root, "MCP: Add NavigationAgent3D")
return success({
"node_path": str(agent.get_path()),
"type": "NavigationAgent3D",
"radius": agent.radius,
"max_speed": agent.max_speed,
"avoidance_enabled": agent.avoidance_enabled,
"navigation_layers": agent.navigation_layers,
"created": true,
})
else:
var agent := NavigationAgent2D.new()
agent.name = agent_name
if params.has("path_desired_distance"):
agent.path_desired_distance = float(params["path_desired_distance"])
if params.has("target_desired_distance"):
agent.target_desired_distance = float(params["target_desired_distance"])
if params.has("radius"):
agent.radius = float(params["radius"])
if params.has("neighbor_distance"):
agent.neighbor_distance = float(params["neighbor_distance"])
if params.has("max_neighbors"):
agent.max_neighbors = int(params["max_neighbors"])
if params.has("max_speed"):
agent.max_speed = float(params["max_speed"])
if params.has("avoidance_enabled"):
agent.avoidance_enabled = bool(params["avoidance_enabled"])
if params.has("navigation_layers"):
agent.navigation_layers = int(params["navigation_layers"])
add_child_with_undo(node, agent, root, "MCP: Add NavigationAgent2D")
return success({
"node_path": str(agent.get_path()),
"type": "NavigationAgent2D",
"radius": agent.radius,
"max_speed": agent.max_speed,
"avoidance_enabled": agent.avoidance_enabled,
"navigation_layers": agent.navigation_layers,
"created": true,
})
func _set_navigation_layers(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var node := find_node_by_path(node_path)
if node == null:
return error_not_found("Node at '%s'" % node_path)
# Support setting by bitmask value
if params.has("layers"):
var layers_val: int = int(params["layers"])
if node is NavigationRegion2D:
set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
elif node is NavigationRegion3D:
set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
elif node is NavigationAgent2D:
set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
elif node is NavigationAgent3D:
set_property_with_undo(node, "navigation_layers", layers_val, "MCP: Set navigation layers")
else:
return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)
return success({
"node_path": node_path,
"navigation_layers": layers_val,
"updated": true,
})
# Support setting individual layer bits by number
if params.has("layer_bits"):
var bits: Array = params["layer_bits"]
var current_layers: int = 0
# Calculate bitmask from layer numbers (1-based)
for bit in bits:
var layer_num: int = int(bit)
if layer_num >= 1 and layer_num <= 32:
current_layers |= (1 << (layer_num - 1))
if node is NavigationRegion2D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationRegion3D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationAgent2D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationAgent3D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
else:
return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)
return success({
"node_path": node_path,
"navigation_layers": current_layers,
"layer_bits": bits,
"updated": true,
})
# Support named layers from ProjectSettings
if params.has("layer_names"):
var names: Array = params["layer_names"]
var current_layers: int = 0
var is_2d: bool = node is NavigationRegion2D or node is NavigationAgent2D
var prefix: String = "layer_names/2d_navigation/layer_" if is_2d else "layer_names/3d_navigation/layer_"
for i in range(1, 33):
var setting_key: String = prefix + str(i)
if ProjectSettings.has_setting(setting_key):
var layer_name: String = str(ProjectSettings.get_setting(setting_key))
if layer_name in names:
current_layers |= (1 << (i - 1))
if node is NavigationRegion2D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationRegion3D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationAgent2D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
elif node is NavigationAgent3D:
set_property_with_undo(node, "navigation_layers", current_layers, "MCP: Set navigation layers")
else:
return error_invalid_params("Node '%s' is not a navigation region or agent" % node_path)
return success({
"node_path": node_path,
"navigation_layers": current_layers,
"layer_names": names,
"updated": true,
})
return error_invalid_params("Must provide 'layers' (bitmask), 'layer_bits' (array of layer numbers), or 'layer_names' (array of named layers)")
func _get_navigation_info(params: Dictionary) -> Dictionary:
var result := require_string(params, "node_path")
if result[1] != null:
return result[1]
var node_path: String = result[0]
var node := find_node_by_path(node_path)
if node == null:
return error_not_found("Node at '%s'" % node_path)
var regions: Array = []
var agents: Array = []
_collect_navigation_nodes(node, regions, agents)
# Collect named layers from ProjectSettings
var layer_names_2d: Dictionary = {}
var layer_names_3d: Dictionary = {}
for i in range(1, 33):
var key_2d: String = "layer_names/2d_navigation/layer_" + str(i)
var key_3d: String = "layer_names/3d_navigation/layer_" + str(i)
if ProjectSettings.has_setting(key_2d):
var name_2d: String = str(ProjectSettings.get_setting(key_2d))
if not name_2d.is_empty():
layer_names_2d[i] = name_2d
if ProjectSettings.has_setting(key_3d):
var name_3d: String = str(ProjectSettings.get_setting(key_3d))
if not name_3d.is_empty():
layer_names_3d[i] = name_3d
return success({
"node_path": node_path,
"regions": regions,
"agents": agents,
"region_count": regions.size(),
"agent_count": agents.size(),
"layer_names_2d": layer_names_2d,
"layer_names_3d": layer_names_3d,
})
func _collect_navigation_nodes(node: Node, regions: Array, agents: Array) -> void:
if node is NavigationRegion2D:
var region: NavigationRegion2D = node as NavigationRegion2D
var region_info := {
"path": str(region.get_path()),
"type": "NavigationRegion2D",
"enabled": region.enabled,
"navigation_layers": region.navigation_layers,
"has_polygon": region.navigation_polygon != null,
}
if region.navigation_polygon != null:
var nav_poly: NavigationPolygon = region.navigation_polygon
region_info["outline_count"] = nav_poly.get_outline_count()
region_info["polygon_count"] = nav_poly.get_polygon_count()
region_info["cell_size"] = nav_poly.cell_size
region_info["agent_radius"] = nav_poly.agent_radius
regions.append(region_info)
elif node is NavigationRegion3D:
var region: NavigationRegion3D = node as NavigationRegion3D
var region_info := {
"path": str(region.get_path()),
"type": "NavigationRegion3D",
"enabled": region.enabled,
"navigation_layers": region.navigation_layers,
"has_mesh": region.navigation_mesh != null,
}
if region.navigation_mesh != null:
var nav_mesh: NavigationMesh = region.navigation_mesh
region_info["agent_radius"] = nav_mesh.agent_radius
region_info["agent_height"] = nav_mesh.agent_height
region_info["agent_max_climb"] = nav_mesh.agent_max_climb
region_info["agent_max_slope"] = nav_mesh.agent_max_slope
region_info["cell_size"] = nav_mesh.cell_size
region_info["cell_height"] = nav_mesh.cell_height
regions.append(region_info)
if node is NavigationAgent2D:
var agent: NavigationAgent2D = node as NavigationAgent2D
agents.append({
"path": str(agent.get_path()),
"type": "NavigationAgent2D",
"radius": agent.radius,
"max_speed": agent.max_speed,
"path_desired_distance": agent.path_desired_distance,
"target_desired_distance": agent.target_desired_distance,
"neighbor_distance": agent.neighbor_distance,
"max_neighbors": agent.max_neighbors,
"avoidance_enabled": agent.avoidance_enabled,
"navigation_layers": agent.navigation_layers,
})
elif node is NavigationAgent3D:
var agent: NavigationAgent3D = node as NavigationAgent3D
agents.append({
"path": str(agent.get_path()),
"type": "NavigationAgent3D",
"radius": agent.radius,
"max_speed": agent.max_speed,
"path_desired_distance": agent.path_desired_distance,
"target_desired_distance": agent.target_desired_distance,
"neighbor_distance": agent.neighbor_distance,
"max_neighbors": agent.max_neighbors,
"avoidance_enabled": agent.avoidance_enabled,
"navigation_layers": agent.navigation_layers,
})
for child in node.get_children():
_collect_navigation_nodes(child, regions, agents)