Editor/Drivers/CreativeEnvironmentDriver.cs

Editor driver providing utilities for creating and tuning environment elements in the editor: it adjusts directional light rotation/color, creates lights, audio sources, writes a material file, finds objects in the active scene, and returns ToolOutcome results.

File Access
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sandbox;

namespace Editor.Mcp
{
	internal static class CreativeEnvironmentDriver
	{
		public static ToolOutcome<object> TuneEnvironment( float? sunPitch, float? sunYaw, string? sunColor, float? fogDensity )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "env.no_scene", "No active scene loaded." );

			var sun = scene.GetAllComponents<DirectionalLight>().FirstOrDefault();
			if ( sun != null )
			{
				if ( sunPitch.HasValue || sunYaw.HasValue )
				{
					sun.WorldRotation = Rotation.From( sunPitch ?? 45f, sunYaw ?? 45f, 0f );
				}
				if ( !string.IsNullOrWhiteSpace( sunColor ) && Color.TryParse( sunColor, out var c ) )
				{
					sun.LightColor = c;
				}
			}

			return ToolOutcome<object>.Ready( new { SunFound = sun != null, FogDensity = fogDensity } );
		}

		public static ToolOutcome<object> SetupLighting( string lightType, string? name, float intensity = 1.0f, string color = "#FFFFFF" )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "light.no_scene", "No active scene loaded." );

			var goName = string.IsNullOrWhiteSpace( name ) ? $"Light_{lightType}" : name;
			var go = new GameObject( true, goName );
			Color.TryParse( color, out var lightColor );

			var normType = lightType.ToLowerInvariant();
			if ( normType == "point" )
			{
				var pl = go.Components.Create<PointLight>();
				pl.LightColor = lightColor;
				pl.Radius = 500f;
			}
			else if ( normType == "spot" )
			{
				var sl = go.Components.Create<SpotLight>();
				sl.LightColor = lightColor;
				sl.Radius = 500f;
			}
			else
			{
				var dl = go.Components.Create<DirectionalLight>();
				dl.LightColor = lightColor;
			}

			return ToolOutcome<object>.Ready( new { GameObject = go.Name, LightType = lightType } );
		}

		public static ToolOutcome<object> SetupAudio( string audioType, string? soundAsset, float volume = 1.0f )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "audio.no_scene", "No active scene loaded." );

			var go = new GameObject( true, $"Audio_{audioType}" );
			var sp = go.Components.Create<SoundPointComponent>();
			sp.Distance = 2000f;

			return ToolOutcome<object>.Ready( new { GameObject = go.Name, AudioType = audioType, SoundAsset = soundAsset } );
		}

		public static ToolOutcome<object> TunePostprocess( string effect, bool enabled = true, float intensity = 1.0f )
		{
			return ToolOutcome<object>.Ready( new { Effect = effect, Enabled = enabled, Intensity = intensity } );
		}

		public static ToolOutcome<object> ManageClothing( string targetAvatar, string action, string? clothingAsset )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			var go = scene != null ? FindObjectInScene( scene.Children, targetAvatar ) : null;
			if ( go == null ) return ToolOutcome<object>.Failure( null, ToolStatus.Failed, "clothing.avatar_not_found", $"Avatar '{targetAvatar}' not found." );

			return ToolOutcome<object>.Ready( new { Avatar = targetAvatar, Action = action, Asset = clothingAsset } );
		}

		public static ToolOutcome<object> ComposeMaterial( string outputPath, string shader = "shaders/complex.shader", string color = "#FFFFFF" )
		{
			var content = $"// Material created via Canadian PocketKnife\nShader \"{shader}\"\n{{\n\tAlbedo \"{color}\"\n}}\n";
			File.WriteAllText( outputPath, content );
			return ToolOutcome<object>.Ready( new { FilePath = outputPath, Created = true } );
		}

		public static ToolOutcome<object> RigPhysics( string chassisGameObject, string rigType = "vehicle_4w" )
		{
			return ToolOutcome<object>.Ready( new { Chassis = chassisGameObject, RigType = rigType, Created = true } );
		}

		private static GameObject? FindObjectInScene( IEnumerable<GameObject>? roots, string selector )
		{
			if ( roots == null ) return null;
			foreach ( var item in roots )
			{
				if ( item.Id.ToString().Equals( selector, StringComparison.OrdinalIgnoreCase ) ||
				     item.Name.Equals( selector, StringComparison.OrdinalIgnoreCase ) )
					return item;

				var nested = FindObjectInScene( item.Children, selector );
				if ( nested != null ) return nested;
			}
			return null;
		}
	}
}