Editor/Commands/Tools/CreateGameObjectTool.cs
using System.Threading.Tasks;
using System.Text.Json;
using System.Collections.Generic;
using System.Linq;
using System;
using Editor;
using Sandbox;

namespace Braxnet.Commands.Tools;

[MCPTool( "create_gameobject", "Create GameObject", "Create a new GameObject in the scene" )]
public class CreateGameObjectTool : IMCPTool
{
	public string Name => "create_gameobject";
	public string Title => "Create GameObject";
	public string Description => "Create a new GameObject in the scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object",
		properties = new
		{
			name = new { type = "string", description = "Name of the GameObject" },
			position = new
			{
				type = "array",
				items = new { type = "number" },
				description = "Vector3 position in world space, Z-up"
			},
			rotation = new
			{
				type = "array",
				items = new { type = "number" },
				description = "Angles rotation in world space"
			},
		},
		required = new[] { "name", "position", "rotation" }
	} );

	public JsonElement OutputSchema => default;

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		if ( arguments == null )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "No arguments provided" } );
			return result;
		}

		var name = arguments.GetValueOrDefault( "name" )?.ToString();
		var positionArray = arguments.GetValueOrDefault( "position" ) as JsonElement?;
		var rotationArray = arguments.GetValueOrDefault( "rotation" ) as JsonElement?;

		if ( string.IsNullOrEmpty( name ) )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "Name is required" } );
			return result;
		}

		try
		{
			Vector3 position = Vector3.Zero;
			Angles rotation = Angles.Zero;

			// Parse position
			if ( positionArray.HasValue && positionArray.Value.ValueKind == JsonValueKind.Array )
			{
				var posArray = positionArray.Value.EnumerateArray().Select( x => x.GetSingle() ).ToArray();
				if ( posArray.Length >= 3 )
				{
					position = new Vector3( posArray[0], posArray[1], posArray[2] );
				}
			}

			// Parse rotation
			if ( rotationArray.HasValue && rotationArray.Value.ValueKind == JsonValueKind.Array )
			{
				var rotArray = rotationArray.Value.EnumerateArray().Select( x => x.GetSingle() ).ToArray();
				if ( rotArray.Length >= 3 )
				{
					rotation = new Angles( rotArray[0], rotArray[1], rotArray[2] );
				}
			}

			await GameTask.MainThread(); // Ensure this runs on the main thread

			GameObject gameObject;

			using ( SceneEditorSession.Active.UndoScope( "Create Empty" ).WithGameObjectCreations().Push() )
			{
				gameObject = SceneEditorSession.Active.Scene.CreateObject();
				gameObject.Name = name;
				gameObject.WorldPosition = position;
				gameObject.WorldRotation = rotation;
			}

			result.Content.Add( new TextContent { Text = $"Successfully created GameObject: {name}" } );
			result.StructuredContent = new
			{
				gameObjectId = gameObject.Id,
				name = gameObject.Name,
				position =
					new[] { gameObject.WorldPosition.x, gameObject.WorldPosition.y, gameObject.WorldPosition.z },
				rotation = new[]
				{
					gameObject.WorldRotation.Pitch(), gameObject.WorldRotation.Yaw(),
					gameObject.WorldRotation.Roll()
				}
			};
		}
		catch ( Exception ex )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = $"Error creating GameObject: {ex.Message}" } );
		}

		return result;
	}
}

/*[MCPTool( "enter_play_mode", "Enter Play Mode",
	"Enter the play mode of the scene" )]
public class EnterPlayModeTool : IMCPTool
{
	public string Name => "enter_play_mode";
	public string Title => "Enter Play Mode";
	public string Description => "Enter the play mode of the scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object", properties = new { }, required = Array.Empty<string>()
	} );

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		EditorScene.Play();

		result.Content.Add( new TextContent { Text = "Entered play mode" } );
		result.StructuredContent = new { status = "play_mode", message = "Entered play mode successfully" };

		return result;
	}
}*/

/*[MCPTool( "take_screenshot", "Take Screenshot",
	"Take a screenshot of the current scene" )]
public class TakeScreenshotTool : IMCPTool
{
	public string Name => "take_screenshot";
	public string Title => "Take Screenshot";
	public string Description => "Take a screenshot of the current scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object", properties = new { }, required = Array.Empty<string>()
	} );

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		try
		{
			if ( Gizmo.Camera == null )
			{
				result.IsError = true;
				result.Content.Add( new TextContent { Text = "No active camera found" } );
				return result;
			}

			var pixmap = new Pixmap( (int)Screen.Width, (int)Screen.Height );
			Gizmo.Camera.RenderToPixmap( pixmap );

			// store the screenshot in a temporary file
			var tempFilePath = Path.Combine( FileSystem.Temporary.GetFullPath( "." ),
				$"screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png" );
			pixmap.SaveJpg( tempFilePath, 90 );

			result.Content.Add( new TextContent { Text = "Screenshot taken successfully" } );
			result.StructuredContent = new { filePath = tempFilePath, message = "Screenshot saved to temporary file" };
		}
		catch ( Exception ex )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = $"Error taking screenshot: {ex.Message}" } );
		}

		return result;
	}
}*/