🔍 s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (1 total matches found)
kitsupanic.sbox_mcp_plus / Editor/McpExtras.cs
Editor library
using Sandbox;
using System;
using System.Linq;

namespace Editor.Mcp;

/// <summary>
/// Local extensions to the built in MCP tools, living in a shared library so every project here
/// gets them without waiting on an engine release. Tool names are prefixed 'x_' so they can never
/// collide with the engine's own once the upstream equivalents land.
/// </summary>
[McpToolset( "extras", "Local extensions to the built-in MCP tools" )]
public static class ExtrasTools
{
	/// <summary>
	/// Make a scene the active editor tab, opening it from its asset path when it isn't open yet.
	/// Scene edits always target the active scene, so switch before editing a background scene.
	/// Returns the tab it settled on - name, resource path, type, unsaved changes and root object
	/// count - plus a message saying what happened. list_scenes shows what's already open.
	/// </summary>
	/// <param name="scene">Scene name or resource path as list_scenes reports it, or a .scene/.prefab path from asset_search.</param>
	[McpTool( "x_open_scene" )]
	public static SceneTab OpenSceneTab( string scene )
	{
		if ( string.IsNullOrWhiteSpace( scene ) )
			throw new Exception( "Give a scene name or resource path - list_scenes shows what's open, asset_search type:scene finds scene assets on disk" );

		if ( Game.IsPlaying )
			throw new Exception( "Can't switch scene tabs while playing - play_stop first" );

		var session = FindSession( scene );

		if ( session is GameEditorSession )
			throw new Exception( "That's the running game session, which has no tab to switch to - play_stop first, then open the scene you want to edit" );

		if ( session is not null && session == SceneEditorSession.Active )
			return Row( session, $"'{session.Scene?.Name}' was already the active tab - nothing changed" );

		var opened = session is null;

		session ??= SceneEditorSession.CreateFromPath( scene )
			?? throw new Exception( $"Nothing to open for '{scene}' - list_scenes shows what's already open, asset_search type:scene finds scene assets on disk" );

		session.MakeActive();

		return Row( session, opened
			? $"Opened '{session.Scene?.Name}' from disk and made it the active tab"
			: $"Switched the active tab to the already open '{session.Scene?.Name}'" );
	}

	/// <summary>
	/// What the editor is doing right now - which project is open, which scene tab is active and
	/// whether it has unsaved changes, and whether play mode is running or paused. ActiveScene here
	/// is the editor's active tab, which is what the scene tools edit; the built in editor_status
	/// reports the running game's scene instead and can disagree while playing. Follow up with
	/// scene_tree for the hierarchy, or x_open_scene to switch tabs.
	/// </summary>
	[McpTool.ReadOnly( "x_editor_status" )]
	public static EditorStatusExtras GetEditorStatus()
	{
		var session = SceneEditorSession.Active;
		var scene = session?.Scene ?? Game.ActiveScene;

		return new EditorStatusExtras
		{
			Project = Project.Current?.Config?.Ident,
			ProjectTitle = Project.Current?.Config?.Title,
			ActiveScene = scene?.Name,
			ActiveScenePath = scene?.Source?.ResourcePath,
			SceneHasUnsavedChanges = session?.HasUnsavedChanges ?? false,
			OpenSceneCount = SceneEditorSession.All.Count,
			IsPlaying = Game.IsPlaying,
			IsPaused = Game.IsPaused
		};
	}

