Editor/Tools/AdvancedGameplayTools.cs
#nullable enable
using System;
using Sandbox;
namespace Editor.Mcp
{
public static partial class SboxMcpAssistant
{
[McpTool("pocketknife_paint_terrain")]
public static string PaintTerrain([Description("Terrain type: water, grass, or road")] string type, [Description("Coordinate list or bounding box string")] string bounds)
{
var normalized = (type ?? "").Trim().ToLowerInvariant(); var names = normalized switch { "water" => new[] { "WaterComponent", "WaterTool.WaterComponent" }, "grass" => new[] { "GrassComponent", "GrassTool.GrassComponent" }, "road" => new[] { "RoadComponent", "RoadTool.RoadComponent" }, _ => Array.Empty<string>() };
if (names.Length == 0) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "terrain.invalid_type", $"Unsupported terrain type '{type}'. Use water, grass, or road."));
try
{
var componentType = EditorRuntime.GetComponentType(names[0]) ?? EditorRuntime.GetComponentType(names[1]); if (componentType is null) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Unavailable, "terrain.dependency_missing", $"No component for '{normalized}' is loaded; nothing was painted."));
if (string.IsNullOrWhiteSpace(bounds)) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "terrain.bounds_missing", "A non-empty bounds/coordinate expression is required."));
var go = new GameObject(true, $"{normalized} terrain"); var component = go.Components.Create((dynamic)componentType); return McpJson.Write(ToolOutcome<object>.Degraded(new { Id = go.Id.ToString(), Type = normalized, Bounds = bounds, Component = componentType.ToString() }, "terrain.paint_api_unavailable", "The compatible component was created, but this addon does not know a safe native paint method for its bounds format."));
}
catch (Exception ex) { return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "terrain.paint_failed", "Terrain operation failed.", ex)); }
}
[McpTool("pocketknife_spawn_sandbox_entity")]
public static string SpawnSandboxEntity([Description("Type of entity: prop, weapon, or npc")] string entityType, [Description("Path to model or prefab")] string assetPath)
{
var kind = (entityType ?? "").Trim().ToLowerInvariant(); if (kind is not ("prop" or "weapon" or "npc")) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "entity.invalid_type", "Entity type must be prop, weapon, or npc."));
if (string.IsNullOrWhiteSpace(assetPath)) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "entity.asset_missing", "An asset path is required."));
if (kind != "prop") return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Unavailable, "entity.prefab_instantiation_unavailable", $"No verified prefab factory is available for {kind}; no entity was created.", remediation: "Provide a project prefab factory or use entityType=prop with a model asset."));
try
{
var model = Model.Load(assetPath); if (model is null) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Unavailable, "entity.model_not_found", $"Model '{assetPath}' could not be loaded."));
var go = new GameObject(true, $"prop - {System.IO.Path.GetFileNameWithoutExtension(assetPath)}"); var renderer = go.Components.Create<ModelRenderer>(); renderer.Model = model; var collider = go.Components.Create<ModelCollider>(); collider.Model = model; go.Components.Create<Rigidbody>();
return McpJson.Write(ToolOutcome<object>.Ready(new { Id = go.Id.ToString(), Type = kind, Asset = assetPath, HasRenderer = renderer.Model is not null, HasCollider = collider.Model is not null, Physics = true }));
}
catch (Exception ex) { return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "entity.spawn_failed", "Physics prop creation failed.", ex)); }
}
}
}