using System; using System.Linq; using System.Reflection; using UnityEngine; using UnityEditor; using LLM.Editor.Helper; using Newtonsoft.Json.Linq; using JetBrains.Annotations; using Object = UnityEngine.Object; namespace LLM.Editor.Commands { [Serializable] public class SetComponentValueParams { public string subjectIdentifier; public string componentName; public string memberName; public JToken value; } [UsedImplicitly] public class SetComponentValueCommand : ICommand { 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)) { context.ErrorMessage = "Invalid parameters. Subject, component, and member names are required."; return CommandOutcome.Error; } var targetObject = CommandUtility.ResolveIdentifier(context, _params.subjectIdentifier); if (!targetObject) { context.ErrorMessage = $"Could not find subject with logical name '{_params.subjectIdentifier}'."; return CommandOutcome.Error; } if (targetObject is not GameObject go) { context.ErrorMessage = $"The identified subject '{targetObject.name}' is not a GameObject."; return CommandOutcome.Error; } var targetComponent = go.GetComponent(_params.componentName); if (!targetComponent) { context.ErrorMessage = $"Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'."; return CommandOutcome.Error; } // Attempt 1: Use SerializedObject (the preferred Unity way) var serializedObject = new SerializedObject(targetComponent); var serializedProperty = serializedObject.FindProperty(_params.memberName); if (serializedProperty != null) { return SetValueWithSerializedProperty(context, serializedObject, serializedProperty); } // Attempt 2: Fallback to C# Reflection for public properties var propertyInfo = targetComponent.GetType().GetProperty(_params.memberName, BindingFlags.Public | BindingFlags.Instance); if (propertyInfo != null && propertyInfo.CanWrite) { return SetValueWithReflection(context, targetComponent, propertyInfo); } context.ErrorMessage = $"Could not find a writable serialized field or public property named '{_params.memberName}' on component '{_params.componentName}'."; return CommandOutcome.Error; } private CommandOutcome SetValueWithSerializedProperty(Data.CommandContext context, SerializedObject serializedObject, SerializedProperty property) { serializedObject.Update(); try { switch (property.propertyType) { case SerializedPropertyType.ObjectReference: var objToAssign = GetObjectReferenceValue(context, property); if (objToAssign == null && _params.value.Type != JTokenType.Null) return CommandOutcome.Error; property.objectReferenceValue = objToAssign; break; case SerializedPropertyType.Float: property.floatValue = _params.value.Value(); break; case SerializedPropertyType.Integer: property.intValue = _params.value.Value(); break; case SerializedPropertyType.Boolean: property.boolValue = _params.value.Value(); break; case SerializedPropertyType.String: property.stringValue = _params.value.Value(); break; case SerializedPropertyType.Vector3: property.vector3Value = _params.value.ToVector3(); break; case SerializedPropertyType.Quaternion: property.quaternionValue = _params.value.ToQuaternion(); break; default: context.ErrorMessage = $"The property type '{property.propertyType}' is not currently supported for setting values via SerializedObject."; return CommandOutcome.Error; } } catch (Exception e) { context.ErrorMessage = $"Failed to set value for '{_params.memberName}'. Error: {e.Message}"; return CommandOutcome.Error; } serializedObject.ApplyModifiedProperties(); Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}' using SerializedObject."); return CommandOutcome.Success; } private CommandOutcome SetValueWithReflection(Data.CommandContext context, Component target, PropertyInfo propertyInfo) { Undo.RecordObject(target, $"Set {propertyInfo.Name}"); try { object valueToSet; var targetType = propertyInfo.PropertyType; if (targetType == typeof(Vector3)) valueToSet = _params.value.ToVector3(); else if (targetType == typeof(Quaternion)) valueToSet = _params.value.ToQuaternion(); else if (targetType.IsSubclassOf(typeof(Object)) || targetType == typeof(Object)) { valueToSet = GetObjectReferenceValue(context, null); if (valueToSet == null && _params.value.Type != JTokenType.Null) return CommandOutcome.Error; } else { valueToSet = Convert.ChangeType(_params.value.ToObject(targetType), targetType); } propertyInfo.SetValue(target, valueToSet); } catch (Exception e) { context.ErrorMessage = $"Failed to set value for '{_params.memberName}' using Reflection. Error: {e.Message}"; return CommandOutcome.Error; } EditorUtility.SetDirty(target); Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}' using Reflection."); return CommandOutcome.Success; } private Object GetObjectReferenceValue(Data.CommandContext context, SerializedProperty property) { var referenceData = _params.value.ToObject(); if (referenceData == null) { if (_params.value.Type == JTokenType.Null) return null; context.ErrorMessage = "The 'value' for an ObjectReference must be an object with an 'identifier' or be null."; return null; } var baseObject = CommandUtility.ResolveIdentifier(context, referenceData.identifier); if (!baseObject) { context.ErrorMessage = $"Could not resolve the base identifier '{referenceData.identifier}' for the object reference."; return null; } Object objectToAssign = baseObject; if (baseObject is GameObject go && !string.IsNullOrEmpty(referenceData.childPath)) { var childTransform = go.transform.Find(referenceData.childPath); if (!childTransform) { context.ErrorMessage = $"Could not find child '{referenceData.childPath}' on object '{baseObject.name}'."; return null; } objectToAssign = childTransform.gameObject; var componentType = GetTypeFromProperty(property); if (componentType != null && childTransform.TryGetComponent(componentType, out var component)) { objectToAssign = component; } } return objectToAssign; } private static Type GetTypeFromProperty(SerializedProperty property) { if (property == null) return null; 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, false)) .FirstOrDefault(type => type != null); } [Serializable] private class ObjectReferenceValue { public string identifier; public string childPath; } } }