using System; using System.Collections.Generic; 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 MemberValue { public string memberName; public JToken value; } [Serializable] public class SetComponentValueParams { public string subjectIdentifier; public string componentName; public List members; } [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)) { context.ErrorMessage = "Invalid parameters. Subject and component names are required."; return CommandOutcome.Error; } if (_params.members == null || !_params.members.Any()) { context.ErrorMessage = "No members provided to set."; 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 = _params.componentName == "UnityEngine.Transform" ? go.transform : go.GetComponent(GetTypeByName(_params.componentName)); if (!targetComponent) { context.ErrorMessage = $"Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'."; return CommandOutcome.Error; } foreach (var member in _params.members) { var outcome = SetMemberValue(context, targetComponent, member.memberName, member.value); if (outcome == CommandOutcome.Error) { // The error message will be set by SetMemberValue return CommandOutcome.Error; } } return CommandOutcome.Success; } private CommandOutcome SetMemberValue(Data.CommandContext context, Component targetComponent, string memberName, JToken value) { var serializedObject = new SerializedObject(targetComponent); var serializedProperty = serializedObject.FindProperty(memberName); if (serializedProperty != null) { return SetValueWithSerializedProperty(context, serializedObject, serializedProperty, value); } var propertyInfo = targetComponent.GetType().GetProperty(memberName, BindingFlags.Public | BindingFlags.Instance); if (propertyInfo != null && propertyInfo.CanWrite) { return SetValueWithReflection(context, targetComponent, propertyInfo, value); } context.ErrorMessage = $"Could not find a writable serialized field or public property named '{memberName}' on component '{targetComponent.GetType().Name}'."; return CommandOutcome.Error; } private CommandOutcome SetValueWithSerializedProperty(Data.CommandContext context, SerializedObject serializedObject, SerializedProperty property, JToken value) { serializedObject.Update(); try { switch (property.propertyType) { case SerializedPropertyType.ObjectReference: var objToAssign = GetObjectReferenceValue(context, property, value); if (objToAssign == null && value.Type != JTokenType.Null) return CommandOutcome.Error; property.objectReferenceValue = objToAssign; break; case SerializedPropertyType.Float: property.floatValue = value.Value(); break; case SerializedPropertyType.Integer: property.intValue = value.Value(); break; case SerializedPropertyType.Boolean: property.boolValue = value.Value(); break; case SerializedPropertyType.String: property.stringValue = value.Value(); break; case SerializedPropertyType.Vector3: property.vector3Value = value.ToVector3(); break; case SerializedPropertyType.Quaternion: property.quaternionValue = 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 '{property.name}'. Error: {e.Message}"; return CommandOutcome.Error; } serializedObject.ApplyModifiedProperties(); Debug.Log($"[SetComponentValueCommand] Successfully set '{property.name}' on '{serializedObject.targetObject.GetType().Name}' using SerializedObject."); return CommandOutcome.Success; } private CommandOutcome SetValueWithReflection(Data.CommandContext context, Component target, PropertyInfo propertyInfo, JToken value) { Undo.RecordObject(target, $"Set {propertyInfo.Name}"); try { object valueToSet; var targetType = propertyInfo.PropertyType; if (targetType == typeof(Vector3)) valueToSet = value.ToVector3(); else if (targetType == typeof(Quaternion)) valueToSet = value.ToQuaternion(); else if (targetType.IsSubclassOf(typeof(Object)) || targetType == typeof(Object)) { valueToSet = GetObjectReferenceValue(context, null, value); if (valueToSet == null && value.Type != JTokenType.Null) return CommandOutcome.Error; } else { valueToSet = Convert.ChangeType(value.ToObject(targetType), targetType); } propertyInfo.SetValue(target, valueToSet); } catch (Exception e) { context.ErrorMessage = $"Failed to set value for '{propertyInfo.Name}' using Reflection. Error: {e.Message}"; return CommandOutcome.Error; } EditorUtility.SetDirty(target); Debug.Log($"[SetComponentValueCommand] Successfully set '{propertyInfo.Name}' on '{target.GetType().Name}' using Reflection."); return CommandOutcome.Success; } private Object GetObjectReferenceValue(Data.CommandContext context, SerializedProperty property, JToken value) { var referenceData = value.ToObject(); if (referenceData == null) { if (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; } } }