using System; using System.Linq; using UnityEngine; using UnityEditor; using LLM.Editor.Helper; using Newtonsoft.Json.Linq; using JetBrains.Annotations; namespace LLM.Editor.Commands { [Serializable] public class SetComponentValueParams { public string subjectIdentifier; public string componentName; public string memberName; public JToken value; // Changed to JToken to handle both simple values and objects } /// /// The final action command in an analysis workflow. It takes a calculated value /// from the LLM and applies it to a specific property on a component. /// Now supports setting simple values AND object references. /// [UsedImplicitly] public class SetComponentValueCommand : ICommand { [Serializable] private class ObjectReferenceValue { public string identifier; public string childPath; // Optional } private readonly SetComponentValueParams _params; public SetComponentValueCommand(string jsonParams) { _params = jsonParams?.FromJson(); } public CommandOutcome Execute(Data.CommandContext context) { if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName) || string.IsNullOrEmpty(_params.memberName)) { Debug.LogError("[SetComponentValueCommand] Invalid parameters. Subject, component, and member names are required."); return CommandOutcome.Error; } var targetObject = ResolveIdentifier(_params.subjectIdentifier); if (!targetObject) { Debug.LogError($"[SetComponentValueCommand] Could not find subject GameObject '{_params.subjectIdentifier}'."); return CommandOutcome.Error; } var targetComponent = targetObject.GetComponent(_params.componentName); if (!targetComponent) { Debug.LogError($"[SetComponentValueCommand] Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'."); return CommandOutcome.Error; } var serializedObject = new SerializedObject(targetComponent); var serializedProperty = serializedObject.FindProperty(_params.memberName); if (serializedProperty == null) { Debug.LogError($"[SetComponentValueCommand] Could not find serialized property or field '{_params.memberName}' on component '{_params.componentName}'. Make sure it is public or has a [SerializeField] attribute."); return CommandOutcome.Error; } serializedObject.Update(); // Set value based on the property type switch (serializedProperty.propertyType) { case SerializedPropertyType.ObjectReference: if (!SetObjectReferenceValue(serializedProperty, targetObject)) { return CommandOutcome.Error; } break; case SerializedPropertyType.Float: serializedProperty.floatValue = _params.value.Value(); break; case SerializedPropertyType.Integer: serializedProperty.intValue = _params.value.Value(); break; case SerializedPropertyType.Boolean: serializedProperty.boolValue = _params.value.Value(); break; case SerializedPropertyType.String: serializedProperty.stringValue = _params.value.Value(); break; default: Debug.LogError($"[SetComponentValueCommand] The property type '{serializedProperty.propertyType}' is not currently supported for setting values."); return CommandOutcome.Error; } serializedObject.ApplyModifiedProperties(); Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}'."); return CommandOutcome.Success; } private bool SetObjectReferenceValue(SerializedProperty property, GameObject contextObject) { var referenceData = _params.value.ToObject(); if (referenceData == null) { // Handle case where value is null to clear the reference if (_params.value.Type == JTokenType.Null) { property.objectReferenceValue = null; return true; } Debug.LogError("[SetComponentValueCommand] The 'value' for an ObjectReference must be an object with an 'identifier' or be null."); return false; } // The object to assign can be identified relative to the object being modified, // or it can be a completely different object identified by its own GUID/InstanceID. var baseObject = ResolveIdentifier(referenceData.identifier) ?? contextObject; if (!baseObject) { Debug.LogError($"[SetComponentValueCommand] Could not resolve the base identifier '{referenceData.identifier}' for the object reference."); return false; } if (!baseObject && !string.IsNullOrEmpty(referenceData.childPath)) { Debug.LogError($"[SetComponentValueCommand] Cannot use 'childPath' because the base object '{baseObject.name}' is not a GameObject."); return false; } UnityEngine.Object objectToAssign = baseObject; if (baseObject && !string.IsNullOrEmpty(referenceData.childPath)) { var childTransform = baseObject.transform.Find(referenceData.childPath); if (!childTransform) { Debug.LogError($"[SetComponentValueCommand] Could not find child '{referenceData.childPath}' on object '{baseObject.name}'."); return false; } objectToAssign = childTransform.gameObject; // Default to assigning the GameObject // If the property is looking for a specific component type (like Transform), get that instead. var componentType = GetTypeFromProperty(property); if (componentType != null) { var component = childTransform.GetComponent(componentType); if (component) { objectToAssign = component; } } } property.objectReferenceValue = objectToAssign; return true; } private static GameObject ResolveIdentifier(string identifier) { if (string.IsNullOrEmpty(identifier)) return null; if (int.TryParse(identifier, out var instanceId)) { return EditorUtility.InstanceIDToObject(instanceId) as GameObject; } var path = AssetDatabase.GUIDToAssetPath(identifier); return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath(path) : null; } private static Type GetTypeFromProperty(SerializedProperty property) { // Extracts the type from a property string like "PPtr" var typeString = property.type; var startIndex = typeString.IndexOf('<'); var endIndex = typeString.IndexOf('>'); if (startIndex == -1 || endIndex == -1) return null; var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim(); return GetTypeByName(managedTypeName); } private static Type GetTypeByName(string typeName) { return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null); } } }