{"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/// \u003Csummary\u003E\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 \u0027x_\u0027 so they can never\n/// collide with the engine\u0027s own once the upstream equivalents land.\n/// \u003C/summary\u003E\n[McpToolset( \u0022extras\u0022, \u0022Local extensions to the built-in MCP tools\u0022 )]\npublic static class ExtrasTools\n{\n\t/// \u003Csummary\u003E\n\t/// Make a scene the active editor tab, opening it from its asset path when it isn\u0027t 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\u0027s already open.\n\t/// \u003C/summary\u003E\n\t/// \u003Cparam name=\u0022scene\u0022\u003EScene name or resource path as list_scenes reports it, or a .scene/.prefab path from asset_search.\u003C/param\u003E\n\t[McpTool( \u0022x_open_scene\u0022 )]\n\tpublic static SceneTab OpenSceneTab( string scene )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( scene ) )\n\t\t\tthrow new Exception( \u0022Give a scene name or resource path - list_scenes shows what\u0027s open, asset_search type:scene finds scene assets on disk\u0022 );\n\n\t\tif ( Game.IsPlaying )\n\t\t\tthrow new Exception( \u0022Can\u0027t switch scene tabs while playing - play_stop first\u0022 );\n\n\t\tvar session = FindSession( scene );\n\n\t\tif ( session is GameEditorSession )\n\t\t\tthrow new Exception( \u0022That\u0027s the running game session, which has no tab to switch to - play_stop first, then open the scene you want to edit\u0022 );\n\n\t\tif ( session is not null \u0026\u0026 session == SceneEditorSession.Active )\n\t\t\treturn Row( session, $\u0022\u0027{session.Scene?.Name}\u0027 was already the active tab - nothing changed\u0022 );\n\n\t\tvar opened = session is null;\n\n\t\tsession ??= SceneEditorSession.CreateFromPath( scene )\n\t\t\t?? throw new Exception( $\u0022Nothing to open for \u0027{scene}\u0027 - list_scenes shows what\u0027s already open, asset_search type:scene finds scene assets on disk\u0022 );\n\n\t\tsession.MakeActive();\n\n\t\treturn Row( session, opened\n\t\t\t? $\u0022Opened \u0027{session.Scene?.Name}\u0027 from disk and made it the active tab\u0022\n\t\t\t: $\u0022Switched the active tab to the already open \u0027{session.Scene?.Name}\u0027\u0022 );\n\t}\n\n\t/// \u003Csummary\u003E\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\u0027s active tab, which is what the scene tools edit; the built in editor_status\n\t/// reports the running game\u0027s 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/// \u003C/summary\u003E\n\t[McpTool.ReadOnly( \u0022x_editor_status\u0022 )]\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/// \u003Csummary\u003E\n\t/// Render a camera in the scene and return it as an image, with UI text intact. Give any\n\t/// CameraComponent\u0027s id or its game object\u0027s id, or nothing for the scene\u0027s 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\u0027s,\n\t/// because rendering offscreen relayouts the UI, which throws away each label\u0027s 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\u0027s 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 \u0027Camera\u0027 lists the\n\t/// cameras in a scene.\n\t/// \u003C/summary\u003E\n\t/// \u003Cparam name=\u0022camera\u0022\u003EA CameraComponent id or its game object\u0027s id. Empty uses the scene\u0027s main camera.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022width\u0022\u003EImage width in pixels.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022height\u0022\u003EImage height in pixels.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022includeUi\u0022\u003EInclude any UI the camera renders.\u003C/param\u003E\n\t[McpTool.ReadOnly( \u0022x_camera_screenshot\u0022 )]\n\tpublic static object CameraScreenshotNative( string camera = \u0022\u0022, [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( \u0022The scene has no camera - find one with find_game_objects component \u0027Camera\u0027, or add one\u0022 );\n\n\t\t// The one size the engine bug can\u0027t bite: identical to the screen, so the offscreen\n\t\t// relayout changes no panel\u0027s 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\u0027s 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 \u003C= 1 || nativeHeight \u003C= 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 \u0026\u0026 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/// \u003Csummary\u003EOne scene tab open in the editor.\u003C/summary\u003E\n\tpublic class SceneTab\n\t{\n\t\t/// \u003Csummary\u003EWhat happened - opened, switched, or already active.\u003C/summary\u003E\n\t\tpublic string Message { get; set; }\n\n\t\t/// \u003Csummary\u003EThe scene\u0027s name, as list_scenes reports it.\u003C/summary\u003E\n\t\tpublic string Name { get; set; }\n\n\t\t/// \u003Csummary\u003EThe scene asset\u0027s resource path. Null for a scene that was never saved.\u003C/summary\u003E\n\t\tpublic string ResourcePath { get; set; }\n\n\t\t/// \u003Csummary\u003EScene, Prefab, or Game for the running session.\u003C/summary\u003E\n\t\tpublic string Type { get; set; }\n\n\t\t/// \u003Csummary\u003EWhether this is the active tab - true unless something else took focus.\u003C/summary\u003E\n\t\tpublic bool IsActive { get; set; }\n\n\t\t/// \u003Csummary\u003EWhether the scene has edits that save_scene hasn\u0027t written yet.\u003C/summary\u003E\n\t\tpublic bool HasUnsavedChanges { get; set; }\n\n\t\t/// \u003Csummary\u003EHow many objects sit at the scene root.\u003C/summary\u003E\n\t\tpublic int RootObjectCount { get; set; }\n\t}\n\n\t/// \u003Csummary\u003EThe editor\u0027s current state, from the editor\u0027s point of view rather than the game\u0027s.\u003C/summary\u003E\n\tpublic class EditorStatusExtras\n\t{\n\t\t/// \u003Csummary\u003EThe open project\u0027s ident.\u003C/summary\u003E\n\t\tpublic string Project { get; set; }\n\n\t\t/// \u003Csummary\u003EThe open project\u0027s title.\u003C/summary\u003E\n\t\tpublic string ProjectTitle { get; set; }\n\n\t\t/// \u003Csummary\u003EThe active scene tab\u0027s name. Falls back to the running game\u0027s scene when no tab is active.\u003C/summary\u003E\n\t\tpublic string ActiveScene { get; set; }\n\n\t\t/// \u003Csummary\u003EThe active scene\u0027s resource path. Null for a scene that was never saved.\u003C/summary\u003E\n\t\tpublic string ActiveScenePath { get; set; }\n\n\t\t/// \u003Csummary\u003EWhether the active scene has edits that save_scene hasn\u0027t written yet.\u003C/summary\u003E\n\t\tpublic bool SceneHasUnsavedChanges { get; set; }\n\n\t\t/// \u003Csummary\u003EHow many scene tabs are open. list_scenes names them.\u003C/summary\u003E\n\t\tpublic int OpenSceneCount { get; set; }\n\n\t\t/// \u003Csummary\u003EWhether play mode is running - play_stop returns to editing.\u003C/summary\u003E\n\t\tpublic bool IsPlaying { get; set; }\n\n\t\t/// \u003Csummary\u003EWhether play mode is paused.\u003C/summary\u003E\n\t\tpublic bool IsPaused { get; set; }\n\t}\n\n\t/// \u003Csummary\u003E\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/// \u003C/summary\u003E\n\tprivate static SceneEditorSession FindSession( string nameOrPath )\n\t{\n\t\treturn SceneEditorSession.All\n\t\t\t.FirstOrDefault( x =\u003E 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/// \u003Csummary\u003E\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\u0027s main camera. The engine\u0027s own\n\t/// resolvers are private to the tools addon, so this repeats them.\n\t/// \u003C/summary\u003E\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( \u0022No scene is open in the editor\u0022 );\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( $\u0022\u0027{camera}\u0027 isn\u0027t a guid - find_game_objects and scene_tree show object ids, get_game_object shows component ids\u0022 );\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( \u0022That component isn\u0027t a camera - give a CameraComponent or its game object\u0022 );\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\u0027s view of a game object\u0027s components\n\t\t\t\treturn go.Components.Get\u003CCameraComponent\u003E( true )\n\t\t\t\t\t?? throw new Exception( $\u0022\u0027{go.Name}\u0027 has no camera component - find one with find_game_objects component \u0027Camera\u0027\u0022 );\n\t\t\t}\n\t\t}\n\n\t\tthrow new Exception( $\u0022Nothing in any open scene has id {guid} - find_game_objects and scene_tree show what\u0027s there\u0022 );\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 ? \u0022Game\u0022 : session.Scene is PrefabScene ? \u0022Prefab\u0022 : \u0022Scene\u0022,\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"}]}