	/// <summary>
	/// Render a camera in the scene and return it as an image, with UI text intact. Give any
	/// CameraComponent's id or its game object's id, or nothing for the scene's main camera.
	/// Use this instead of camera_screenshot whenever the shot includes Razor UI: camera_screenshot
	/// renders every text label as a flat gray rectangle at any size other than the live viewport's,
	/// because rendering offscreen relayouts the UI, which throws away each label's text texture
	/// without rebuilding the render descriptors that point at it. In play mode this tool always
	/// renders at the native screen resolution - where that relayout is a no-op, so the descriptors
	/// stay valid - and downscales the result to the size you asked for: the output matches the
	/// requested width and height exactly, but its detail is capped at the viewport's resolution,
	/// so asking for more pixels than the viewport has gets you an upscale, not more detail. In
	/// edit mode there is no game viewport, so no live screen-size UI exists to corrupt and it
	/// renders directly at the requested size, at full detail - making this a drop in replacement
	/// for camera_screenshot in both modes. find_game_objects with component 'Camera' lists the
	/// cameras in a scene.
	/// </summary>
	/// <param name="camera">A CameraComponent id or its game object's id. Empty uses the scene's main camera.</param>
	/// <param name="width">Image width in pixels.</param>
	/// <param name="height">Image height in pixels.</param>
	/// <param name="includeUi">Include any UI the camera renders.</param>
	[McpTool.ReadOnly( "x_camera_screenshot" )]
	public static object CameraScreenshotNative( string camera = "", [Sandbox.Range( 16, 4096 )] int width = 1280,
		[Sandbox.Range( 16, 4096 )] int height = 720, bool includeUi = true )
	{
		var target = ResolveCamera( camera );

		if ( !target.IsValid() )
			throw new Exception( "The scene has no camera - find one with find_game_objects component 'Camera', or add one" );

		// The one size the engine bug can't bite: identical to the screen, so the offscreen
		// relayout changes no panel's size and no text texture gets released underneath its
		// descriptor. Everything else is a downscale we do ourselves.
		var nativeWidth = Screen.Width.CeilToInt();
		var nativeHeight = Screen.Height.CeilToInt();

		// No screen size means no game viewport - edit mode. Nothing live is laid out at the
		// screen's size, so there are no text textures a relayout can destroy, and we can render
		// straight at the size asked for, exactly as the built in camera_screenshot does.
		if ( nativeWidth <= 1 || nativeHeight <= 1 )
		{
			var direct = new Bitmap( width, height );
			target.RenderToBitmap( direct, includeUi );
			return direct;
		}

		var bitmap = new Bitmap( nativeWidth, nativeHeight );
		target.RenderToBitmap( bitmap, includeUi );

		if ( nativeWidth == width && nativeHeight == height )
			return bitmap;

		// Resize hands back a new bitmap, so the native capture is ours to release
		using ( bitmap )
		{
			return bitmap.Resize( width, height );
		}
	}

	/// <summary>One scene tab open in the editor.</summary>
	public class SceneTab
	{
		/// <summary>What happened - opened, switched, or already active.</summary>
		public string Message { get; set; }

		/// <summary>The scene's name, as list_scenes reports it.</summary>
		public string Name { get; set; }

		/// <summary>The scene asset's resource path. Null for a scene that was never saved.</summary>
		public string ResourcePath { get; set; }

		/// <summary>Scene, Prefab, or Game for the running session.</summary>
		public string Type { get; set; }

		/// <summary>Whether this is the active tab - true unless something else took focus.</summary>
		public bool IsActive { get; set; }

		/// <summary>Whether the scene has edits that save_scene hasn't written yet.</summary>
		public bool HasUnsavedChanges { get; set; }

		/// <summary>How many objects sit at the scene root.</summary>
		public int RootObjectCount { get; set; }
	}

	/// <summary>The editor's current state, from the editor's point of view rather than the game's.</summary>
	public class EditorStatusExtras
	{
		/// <summary>The open project's ident.</summary>
		public string Project { get; set; }

		/// <summary>The open project's title.</summary>
		public string ProjectTitle { get; set; }

		/// <summary>The active scene tab's name. Falls back to the running game's scene when no tab is active.</summary>
		public string ActiveScene { get; set; }

		/// <summary>The active scene's resource path. Null for a scene that was never saved.</summary>
		public string ActiveScenePath { get; set; }

		/// <summary>Whether the active scene has edits that save_scene hasn't written yet.</summary>
		public bool SceneHasUnsavedChanges { get; set; }

		/// <summary>How many scene tabs are open. list_scenes names them.</summary>
		public int OpenSceneCount { get; set; }

