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.
#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;
}
}
}