Editor/GameToolHandlers.cs
using System;
using System.Collections.Generic;
using System.Text.Json;
using Sandbox;
namespace SboxMcpServer;
/// <summary>
/// Game MCP tools: create_spawn_point, create_trigger_hurt, create_envmap_probe.
/// </summary>
internal static class GameToolHandlers
{
private static readonly JsonSerializerOptions _json = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
// ── create_spawn_point ───────────────────────────────────────────────
internal static object CreateSpawnPoint( 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", "Spawn Point" );
string color = OzmiumSceneHelpers.Get( args, "color", "#E3510D" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var sp = go.Components.Create<SpawnPoint>();
try { sp.Color = Color.Parse( color ) ?? default; } catch { }
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created SpawnPoint '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_trigger_hurt ────────────────────────────────────────────────
internal static object CreateTriggerHurt( 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 );
GameObject go;
if ( !string.IsNullOrEmpty( id ) || !string.IsNullOrEmpty( name ) )
{
go = OzmiumSceneHelpers.FindGo( scene, id, name );
if ( go == null ) return OzmiumSceneHelpers.Txt( "Object not found. Provide an existing GO with a Collider." );
}
else
{
go = scene.CreateObject();
go.Name = "Trigger Hurt";
}
try
{
// Add a collider if one doesn't exist
if ( go.Components.Get<Collider>() == null )
go.Components.Create<BoxCollider>();
var th = go.Components.GetOrCreate<TriggerHurt>();
th.Damage = OzmiumSceneHelpers.Get( args, "damage", th.Damage );
th.Rate = OzmiumSceneHelpers.Get( args, "rate", th.Rate );
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created TriggerHurt on '{go.Name}'.",
id = go.Id.ToString(),
damage = th.Damage,
rate = th.Rate
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_envmap_probe ──────────────────────────────────────────────
internal static object CreateEnvmapProbe( 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", "Envmap Probe" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var probe = go.Components.Create<EnvmapProbe>();
if ( args.TryGetProperty( "mode", out var modeEl ) && modeEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<EnvmapProbe.EnvmapProbeMode>( modeEl.GetString(), true, out var mode ) )
probe.Mode = mode;
}
if ( args.TryGetProperty( "resolution", out var resEl ) && resEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<EnvmapProbe.CubemapResolution>( resEl.GetString(), true, out var res ) )
probe.Resolution = res;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created EnvmapProbe '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
mode = probe.Mode.ToString(),
resolution = probe.Resolution.ToString()
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_prop ──────────────────────────────────────────────────────────
internal static object CreateProp( 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", "Prop" );
string modelPath = OzmiumSceneHelpers.Get( args, "modelPath", (string)null );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var prop = go.Components.Create<Prop>();
if ( !string.IsNullOrEmpty( modelPath ) )
{
var model = Model.Load( modelPath );
if ( model != null ) prop.Model = model;
}
prop.Health = OzmiumSceneHelpers.Get( args, "health", prop.Health );
prop.IsStatic = OzmiumSceneHelpers.Get( args, "isStatic", prop.IsStatic );
prop.StartAsleep = OzmiumSceneHelpers.Get( args, "startAsleep", prop.StartAsleep );
if ( args.TryGetProperty( "tint", out var tintEl ) && tintEl.ValueKind == JsonValueKind.String )
{
try { prop.Tint = Color.Parse( tintEl.GetString() ) ?? default; } catch { }
}
if ( args.TryGetProperty( "bodyGroups", out var bgEl ) && bgEl.ValueKind == JsonValueKind.String )
{
if ( ulong.TryParse( bgEl.GetString(), out var bg ) ) prop.BodyGroups = bg;
}
if ( args.TryGetProperty( "materialGroup", out var mgEl ) && mgEl.ValueKind == JsonValueKind.String )
{
prop.MaterialGroup = mgEl.GetString();
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created Prop '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
model = prop.Model?.ResourcePath ?? "null",
health = prop.Health,
isStatic = prop.IsStatic
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_decal ────────────────────────────────────────────────────────
internal static object CreateDecal( 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", "Decal" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var decal = go.Components.Create<Decal>();
if ( args.TryGetProperty( "sizeX", out var sxEl ) )
decal.Size = new Vector2( sxEl.GetSingle(), args.TryGetProperty( "sizeY", out var syEl ) ? syEl.GetSingle() : decal.Size.y );
else if ( args.TryGetProperty( "size", out var sizeEl ) && sizeEl.ValueKind == JsonValueKind.Object )
{
decal.Size = new Vector2(
OzmiumSceneHelpers.Get( sizeEl, "x", decal.Size.x ),
OzmiumSceneHelpers.Get( sizeEl, "y", decal.Size.y ) );
}
decal.Depth = OzmiumSceneHelpers.Get( args, "depth", decal.Depth );
decal.Looped = OzmiumSceneHelpers.Get( args, "looped", decal.Looped );
decal.Transient = OzmiumSceneHelpers.Get( args, "transient", decal.Transient );
decal.AttenuationAngle = OzmiumSceneHelpers.Get( args, "attenuationAngle", decal.AttenuationAngle );
decal.SortLayer = OzmiumSceneHelpers.Get( args, "sortLayer", (uint)decal.SortLayer );
if ( args.TryGetProperty( "colorTint", out var ctEl ) && ctEl.ValueKind == JsonValueKind.String )
{
try { decal.ColorTint = Color.Parse( ctEl.GetString() ) ?? default; } catch { }
}
if ( args.TryGetProperty( "lifeTime", out var ltEl ) )
decal.LifeTime = ltEl.GetSingle();
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created Decal '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition )
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_world_panel ──────────────────────────────────────────────────
internal static object CreateWorldPanel( 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", "World Panel" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var wp = go.Components.Create<WorldPanel>();
wp.RenderScale = OzmiumSceneHelpers.Get( args, "renderScale", 1.0f );
wp.LookAtCamera = OzmiumSceneHelpers.Get( args, "lookAtCamera", false );
wp.InteractionRange = OzmiumSceneHelpers.Get( args, "interactionRange", 1000f );
if ( args.TryGetProperty( "panelSize", out var psEl ) && psEl.ValueKind == JsonValueKind.Object )
{
wp.PanelSize = new Vector2(
OzmiumSceneHelpers.Get( psEl, "x", 512f ),
OzmiumSceneHelpers.Get( psEl, "y", 512f ) );
}
if ( args.TryGetProperty( "horizontalAlign", out var haEl ) && haEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<WorldPanel.HAlignment>( haEl.GetString(), true, out var ha ) )
wp.HorizontalAlign = ha;
}
if ( args.TryGetProperty( "verticalAlign", out var vaEl ) && vaEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<WorldPanel.VAlignment>( vaEl.GetString(), true, out var va ) )
wp.VerticalAlign = va;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created WorldPanel '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
panelSize = new { wp.PanelSize.x, wp.PanelSize.y }
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_fire_damage ─────────────────────────────────────────────────
internal static object CreateFireDamage( 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 );
GameObject go;
if ( !string.IsNullOrEmpty( id ) || !string.IsNullOrEmpty( name ) )
{
go = OzmiumSceneHelpers.FindGo( scene, id, name );
if ( go == null ) return OzmiumSceneHelpers.Txt( "Object not found." );
}
else
{
go = scene.CreateObject();
go.Name = "Fire Damage";
}
try
{
var fd = go.Components.GetOrCreate<FireDamage>();
fd.DamagePerSecond = OzmiumSceneHelpers.Get( args, "damagePerSecond", fd.DamagePerSecond );
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created FireDamage on '{go.Name}'.",
id = go.Id.ToString(),
damagePerSecond = fd.DamagePerSecond
}, _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 };
}
internal static Dictionary<string, object> SchemaCreateSpawnPoint => S( "create_spawn_point",
"Create a SpawnPoint component for player spawning.",
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." },
["color"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Spawn point color hex (default '#E3510D')." }
} );
internal static Dictionary<string, object> SchemaCreateTriggerHurt => S( "create_trigger_hurt",
"Create a TriggerHurt volume that deals damage. Requires a Collider on the GO.",
new Dictionary<string, object>
{
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of existing GO." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Exact name of existing GO." },
["damage"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage per tick." },
["rate"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Seconds between damage ticks." }
} );
internal static Dictionary<string, object> SchemaCreateEnvmapProbe => S( "create_envmap_probe",
"Create an EnvmapProbe for environment reflections.",
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." },
["mode"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Probe mode.",
["enum"] = new[] { "Baked", "Realtime", "CustomTexture" }
},
["resolution"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Cubemap resolution.",
["enum"] = new[] { "Small", "Medium", "Large", "Huge" }
}
} );
// ── Game extension schemas ────────────────────────────────────────────
internal static Dictionary<string, object> SchemaCreateProp => S( "create_prop",
"Create a GO with a Prop component — the fundamental S&box game object combining model, physics, and breakable behavior.",
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." },
["modelPath"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path (e.g. 'models/citizen.vmdl')." },
["health"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Prop health (0 = use model default)." },
["tint"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex (default '#FFFFFF')." },
["isStatic"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Static prop (no dynamic physics)." },
["startAsleep"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Start physics asleep." },
["bodyGroups"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Body group mask (ulong)." },
["materialGroup"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Material group name." }
} );
internal static Dictionary<string, object> SchemaCreateDecal => S( "create_decal",
"Create a GO with a Decal component for projecting textures onto surfaces (bullet holes, graffiti, signs).",
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." },
["sizeX"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Decal width (default 1)." },
["sizeY"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Decal height (default 1)." },
["depth"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Projection depth (default 8)." },
["colorTint"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex." },
["lifeTime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Lifetime in seconds (0 = infinite)." },
["looped"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Repeat forever." },
["transient"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Auto-remove when max decals exceeded." },
["attenuationAngle"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Angle fade (0-1, default 1)." },
["sortLayer"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Render sort layer (default 0)." }
} );
internal static Dictionary<string, object> SchemaCreateWorldPanel => S( "create_world_panel",
"Create a GO with a WorldPanel component for 3D in-world UI (signs, HUDs, screens, billboards).",
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." },
["renderScale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Render scale (default 1.0)." },
["lookAtCamera"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Billboard toward camera." },
["panelSize"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Panel size {x,y} (default 512,512)." },
["horizontalAlign"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Horizontal alignment.",
["enum"] = new[] { "Left", "Center", "Right" }
},
["verticalAlign"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Vertical alignment.",
["enum"] = new[] { "Top", "Center", "Bottom" }
},
["interactionRange"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Max interaction distance (default 1000)." }
} );
internal static Dictionary<string, object> SchemaCreateFireDamage => S( "create_fire_damage",
"Create a GO with a FireDamage component for fire/burn damage zones (lava, fire traps).",
new Dictionary<string, object>
{
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of existing GO." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Exact name of existing GO." },
["damagePerSecond"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage per second (default 20)." }
} );
// ── create_hitbox ─────────────────────────────────────────────────────
internal static object CreateHitbox( 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", "Hitbox" );
string shape = OzmiumSceneHelpers.Get( args, "shape", "Sphere" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var hb = go.Components.Create<ManualHitbox>();
if ( Enum.TryParse<ManualHitbox.HitboxShape>( shape, true, out var hs ) )
hb.Shape = hs;
hb.Radius = OzmiumSceneHelpers.Get( args, "radius", 10f );
if ( args.TryGetProperty( "centerA", out var caEl ) && caEl.ValueKind == JsonValueKind.Object )
hb.CenterA = new Vector3(
OzmiumSceneHelpers.Get( caEl, "x", 0f ),
OzmiumSceneHelpers.Get( caEl, "y", 0f ),
OzmiumSceneHelpers.Get( caEl, "z", 0f ) );
if ( args.TryGetProperty( "centerB", out var cbEl ) && cbEl.ValueKind == JsonValueKind.Object )
hb.CenterB = new Vector3(
OzmiumSceneHelpers.Get( cbEl, "x", 0f ),
OzmiumSceneHelpers.Get( cbEl, "y", 32f ),
OzmiumSceneHelpers.Get( cbEl, "z", 0f ) );
if ( args.TryGetProperty( "hitboxTags", out var htEl ) && htEl.ValueKind == JsonValueKind.String )
{
foreach ( var tag in htEl.GetString().Split( ',', StringSplitOptions.RemoveEmptyEntries ) )
hb.HitboxTags.Add( tag.Trim() );
}
if ( args.TryGetProperty( "targetId", out var tidEl ) && tidEl.ValueKind == JsonValueKind.String )
{
var targetGo = OzmiumSceneHelpers.FindGo( scene, tidEl.GetString(), null );
if ( targetGo != null ) hb.Target = targetGo;
}
else if ( args.TryGetProperty( "targetName", out var tnEl ) && tnEl.ValueKind == JsonValueKind.String )
{
var targetGo = OzmiumSceneHelpers.FindGo( scene, null, tnEl.GetString() );
if ( targetGo != null ) hb.Target = targetGo;
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created ManualHitbox '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
shape = hb.Shape.ToString(),
radius = hb.Radius
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_chair ──────────────────────────────────────────────────────
internal static object CreateChair( 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", "Chair" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var chair = go.Components.Create<BaseChair>();
if ( args.TryGetProperty( "sitPose", out var spEl ) && spEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<BaseChair.AnimatorSitPose>( spEl.GetString(), true, out var pose ) )
chair.SitPose = pose;
}
chair.SitHeight = OzmiumSceneHelpers.Get( args, "sitHeight", 0f );
chair.TooltipTitle = OzmiumSceneHelpers.Get( args, "tooltipTitle", "Sit" );
chair.TooltipIcon = OzmiumSceneHelpers.Get( args, "tooltipIcon", "airline_seat_recline_normal" );
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created BaseChair '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
sitPose = chair.SitPose.ToString()
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_dresser ────────────────────────────────────────────────────
internal static object CreateDresser( 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 );
GameObject go;
if ( !string.IsNullOrEmpty( id ) || !string.IsNullOrEmpty( name ) )
{
go = OzmiumSceneHelpers.FindGo( scene, id, name );
if ( go == null ) return OzmiumSceneHelpers.Txt( "Object not found." );
}
else
{
go = scene.CreateObject();
go.Name = "Dresser";
}
try
{
var dresser = go.Components.GetOrCreate<Dresser>();
if ( args.TryGetProperty( "source", out var srcEl ) && srcEl.ValueKind == JsonValueKind.String )
{
if ( Enum.TryParse<Dresser.ClothingSource>( srcEl.GetString(), true, out var src ) )
dresser.Source = src;
}
dresser.ManualHeight = OzmiumSceneHelpers.Get( args, "manualHeight", 0.5f );
dresser.ManualTint = OzmiumSceneHelpers.Get( args, "manualTint", 0.5f );
dresser.ManualAge = OzmiumSceneHelpers.Get( args, "manualAge", 0.5f );
dresser.ApplyHeightScale = OzmiumSceneHelpers.Get( args, "applyHeightScale", true );
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created Dresser on '{go.Name}'.",
id = go.Id.ToString(),
source = dresser.Source.ToString()
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── create_gib ────────────────────────────────────────────────────────
internal static object CreateGib( 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", "Gib" );
try
{
var go = scene.CreateObject();
go.Name = name;
go.WorldPosition = new Vector3( x, y, z );
var gib = go.Components.Create<Gib>();
if ( args.TryGetProperty( "modelPath", out var mpEl ) && mpEl.ValueKind == JsonValueKind.String )
{
var model = Model.Load( mpEl.GetString() );
if ( model != null ) gib.Model = model;
}
gib.FadeTime = OzmiumSceneHelpers.Get( args, "fadeTime", 5f );
gib.IsStatic = OzmiumSceneHelpers.Get( args, "isStatic", false );
if ( args.TryGetProperty( "tint", out var tintEl ) && tintEl.ValueKind == JsonValueKind.String )
{
try { gib.Tint = Color.Parse( tintEl.GetString() ) ?? default; } catch { }
}
return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
{
message = $"Created Gib '{go.Name}'.",
id = go.Id.ToString(),
position = OzmiumSceneHelpers.V3( go.WorldPosition ),
fadeTime = gib.FadeTime,
model = gib.Model?.ResourcePath ?? "null"
}, _json ) );
}
catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
}
// ── Game extension schemas (batch 2) ─────────────────────────────────
internal static Dictionary<string, object> SchemaCreateHitbox => S( "create_hitbox",
"Create a GO with a ManualHitbox for custom damage zones on NPCs/props. Supports sphere, capsule, box, and cylinder shapes.",
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." },
["shape"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Hitbox shape.",
["enum"] = new[] { "Sphere", "Capsule", "Box", "Cylinder" }
},
["radius"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Hitbox radius (default 10)." },
["centerA"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Center A point {x,y,z} (default 0,0,0)." },
["centerB"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Center B point {x,y,z} (default 0,32,0)." },
["hitboxTags"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Comma-separated hitbox tags." },
["targetId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of the target GameObject." },
["targetName"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of the target GameObject." }
} );
internal static Dictionary<string, object> SchemaCreateChair => S( "create_chair",
"Create a GO with a BaseChair component for sittable furniture. Players can interact to sit down.",
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." },
["sitPose"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Sit pose animation.",
["enum"] = new[] { "Standing", "Chair", "ChairForward", "ChairCrossed", "KneelingOpen", "Kneeling", "Ground", "GroundCrossed" }
},
["sitHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Sit height offset (default 0)." },
["tooltipTitle"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Tooltip title (default 'Sit')." },
["tooltipIcon"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Tooltip icon name (default 'airline_seat_recline_normal')." }
} );
internal static Dictionary<string, object> SchemaCreateDresser => S( "create_dresser",
"Add a Dresser component to a GO for NPC clothing/appearance setup. Handles clothing, skin color, and height for citizen models. Critical for NPC creation.",
new Dictionary<string, object>
{
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of existing GO." },
["name"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Exact name of existing GO." },
["source"] = new Dictionary<string, object>
{
["type"] = "string", ["description"] = "Clothing source.",
["enum"] = new[] { "Manual", "LocalUser", "OwnerConnection" }
},
["manualHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual height (0-1, default 0.5)." },
["manualTint"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual skin tint (0-1, default 0.5)." },
["manualAge"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual age (0-1, default 0.5)." },
["applyHeightScale"]= new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Apply height scale (default true)." }
} );
internal static Dictionary<string, object> SchemaCreateGib => S( "create_gib",
"Create a GO with a Gib component — a prop that fades and self-destructs after a delay. Essential for death effects, explosions, breakable objects.",
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." },
["modelPath"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path (e.g. 'models/gibs/wood_gib01.vmdl')." },
["tint"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex." },
["fadeTime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Seconds before auto-fade (default 5)." },
["isStatic"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Static gib (default false)." }
} );
// ── create_game_entity (Omnibus) ────────────────────────────────────────
internal static object CreateGameEntity( JsonElement args )
{
string entityType = OzmiumSceneHelpers.Get( args, "entityType", "" );
return entityType switch
{
"SpawnPoint" => CreateSpawnPoint( args ),
"TriggerHurt" => CreateTriggerHurt( args ),
"EnvmapProbe" => CreateEnvmapProbe( args ),
"Prop" => CreateProp( args ),
"Decal" => CreateDecal( args ),
"WorldPanel" => CreateWorldPanel( args ),
"FireDamage" => CreateFireDamage( args ),
"ManualHitbox" => CreateHitbox( args ),
"BaseChair" => CreateChair( args ),
"Dresser" => CreateDresser( args ),
"Gib" => CreateGib( args ),
_ => OzmiumSceneHelpers.Txt( $"Unknown entityType: {entityType}" )
};
}
internal static Dictionary<string, object> SchemaCreateGameEntity => S( "create_game_entity",
"Create a specific game entity (SpawnPoint, TriggerHurt, EnvmapProbe, Prop, Decal, WorldPanel, FireDamage, ManualHitbox, BaseChair, Dresser, Gib).",
new Dictionary<string, object>
{
["entityType"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Type of entity to create.", ["enum"] = new[] { "SpawnPoint", "TriggerHurt", "EnvmapProbe", "Prop", "Decal", "WorldPanel", "FireDamage", "ManualHitbox", "BaseChair", "Dresser", "Gib" } },
["id"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of existing GO (for some components)." },
["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." },
["modelPath"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path." },
["tint"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex." },
["colorTint"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Decal color tint hex." },
["health"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Prop health." },
["isStatic"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Static prop/gib." },
["startAsleep"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Start physics asleep." },
["damage"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage per tick (TriggerHurt)." },
["rate"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Seconds between damage ticks (TriggerHurt)." },
["damagePerSecond"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Damage per second (FireDamage)." },
["mode"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Probe mode." },
["resolution"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Cubemap resolution." },
["sizeX"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Decal width." },
["sizeY"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Decal height." },
["depth"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Projection depth." },
["lifeTime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Lifetime in seconds." },
["fadeTime"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Seconds before auto-fade (Gib)." },
["looped"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Repeat forever." },
["transient"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Auto-remove when max decals exceeded." },
["attenuationAngle"]= new Dictionary<string, object> { ["type"] = "number", ["description"] = "Angle fade." },
["sortLayer"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Render sort layer." },
["renderScale"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Render scale (WorldPanel)." },
["lookAtCamera"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Billboard toward camera (WorldPanel)." },
["panelSize"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Panel size {x,y}." },
["horizontalAlign"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Horizontal alignment." },
["verticalAlign"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Vertical alignment." },
["interactionRange"]= new Dictionary<string, object> { ["type"] = "number", ["description"] = "Max interaction distance." },
["shape"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Hitbox shape." },
["radius"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Hitbox radius." },
["centerA"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Center A point {x,y,z}." },
["centerB"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Center B point {x,y,z}." },
["hitboxTags"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Comma-separated hitbox tags." },
["targetId"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID of the target GameObject." },
["targetName"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name of the target GameObject." },
["sitPose"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Sit pose animation." },
["sitHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Sit height offset." },
["tooltipTitle"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Tooltip title." },
["tooltipIcon"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Tooltip icon name." },
["source"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Clothing source." },
["manualHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual height." },
["manualTint"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual skin tint." },
["manualAge"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Manual age." },
["applyHeightScale"]= new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Apply height scale." }
},
new[] { "entityType" } );
}