		/// <summary>Whether play mode is running - play_stop returns to editing.</summary>
		public bool IsPlaying { get; set; }

		/// <summary>Whether play mode is paused.</summary>
		public bool IsPaused { get; set; }
	}

	/// <summary>
	/// The open session whose scene matches a name or resource path, case insensitive. Null when
	/// nothing open matches - the caller decides whether to open it from disk.
	/// </summary>
	private static SceneEditorSession FindSession( string nameOrPath )
	{
		return SceneEditorSession.All
			.FirstOrDefault( x => string.Equals( x.Scene?.Name, nameOrPath, StringComparison.OrdinalIgnoreCase )
				|| string.Equals( x.Scene?.Source?.ResourcePath, nameOrPath, StringComparison.OrdinalIgnoreCase ) );
	}

	/// <summary>
	/// The camera a tool argument names - a CameraComponent id, or a game object id whose
	/// CameraComponent we take. Empty means the active scene's main camera. The engine's own
	/// resolvers are private to the tools addon, so this repeats them.
	/// </summary>
	private static CameraComponent ResolveCamera( string camera )
	{
		if ( string.IsNullOrWhiteSpace( camera ) )
		{
			var scene = SceneEditorSession.Active?.Scene ?? Game.ActiveScene
				?? throw new Exception( "No scene is open in the editor" );

			return scene.Camera;
		}

		if ( !Guid.TryParse( camera, out var guid ) )
			throw new Exception( $"'{camera}' isn't a guid - find_game_objects and scene_tree show object ids, get_game_object shows component ids" );

		foreach ( var session in SceneEditorSession.All )
		{
			if ( session.Scene?.Directory?.FindComponentByGuid( guid ) is Component component )
			{
				return component as CameraComponent
					?? throw new Exception( "That component isn't a camera - give a CameraComponent or its game object" );
			}

			if ( session.Scene?.Directory?.FindByGuid( guid ) is GameObject go )
			{
				// includeDisabled, matching the built-in resolver's view of a game object's components
				return go.Components.Get<CameraComponent>( true )
					?? throw new Exception( $"'{go.Name}' has no camera component - find one with find_game_objects component 'Camera'" );
			}
		}

		throw new Exception( $"Nothing in any open scene has id {guid} - find_game_objects and scene_tree show what's there" );
	}

