using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using MCPForUnity.Editor.Helpers; using UnityEngine; namespace MCPForUnity.Editor.Tools.Animation { [McpForUnityTool("manage_animation", AutoRegister = false, Group = "animation")] public static class ManageAnimation { private static readonly Dictionary ParamAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "clip_path", "clipPath" }, { "controller_path", "controllerPath" }, { "state_name", "stateName" }, { "from_state", "fromState" }, { "to_state", "toState" }, { "parameter_name", "parameterName" }, { "parameter_type", "parameterType" }, { "property_path", "propertyPath" }, { "default_value", "defaultValue" }, { "has_exit_time", "hasExitTime" }, { "exit_time", "exitTime" }, { "layer_index", "layerIndex" }, { "is_default", "isDefault" }, { "relative_path", "relativePath" }, { "function_name", "functionName" }, { "string_parameter", "stringParameter" }, { "float_parameter", "floatParameter" }, { "int_parameter", "intParameter" }, { "event_index", "eventIndex" }, { "layer_name", "layerName" }, { "blending_mode", "blendingMode" }, { "blend_parameter", "blendParameter" }, { "blend_parameter_x", "blendParameterX" }, { "blend_parameter_y", "blendParameterY" }, { "blend_type", "blendType" }, }; private static JObject NormalizeParams(JObject source) { if (source == null) { return new JObject(); } var normalized = new JObject(); var properties = ExtractProperties(source); if (properties != null) { foreach (var prop in properties.Properties()) { normalized[NormalizeKey(prop.Name, true)] = NormalizeToken(prop.Value); } } foreach (var prop in source.Properties()) { if (string.Equals(prop.Name, "properties", StringComparison.OrdinalIgnoreCase)) { continue; } normalized[NormalizeKey(prop.Name, true)] = NormalizeToken(prop.Value); } return normalized; } private static JObject ExtractProperties(JObject source) { if (source == null) { return null; } if (!source.TryGetValue("properties", StringComparison.OrdinalIgnoreCase, out var token)) { return null; } if (token == null || token.Type == JTokenType.Null) { return null; } if (token is JObject obj) { return obj; } if (token.Type == JTokenType.String) { try { return JToken.Parse(token.ToString()) as JObject; } catch (JsonException ex) { throw new JsonException( $"Failed to parse 'properties' JSON string. Raw value: {token}", ex); } } return null; } private static string NormalizeKey(string key, bool allowAliases) { if (string.IsNullOrEmpty(key)) { return key; } if (string.Equals(key, "action", StringComparison.OrdinalIgnoreCase)) { return "action"; } if (allowAliases && ParamAliases.TryGetValue(key, out var alias)) { return alias; } if (key.IndexOf('_') >= 0) { return StringCaseUtility.ToCamelCase(key); } return key; } private static JToken NormalizeToken(JToken token) { if (token == null) { return null; } if (token is JObject obj) { var normalized = new JObject(); foreach (var prop in obj.Properties()) { normalized[NormalizeKey(prop.Name, false)] = NormalizeToken(prop.Value); } return normalized; } if (token is JArray array) { var normalized = new JArray(); foreach (var item in array) { normalized.Add(NormalizeToken(item)); } return normalized; } return token; } public static object HandleCommand(JObject @params) { JObject normalizedParams = NormalizeParams(@params); string action = normalizedParams["action"]?.ToString(); if (string.IsNullOrEmpty(action)) { return new { success = false, message = "Action is required" }; } try { string actionLower = action.ToLowerInvariant(); if (actionLower.StartsWith("animator_")) { return HandleAnimatorAction(normalizedParams, actionLower.Substring(9)); } if (actionLower.StartsWith("controller_")) { return HandleControllerAction(normalizedParams, actionLower.Substring(11)); } if (actionLower.StartsWith("clip_")) { return HandleClipAction(normalizedParams, actionLower.Substring(5)); } return new { success = false, message = $"Unknown action: {action}. Actions must be prefixed with: animator_, controller_, or clip_" }; } catch (Exception e) { McpLog.Error($"[ManageAnimation] Action '{action}' failed: {e}"); return new ErrorResponse($"Internal error processing action '{action}': {e.Message}"); } } private static object HandleAnimatorAction(JObject @params, string action) { switch (action) { case "get_info": return AnimatorRead.GetInfo(@params); case "get_parameter": return AnimatorRead.GetParameter(@params); case "play": return AnimatorControl.Play(@params); case "crossfade": return AnimatorControl.Crossfade(@params); case "set_parameter": return AnimatorControl.SetParameter(@params); case "set_speed": return AnimatorControl.SetSpeed(@params); case "set_enabled": return AnimatorControl.SetEnabled(@params); default: return new { success = false, message = $"Unknown animator action: {action}. Valid: get_info, get_parameter, play, crossfade, set_parameter, set_speed, set_enabled" }; } } private static object HandleControllerAction(JObject @params, string action) { switch (action) { case "create": return ControllerCreate.Create(@params); case "add_state": return ControllerCreate.AddState(@params); case "add_transition": return ControllerCreate.AddTransition(@params); case "add_parameter": return ControllerCreate.AddParameter(@params); case "get_info": return ControllerCreate.GetInfo(@params); case "assign": return ControllerCreate.AssignToGameObject(@params); case "add_layer": return ControllerLayers.AddLayer(@params); case "remove_layer": return ControllerLayers.RemoveLayer(@params); case "set_layer_weight": return ControllerLayers.SetLayerWeight(@params); case "create_blend_tree_1d": return ControllerBlendTrees.CreateBlendTree1D(@params); case "create_blend_tree_2d": return ControllerBlendTrees.CreateBlendTree2D(@params); case "add_blend_tree_child": return ControllerBlendTrees.AddBlendTreeChild(@params); default: return new { success = false, message = $"Unknown controller action: {action}. Valid: create, add_state, add_transition, add_parameter, get_info, assign, add_layer, remove_layer, set_layer_weight, create_blend_tree_1d, create_blend_tree_2d, add_blend_tree_child" }; } } private static object HandleClipAction(JObject @params, string action) { switch (action) { case "create": return ClipCreate.Create(@params); case "get_info": return ClipCreate.GetInfo(@params); case "add_curve": return ClipCreate.AddCurve(@params); case "set_curve": return ClipCreate.SetCurve(@params); case "set_vector_curve": return ClipCreate.SetVectorCurve(@params); case "create_preset": return ClipPresets.CreatePreset(@params); case "assign": return ClipCreate.Assign(@params); case "add_event": return ClipCreate.AddEvent(@params); case "remove_event": return ClipCreate.RemoveEvent(@params); default: return new { success = false, message = $"Unknown clip action: {action}. Valid: create, get_info, add_curve, set_curve, set_vector_curve, create_preset, assign, add_event, remove_event" }; } } } }