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);
}
}
}