	private static SceneTab Row( SceneEditorSession session, string message )
	{
		return new SceneTab
		{
			Message = message,
			Name = session.Scene?.Name,
			ResourcePath = session.Scene?.Source?.ResourcePath,
			Type = session is GameEditorSession ? "Game" : session.Scene is PrefabScene ? "Prefab" : "Scene",
			IsActive = session == SceneEditorSession.Active,
			HasUnsavedChanges = session.HasUnsavedChanges,
			RootObjectCount = session.Scene?.Children.Count ?? 0
		};
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 1,
    "Files": [
        {
            "Ident": "kitsupanic.sbox_mcp_plus",
            "Path": "Editor/McpExtras.cs",
            "FileName": "McpExtras.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 341820,
            "Code": "using Sandbox;\nusing System;\nusing System.Linq;\n\nnamespace Editor.Mcp;\n\n/// <summary>\n/// Local extensions to the built in MCP tools, living in a shared library so every project here\n/// gets them without waiting on an engine release. Tool names are prefixed 'x_' so they can never\n/// collide with the engine's own once the upstream equivalents land.\n/// </summary>\n[McpToolset( \"extras\", \"Local extensions to the built-in MCP tools\" )]\npublic static class ExtrasTools\n{\n\t/// <summary>\n\t/// Make a scene the active editor tab, opening it from its asset path when it isn't open yet.\n\t/// Scene edits always target the active scene, so switch before editing a background scene.\n\t/// Returns the tab it settled on - name, resource path, type, unsaved changes and root object\n\t/// count - plus a message saying what happened. list_scenes shows what's already open.\n\t/// </summary>\n\t/// <param name=\"scene\">Scene name or resource path as list_scenes reports it, or a .scene/.prefab path from asset_search.</param>\n\t[McpTool( \"x_open_scene\" )]\n\tpublic static SceneTab OpenSceneTab( string scene )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( scene ) )\n\t\t\tthrow new Exception( \"Give a scene name or resource path - list_scenes shows what's open, asset_search type:scene finds scene assets on disk\" );\n\n\t\tif ( Game.IsPlaying )\n\t\t\tthrow new Exception( \"Can't switch scene tabs while playing - play_stop first\" );\n\n\t\tvar session = FindSession( scene );\n\n\t\tif ( session is GameEditorSession )\n\t\t\tthrow new Exception( \"That's the running game session, which has no tab to switch to - play_stop first, then open the scene you want to edit\" );\n\n\t\tif ( session is not null && session == SceneEditorSession.Active )\n\t\t\treturn Row( session, $\"'{session.Scene?.Name}' was already the active tab - nothing changed\" );\n\n\t\tvar opened = session is null;\n\n\t\tsession ??= SceneEditorSession.CreateFromPath( scene )\n\t\t\t?? throw new Exception( $\"Nothing to open for '{scene}' - list_scenes shows what's already open, asset_search type:scene finds scene assets on disk\" );\n\n\t\tsession.MakeActive();\n\n\t\treturn Row( session, opened\n\t\t\t? $\"Opened '{session.Scene?.Name}' from disk and made it the active tab\"\n\t\t\t: $\"Switched the active tab to the already open '{session.Scene?.Name}'\" );\n\t}\n\n\t/// <summary>\n\t/// What the editor is doing right now - which project is open, which scene tab is active and\n\t/// whether it has unsaved changes, and whether play mode is running or paused. ActiveScene here\n\t/// is the editor's active tab, which is what the scene tools edit; the built in editor_status\n\t/// reports the running game's scene instead and can disagree while playing. Follow up with\n\t/// scene_tree for the hierarchy, or x_open_scene to switch tabs.\n\t/// </summary>\n\t[McpTool.ReadOnly( \"x_editor_status\" )]\n\tpublic static EditorStatusExtras GetEditorStatus()\n\t{\n\t\tvar session = SceneEditorSession.Active;\n\t\tvar scene = session?.Scene ?? Game.ActiveScene;\n\n\t\treturn new EditorStatusExtras\n\t\t{\n\t\t\tProject = Project.Current?.Config?.Ident,\n\t\t\tProjectTitle = Project.Current?.Config?.Title,\n\t\t\tActiveScene = scene?.Name,\n\t\t\tActiveScenePath = scene?.Source?.ResourcePath,\n\t\t\tSceneHasUnsavedChanges = session?.HasUnsavedChanges ?? false,\n\t\t\tOpenSceneCount = SceneEditorSession.All.Count,\n\t\t\tIsPlaying = Game.IsPlaying,\n\t\t\tIsPaused = Game.IsPaused\n\t\t};\n\t}\n\n\t/// <summary>\n\t/// Render a camera in the scene and return it as an image, with UI text intact. Give any\n\t/// CameraComponent's id or its game object's id, or nothing for the scene's main camera.\n\t/// Use this instead of camera_screenshot whenever the shot includes Razor UI: camera_screenshot\n\t/// renders every text label as a flat gray rectangle at any size other than the live viewport's,\n\t/// because rendering offscreen relayouts the UI, which throws away each label's text texture\n\t/// without rebuilding the render descriptors that point at it. In play mode this tool always\n\t/// renders at the native screen resolution - where that relayout is a no-op, so the descriptors\n\t/// stay valid - and downscales the result to the size you asked for: the output matches the\n\t/// requested width and height exactly, but its detail is capped at the viewport's resolution,\n\t/// so asking for more pixels than the viewport has gets you an upscale, not more detail. In\n\t/// edit mode there is no game viewport, so no live screen-size UI exists to corrupt and it\n\t/// renders directly at the requested size, at full detail - making this a drop in replacement\n\t/// for camera_screenshot in both modes. find_game_objects with component 'Camera' lists the\n\t/// cameras in a scene.\n\t/// </summary>\n\t/// <param name=\"camera\">A CameraComponent id or its game object's id. Empty uses the scene's main camera.</param>\n\t/// <param name=\"width\">Image width in pixels.</param>\n\t/// <param name=\"height\">Image height in pixels.</param>\n\t/// <param name=\"includeUi\">Include any UI the camera renders.</param>\n\t[McpTool.ReadOnly( \"x_camera_screenshot\" )]\n\tpublic static object CameraScreenshotNative( string camera = \"\", [Sandbox.Range( 16, 4096 )] int width = 1280,\n\t\t[Sandbox.Range( 16, 4096 )] int height = 720, bool includeUi = true )\n\t{\n\t\tvar target = ResolveCamera( camera );\n\n\t\tif ( !target.IsValid() )\n\t\t\tthrow new Exception( \"The scene has no camera - find one with find_game_objects component 'Camera', or add one\" );\n\n\t\t// The one size the engine bug can't bite: identical to the screen, so the offscreen\n\t\t// relayout changes no panel's size and no text texture gets released underneath its\n\t\t// descriptor. Everything else is a downscale we do ourselves.\n\t\tvar nativeWidth = Screen.Width.CeilToInt();\n\t\tvar nativeHeight = Screen.Height.CeilToInt();\n\n\t\t// No screen size means no game viewport - edit mode. Nothing live is laid out at the\n\t\t// screen's size, so there are no text textures a relayout can destroy, and we can render\n\t\t// straight at the size asked for, exactly as the built in camera_screenshot does.\n\t\tif ( nativeWidth <= 1 || nativeHeight <= 1 )\n\t\t{\n\t\t\tvar direct = new Bitmap( width, height );\n\t\t\ttarget.RenderToBitmap( direct, includeUi );\n\t\t\treturn direct;\n\t\t}\n\n\t\tvar bitmap = new Bitmap( nativeWidth, nativeHeight );\n\t\ttarget.RenderToBitmap( bitmap, includeUi );\n\n\t\tif ( nativeWidth == width && nativeHeight == height )\n\t\t\treturn bitmap;\n\n\t\t// Resize hands back a new bitmap, so the native capture is ours to release\n\t\tusing ( bitmap )\n\t\t{\n\t\t\treturn bitmap.Resize( width, height );\n\t\t}\n\t}\n\n\t/// <summary>One scene tab open in the editor.</summary>\n\tpublic class SceneTab\n\t{\n\t\t/// <summary>What happened - opened, switched, or already active.</summary>\n\t\tpublic string Message { get; set; }\n\n\t\t/// <summary>The scene's name, as list_scenes reports it.</summary>\n\t\tpublic string Name { get; set; }\n\n\t\t/// <summary>The scene asset's resource path. Null for a scene that was never saved.</summary>\n\t\tpublic string ResourcePath { get; set; }\n\n\t\t/// <summary>Scene, Prefab, or Game for the running session.</summary>\n\t\tpublic string Type { get; set; }\n\n\t\t/// <summary>Whether this is the active tab - true unless something else took focus.</summary>\n\t\tpublic bool IsActive { get; set; }\n\n\t\t/// <summary>Whether the scene has edits that save_scene hasn't written yet.</summary>\n\t\tpublic bool HasUnsavedChanges { get; set; }\n\n\t\t/// <summary>How many objects sit at the scene root.</summary>\n\t\tpublic int RootObjectCount { get; set; }\n\t}\n\n\t/// <summary>The editor's current state, from the editor's point of view rather than the game's.</summary>\n\tpublic class EditorStatusExtras\n\t{\n\t\t/// <summary>The open project's ident.</summary>\n\t\tpublic string Project { get; set; }\n\n\t\t/// <summary>The open project's title.</summary>\n\t\tpublic string ProjectTitle { get; set; }\n\n\t\t/// <summary>The active scene tab's name. Falls back to the running game's scene when no tab is active.</summary>\n\t\tpublic string ActiveScene { get; set; }\n\n\t\t/// <summary>The active scene's resource path. Null for a scene that was never saved.</summary>\n\t\tpublic string ActiveScenePath { get; set; }\n\n\t\t/// <summary>Whether the active scene has edits that save_scene hasn't written yet.</summary>\n\t\tpublic bool SceneHasUnsavedChanges { get; set; }\n\n\t\t/// <summary>How many scene tabs are open. list_scenes names them.</summary>\n\t\tpublic int OpenSceneCount { get; set; }\n\n\t\t/// <summary>Whether play mode is running - play_stop returns to editing.</summary>\n\t\tpublic bool IsPlaying { get; set; }\n\n\t\t/// <summary>Whether play mode is paused.</summary>\n\t\tpublic bool IsPaused { get; set; }\n\t}\n\n\t/// <summary>\n\t/// The open session whose scene matches a name or resource path, case insensitive. Null when\n\t/// nothing open matches - the caller decides whether to open it from disk.\n\t/// </summary>\n\tprivate static SceneEditorSession FindSession( string nameOrPath )\n\t{\n\t\treturn SceneEditorSession.All\n\t\t\t.FirstOrDefault( x => string.Equals( x.Scene?.Name, nameOrPath, StringComparison.OrdinalIgnoreCase )\n\t\t\t\t|| string.Equals( x.Scene?.Source?.ResourcePath, nameOrPath, StringComparison.OrdinalIgnoreCase ) );\n\t}\n\n\t/// <summary>\n\t/// The camera a tool argument names - a CameraComponent id, or a game object id whose\n\t/// CameraComponent we take. Empty means the active scene's main camera. The engine's own\n\t/// resolvers are private to the tools addon, so this repeats them.\n\t/// </summary>\n\tprivate static CameraComponent ResolveCamera( string camera )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( camera ) )\n\t\t{\n\t\t\tvar scene = SceneEditorSession.Active?.Scene ?? Game.ActiveScene\n\t\t\t\t?? throw new Exception( \"No scene is open in the editor\" );\n\n\t\t\treturn scene.Camera;\n\t\t}\n\n\t\tif ( !Guid.TryParse( camera, out var guid ) )\n\t\t\tthrow new Exception( $\"'{camera}' isn't a guid - find_game_objects and scene_tree show object ids, get_game_object shows component ids\" );\n\n\t\tforeach ( var session in SceneEditorSession.All )\n\t\t{\n\t\t\tif ( session.Scene?.Directory?.FindComponentByGuid( guid ) is Component component )\n\t\t\t{\n\t\t\t\treturn component as CameraComponent\n\t\t\t\t\t?? throw new Exception( \"That component isn't a camera - give a CameraComponent or its game object\" );\n\t\t\t}\n\n\t\t\tif ( session.Scene?.Directory?.FindByGuid( guid ) is GameObject go )\n\t\t\t{\n\t\t\t\t// includeDisabled, matching the built-in resolver's view of a game object's components\n\t\t\t\treturn go.Components.Get<CameraComponent>( true )\n\t\t\t\t\t?? throw new Exception( $\"'{go.Name}' has no camera component - find one with find_game_objects component 'Camera'\" );\n\t\t\t}\n\t\t}\n\n\t\tthrow new Exception( $\"Nothing in any open scene has id {guid} - find_game_objects and scene_tree show what's there\" );\n\t}\n\n\tprivate static SceneTab Row( SceneEditorSession session, string message )\n\t{\n\t\treturn new SceneTab\n\t\t{\n\t\t\tMessage = message,\n\t\t\tName = session.Scene?.Name,\n\t\t\tResourcePath = session.Scene?.Source?.ResourcePath,\n\t\t\tType = session is GameEditorSession ? \"Game\" : session.Scene is PrefabScene ? \"Prefab\" : \"Scene\",\n\t\t\tIsActive = session == SceneEditorSession.Active,\n\t\t\tHasUnsavedChanges = session.HasUnsavedChanges,\n\t\t\tRootObjectCount = session.Scene?.Children.Count ?? 0\n\t\t};\n\t}\n}\n"
        }
    ]
}