Editor/EffectToolHandlers.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Editor;
using Sandbox;
using Sandbox.Clutter;
namespace SboxMcpServer;
/// <summary>
/// Effect & environment MCP tools: create_particle_effect, configure_particle_effect,
/// create_fog_volume, configure_post_processing, create_environment_light.
/// </summary>
internal static class EffectToolHandlers
{
private static readonly JsonSerializerOptions _json = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
// ── create_particle_effect ──────────────────────────────────────────────
internal static object CreateParticleEffect( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Particle Effect" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var pe = go.Components.Create<ParticleEffect>();
pe.MaxParticles = OzmiumSceneHelpers.Get( args, "maxParticles", 1000 );
pe.Lifetime = OzmiumSceneHelpers.Get( args, "lifetime", 1f );
pe.TimeScale = OzmiumSceneHelpers.Get( args, "timeScale", 1.0f );
pe.PreWarm = OzmiumSceneHelpers.Get( args, "preWarm", 0.0f );
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created ParticleEffect '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── configure_particle_effect ────────────────────────────────────────────
internal static object ConfigureParticleEffect( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
string id = OzmiumSceneHelpers.Get( args, "id", (string)null );
string name = OzmiumSceneHelpers.Get( args, "name", (string)null );
var go = OzmiumSceneHelpers.FindGoWithComponent<ParticleEffect>( scene, id, name );
if ( go == null ) return OzmiumSceneHelpers.Txt( $"No object with ParticleEffect found (id={id ?? "null"}, name={name ?? "null"})." );
var pe = go.Components.Get<ParticleEffect>();
try
{
if ( args.TryGetProperty( "maxParticles", out var mpEl ) )
pe.MaxParticles = mpEl.GetInt32();
if ( args.TryGetProperty( "lifetime", out var ltEl ) )
pe.Lifetime = ltEl.GetSingle();
if ( args.TryGetProperty( "timeScale", out var tsEl ) )
pe.TimeScale = tsEl.GetSingle();
if ( args.TryGetProperty( "preWarm", out var pwEl ) )
pe.PreWarm = pwEl.GetSingle();
return OzmiumSceneHelpers.Txt( $"Configured ParticleEffect on '{go.Name}'." );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_fog_volume ────────────────────────────────────────────────────
internal static object CreateFogVolume( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Fog Volume" );
string fogType = OzmiumSceneHelpers.Get( args, "fogType", "gradient" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
if ( fogType.Equals( "volumetric", StringComparison.OrdinalIgnoreCase ) )
{
var vf = go.Components.Create<VolumetricFogVolume>();
vf.Strength = OzmiumSceneHelpers.Get( args, "strength", 1.0f );
vf.FalloffExponent = OzmiumSceneHelpers.Get( args, "falloffExponent", 1.0f );
vf.Bounds = BBox.FromPositionAndSize( 0, 300 );
}
else
{
var gf = go.Components.Create<GradientFog>();
gf.Color = Color.White;
gf.Height = OzmiumSceneHelpers.Get( args, "height", 100f );
gf.StartDistance = OzmiumSceneHelpers.Get( args, "startDistance", 0f );
gf.EndDistance = OzmiumSceneHelpers.Get( args, "endDistance", 1024f );
gf.FalloffExponent = OzmiumSceneHelpers.Get( args, "falloffExponent", 1.0f );
gf.VerticalFalloffExponent = OzmiumSceneHelpers.Get( args, "verticalFalloffExponent", 1.0f );
if ( args.TryGetProperty( "color", out var colEl ) && colEl.ValueKind == JsonValueKind.String )
{
try { gf.Color = Color.Parse( colEl.GetString() ) ?? default; } catch { }
}
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created {fogType} fog on '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── configure_post_processing ────────────────────────────────────────────
internal static object ConfigurePostProcessing( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
string id = OzmiumSceneHelpers.Get( args, "id", (string)null );
string name = OzmiumSceneHelpers.Get( args, "name", (string)null );
var go = OzmiumSceneHelpers.FindGo( scene, id, name );
if ( go == null ) return OzmiumSceneHelpers.Txt( "Object not found." );
try
{
var pp = go.Components.Create<PostProcessVolume>();
pp.Priority = OzmiumSceneHelpers.Get( args, "priority", 0 );
pp.BlendWeight = OzmiumSceneHelpers.Get( args, "blendWeight", 1.0f );
pp.BlendDistance = OzmiumSceneHelpers.Get( args, "blendDistance", 50.0f );
pp.EditorPreview = OzmiumSceneHelpers.Get( args, "editorPreview", true );
return OzmiumSceneHelpers.Txt( $"Created PostProcessVolume on '{go.Name}'." );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_environment_light ───────────────────────────────────────────────
internal static object CreateEnvironmentLight( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
string sunDir = OzmiumSceneHelpers.Get( args, "sunDirection", "0 -45 0" );
string sunCol = OzmiumSceneHelpers.Get( args, "sunColor", "#FFFFFF" );
string ambCol = OzmiumSceneHelpers.Get( args, "ambientColor", "#808080" );
string skyMat = OzmiumSceneHelpers.Get( args, "skyMaterial", "materials/skybox/skybox_day_01.vmat" );
try
{
// Parse sun direction as "pitch yaw roll"
var parts = sunDir.Split( ' ', StringSplitOptions.RemoveEmptyEntries );
float pitch = 0, yaw = 0, roll = 0;
if ( parts.Length >= 1 ) float.TryParse( parts[0], out pitch );
if ( parts.Length >= 2 ) float.TryParse( parts[1], out yaw );
if ( parts.Length >= 3 ) float.TryParse( parts[2], out roll );
var sunRot = Rotation.From( pitch, yaw, roll );
var sunColor = Color.Parse( sunCol ) ?? default;
var ambColor = Color.Parse( ambCol ) ?? default;
// Create Directional Light (sun)
var sun = scene.CreateObject();
sun.Name = "Sun";
sun.WorldRotation = sunRot;
var dl = sun.Components.Create<DirectionalLight>();
dl.LightColor = sunColor;
// Create Ambient Light
var amb = scene.CreateObject();
amb.Name = "Ambient Light";
amb.Components.Create<AmbientLight>().Color = ambColor;
// Create SkyBox2D
var sky = scene.CreateObject();
sky.Name = "Sky Box";
var skyComp = sky.Components.Create<SkyBox2D>();
var mat = Material.Load( skyMat );
if ( mat != null ) skyComp.SkyMaterial = mat;
skyComp.SkyIndirectLighting = true;
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = "Created environment setup (DirectionalLight + AmbientLight + SkyBox2D).",
sun = new { id = sun.Id.ToString(), position = OzmiumSceneHelpers.V3( sun.WorldPosition ), rotation = OzmiumSceneHelpers.Rot( sun.WorldRotation ) },
ambient = new { id = amb.Id.ToString(), position = OzmiumSceneHelpers.V3( amb.WorldPosition ) },
skybox = new { id = sky.Id.ToString(), position = OzmiumSceneHelpers.V3( sky.WorldPosition ), material = skyMat }
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── Schemas ─────────────────────────────────────────────────────────────
private static Dictionary<string, object> S( string name, string desc, Dictionary<string, object> props, string[] req = null )
{
var schema = new Dictionary<string, object> { ["type"] = "object", ["properties"] = props };
if ( req != null ) schema["required"] = req;
return new Dictionary<string, object> { ["name"] = name, ["description"] = desc, ["inputSchema"] = schema };
}
private static readonly Dictionary<string, object> FogTypes = new()
{
["type"] = "string",
["description"] = "Type of fog volume.",
["enum"] = new[] { "gradient", "volumetric" }
};
internal static Dictionary<string, object> SchemaCreateParticleEffect => S( "create_particle_effect",
"Creates a GO with a ParticleEffect component.",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["maxParticles"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Max particles." },
["lifetime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Particle lifetime in seconds." },
["timeScale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Time scale (0-1)." },
["preWarm"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Pre-warm seconds." }
} );
internal static Dictionary<string, object> SchemaConfigureParticleEffect => S( "configure_particle_effect",
"Sets properties on an existing ParticleEffect.",
new Dictionary<string, object>
{
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Exact name." },
["maxParticles"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Max particles." },
["lifetime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Particle lifetime." },
["timeScale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Time scale." },
["preWarm"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Pre-warm." }
} );
internal static Dictionary<string, object> SchemaCreateFogVolume => S( "create_fog_volume",
"Creates a GO with a fog volume component (GradientFog or VolumetricFogVolume).",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["fogType"] = FogTypes,
["color"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Fog color (GradientFog)." },
["strength"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Fog strength (VolumetricFogVolume)." },
["height"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Fog height (GradientFog)." },
["startDistance"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Start distance (GradientFog)." },
["endDistance"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "End distance (GradientFog)." },
["falloffExponent"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Falloff exponent." },
["verticalFalloffExponent"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Vertical falloff exponent (GradientFog)." }
} );
internal static Dictionary<string, object> SchemaConfigurePostProcessing => S( "configure_post_processing",
"Creates a PostProcessVolume GO for post-processing effects.",
new Dictionary<string, object>
{
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of existing object." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of existing object." },
["priority"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Volume priority." },
["blendWeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Blend weight (0-1)." },
["blendDistance"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Blend distance for soft edges." },
["editorPreview"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Show preview when selected." }
} );
// ── create_beam_effect ────────────────────────────────────────────────────
internal static object CreateBeamEffect( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Beam Effect" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var beam = go.Components.Create<BeamEffect>();
beam.Scale = OzmiumSceneHelpers.Get( args, "scale", 32f );
beam.BeamsPerSecond = OzmiumSceneHelpers.Get( args, "beamsPerSecond", 0f );
beam.MaxBeams = OzmiumSceneHelpers.Get( args, "maxBeams", 1 );
beam.Looped = OzmiumSceneHelpers.Get( args, "looped", false );
if ( args.TryGetProperty( "targetPosition", out var tpEl ) && tpEl.ValueKind == JsonValueKind.Object )
{
beam.TargetPosition = new Vector3(
OzmiumSceneHelpers.Get( tpEl, "x", 0f ),
OzmiumSceneHelpers.Get( tpEl, "y", 0f ),
OzmiumSceneHelpers.Get( tpEl, "z", 0f ) );
}
if ( !string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "targetId", (string)null ) ) ||
!string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "targetName", (string)null ) ) )
{
var targetGo = OzmiumSceneHelpers.FindGo( scene,
OzmiumSceneHelpers.Get( args, "targetId", (string)null ),
OzmiumSceneHelpers.Get( args, "targetName", (string)null ) );
if ( targetGo != null ) beam.TargetGameObject = targetGo;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created BeamEffect '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_verlet_rope ───────────────────────────────────────────────────
internal static object CreateVerletRope( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Verlet Rope" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var rope = go.Components.Create<VerletRope>();
rope.SegmentCount = OzmiumSceneHelpers.Get( args, "segmentCount", 16 );
rope.Slack = OzmiumSceneHelpers.Get( args, "slack", 0f );
rope.Radius = OzmiumSceneHelpers.Get( args, "radius", 1f );
rope.Stiffness = OzmiumSceneHelpers.Get( args, "stiffness", 0.7f );
rope.DampingFactor = OzmiumSceneHelpers.Get( args, "dampingFactor", 0.2f );
if ( !string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "attachmentId", (string)null ) ) ||
!string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "attachmentName", (string)null ) ) )
{
var attachGo = OzmiumSceneHelpers.FindGo( scene,
OzmiumSceneHelpers.Get( args, "attachmentId", (string)null ),
OzmiumSceneHelpers.Get( args, "attachmentName", (string)null ) );
if ( attachGo != null ) rope.Attachment = attachGo;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created VerletRope '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_joint ────────────────────────────────────────────────────────
internal static object CreateJoint( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Joint" );
string jointType = OzmiumSceneHelpers.Get( args, "type", "Fixed" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
Joint joint = jointType.ToLowerInvariant() switch
{
"ball" => go.Components.Create<BallJoint>(),
"hinge" => go.Components.Create<HingeJoint>(),
"slider" => go.Components.Create<SliderJoint>(),
"spring" => go.Components.Create<SpringJoint>(),
"wheel" => go.Components.Create<WheelJoint>(),
_ => go.Components.Create<FixedJoint>()
};
joint.BreakForce = OzmiumSceneHelpers.Get( args, "strength", 1000f );
joint.BreakTorque = OzmiumSceneHelpers.Get( args, "angularStrength", 1000f );
if ( !string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "bodyId", (string)null ) ) ||
!string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "bodyName", (string)null ) ) )
{
var bodyGo = OzmiumSceneHelpers.FindGo( scene,
OzmiumSceneHelpers.Get( args, "bodyId", (string)null ),
OzmiumSceneHelpers.Get( args, "bodyName", (string)null ) );
if ( bodyGo != null ) joint.Body = bodyGo;
}
if ( !string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "anchorBodyId", (string)null ) ) ||
!string.IsNullOrEmpty( OzmiumSceneHelpers.Get( args, "anchorBodyName", (string)null ) ) )
{
var anchorGo = OzmiumSceneHelpers.FindGo( scene,
OzmiumSceneHelpers.Get( args, "anchorBodyId", (string)null ),
OzmiumSceneHelpers.Get( args, "anchorBodyName", (string)null ) );
if ( anchorGo != null ) joint.AnchorBody = anchorGo;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created {jointType}Joint '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
type = jointType
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── Schemas (extensions) ───────────────────────────────────────────────
internal static Dictionary<string, object> SchemaCreateBeamEffect => S( "create_beam_effect",
"Creates a GO with a BeamEffect component for laser/energy effects.",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["scale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Beam scale." },
["targetPosition"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Target position {x,y,z}." },
["targetId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of target GO." },
["targetName"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of target GO." },
["beamsPerSecond"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Beams per second." },
["maxBeams"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Max simultaneous beams." },
["looped"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Loop the beam." }
} );
internal static Dictionary<string, object> SchemaCreateVerletRope => S( "create_verlet_rope",
"Creates a GO with a VerletRope component for rope physics.",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["attachmentId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of attachment GO." },
["attachmentName"]= new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of attachment GO." },
["segmentCount"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Rope segment count." },
["slack"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Rope slack." },
["radius"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Rope radius." },
["stiffness"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Rope stiffness (0-1)." },
["dampingFactor"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Rope damping (0-1)." }
} );
internal static Dictionary<string, object> SchemaCreateJoint => S( "create_joint",
"Creates a physics joint connecting two bodies. Types: Fixed, Ball, Hinge, Slider, Spring, Wheel.",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["type"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Joint type.",
["enum"] = new[] { "Fixed", "Ball", "Hinge", "Slider", "Spring", "Wheel" }
},
["bodyId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of body GO." },
["bodyName"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of body GO." },
["anchorBodyId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of anchor GO." },
["anchorBodyName"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of anchor GO." },
["strength"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Linear strength." },
["angularStrength"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Angular strength." }
} );
internal static Dictionary<string, object> SchemaCreateEnvironmentLight => S( "create_environment_light",
"Creates a complete environment lighting setup: DirectionalLight (sun) + AmbientLight + SkyBox2D.",
new Dictionary<string, object>
{
["sunDirection"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Sun direction as 'pitch yaw roll'." },
["sunColor"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Sun color hex." },
["ambientColor"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Ambient color hex." },
["skyMaterial"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Sky material path." }
} );
// ── create_clutter ───────────────────────────────────────────────────────
internal static object CreateClutter( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Clutter" );
string mode = OzmiumSceneHelpers.Get( args, "mode", "Volume" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var clutter = go.Components.Create<ClutterComponent>();
clutter.Seed = OzmiumSceneHelpers.Get( args, "seed", 0 );
if ( Enum.TryParse<ClutterComponent.ClutterMode>( mode, true, out var cm ) )
clutter.Mode = cm;
if ( args.TryGetProperty( "clutterDefinitionPath", out var cdEl ) && cdEl.ValueKind == JsonValueKind.String )
{
var asset = AssetSystem.FindByPath( cdEl.GetString() );
if ( asset != null )
{
var def = asset.LoadResource<ClutterDefinition>();
if ( def != null ) clutter.Clutter = def;
}
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created Clutter '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
mode = clutter.Mode.ToString()
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_radius_damage ──────────────────────────────────────────────────
internal static object CreateRadiusDamage( JsonElement args )
{
var scene = OzmiumSceneHelpers.ResolveScene();
if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );
float x = OzmiumSceneHelpers.Get( args, "x", 0f );
float y = OzmiumSceneHelpers.Get( args, "y", 0f );
float z = OzmiumSceneHelpers.Get( args, "z", 0f );
string name = OzmiumSceneHelpers.Get( args, "name", "Radius Damage" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var rd = go.Components.Create<RadiusDamage>();
rd.Radius = OzmiumSceneHelpers.Get( args, "radius", 512f );
rd.DamageAmount = OzmiumSceneHelpers.Get( args, "damageAmount", 100f );
rd.PhysicsForceScale = OzmiumSceneHelpers.Get( args, "physicsForceScale", 1.0f );
rd.DamageOnEnabled = OzmiumSceneHelpers.Get( args, "damageOnEnabled", true );
rd.Occlusion = OzmiumSceneHelpers.Get( args, "occlusion", true );
if ( args.TryGetProperty( "damageTags", out var dtEl ) && dtEl.ValueKind == JsonValueKind.String )
{
foreach ( var tag in dtEl.GetString().Split( ',', StringSplitOptions.RemoveEmptyEntries ) )
{
rd.DamageTags.Add( tag.Trim() );
}
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created RadiusDamage '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
radius = rd.Radius,
damage = rd.DamageAmount
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── Effect extension schemas ────────────────────────────────────────────
internal static Dictionary<string, object> SchemaCreateClutter => S( "create_clutter",
"Create a GO with a ClutterComponent for scattering vegetation/objects (grass, rocks, debris).",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["clutterDefinitionPath"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "ClutterDefinition asset path." },
["seed"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Random seed (default 0)." },
["mode"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Generation mode.",
["enum"] = new[] { "Volume", "Infinite" }
}
} );
internal static Dictionary<string, object> SchemaCreateRadiusDamage => S( "create_radius_damage",
"Create a GO with a RadiusDamage component for explosion/area damage effects.",
new Dictionary<string, object>
{
["x"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
["y"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
["z"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
["radius"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage radius (default 512)." },
["damageAmount"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage amount (default 100)." },
["physicsForceScale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Physics force scale (default 1.0)." },
["damageOnEnabled"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Apply damage on enable (default true)." },
["occlusion"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Block damage through walls (default true)." },
["damageTags"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Comma-separated damage tags." }
} );
}