using EasyTalk.Controller; using EasyTalk.Nodes.Core; using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace EasyTalk.Nodes.Logic { /// /// A node for triggering script methods. /// [Serializable] public class TriggerScriptNode : Node, DialogueFlowNode, FunctionalNode { /// /// The name of the class to trigger a method on. /// [SerializeField] private string triggeredClassName; /// /// The name of the method to trigger. /// [SerializeField] private string triggeredMethodName; /// /// A string representation of the method signature for the triggered method. /// [SerializeField] private string methodSignature; /// /// The type names for each parameter required for the triggered method. /// [SerializeField] private string[] parameterTypes; /// /// The values to pass into the triggered method. /// [SerializeField] private string[] parameterValues; /// /// The mode or type of method to trigger. /// [SerializeField] private TriggerFilterType triggerType = TriggerFilterType.SELF; /// /// The name of the GameObject or the tag of the GameObject to trigger the method on. /// [SerializeField] private string objectTagOrName; /// /// Creates a new TriggerScriptNode. /// public TriggerScriptNode() { this.name = "TRIGGER"; this.nodeType = NodeType.TRIGGER; } /// /// Gets or sets the class name to trigger a method on. /// public string TriggeredClassName { get { return this.triggeredClassName; } set { this.triggeredClassName = value; } } /// /// Gets or sets the name of the method to trigger. /// public string TriggeredMethodName { get { return this.triggeredMethodName; } set { this.triggeredMethodName = value; } } /// /// Gets or sets the string representation of the method signature for the method to be triggered. /// public string MethodSignature { get { return this.methodSignature; } set { this.methodSignature = value; } } /// /// Gets or sets the type names for the parameters passed into the method to be triggered. /// public string[] ParameterTypes { get { return this.parameterTypes; } set { this.parameterTypes = value; } } /// /// Gets or sets the parameter values to pass into the method to be triggered. /// public string[] ParameterValues { get { return this.parameterValues; } set { this.parameterValues = value; } } /// /// Gets or sets the mode or type of method to trigger (from a string equivalent to a TriggerFilterType toString() value). /// public string TriggerTypeString { get { return this.triggerType.ToString(); } set { this.triggerType = Enum.Parse(value); } } /// /// Gets or sets the mode or type of method to trigger. /// public TriggerFilterType TriggerType { get { return this.triggerType; } set { this.triggerType = value; } } /// /// Gets or sets the name or tag of the GameObject to use when triggering the method. /// public string ObjectTagOrName { get { return this.objectTagOrName; } set { this.objectTagOrName = value;} } /// /// Triggers the script as defined by the node. /// /// The node handler currently processing dialogue. /// A mapping of node IDs and connection IDs to the corresponding output values attributed to those IDs. /// The GameObject which is currently running the dialogue. /// Returns the result of the method. public object TriggerScript(NodeHandler nodeHandler, Dictionary nodeOutputValues, GameObject convoOwner = null) { Type checkedClassType = Type.GetType(this.TriggeredClassName); if (checkedClassType == null) { bool foundClass = false; foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (Type type in assembly.GetTypes()) { if (type.ToString().Equals(this.triggeredClassName)) { checkedClassType = type; foundClass = true; break; } } if (foundClass) { break; } } } if(checkedClassType == null) { Debug.LogError("Unable to find class '" + this.triggeredClassName + "' in loaded assemblies. Cannot call from trigger script node."); return null; } if (this.triggerType == TriggerFilterType.STATIC) { //Execute static method Type[] paramTypes = GetParameterTypes(); MethodInfo method = checkedClassType.GetMethod(this.TriggeredMethodName, paramTypes); object[] parameters = DetermineParameterValues(nodeHandler, nodeOutputValues, paramTypes); object returnValue = method.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, parameters, null); NodeConnection valueOutput = Outputs[0]; nodeOutputValues.TryAdd(valueOutput.ID, returnValue); return returnValue; } else { UnityEngine.Object[] unityObjects = new UnityEngine.Object[1]; if (this.triggerType == TriggerFilterType.SELF) { unityObjects[0] = convoOwner.gameObject.GetComponentInChildren(checkedClassType); } else if (this.triggerType == TriggerFilterType.FIRST) { #if UNITY_2022_3_OR_NEWER unityObjects[0] = GameObject.FindFirstObjectByType(checkedClassType, FindObjectsInactive.Exclude); #else unityObjects[0] = GameObject.FindObjectOfType(checkedClassType); #endif } else if (this.triggerType == TriggerFilterType.NAME) { unityObjects[0] = GameObject.Find(objectTagOrName).GetComponentInChildren(checkedClassType); } else if (this.triggerType == TriggerFilterType.TAG) { unityObjects[0] = GameObject.FindGameObjectWithTag(objectTagOrName).GetComponentInChildren(checkedClassType); } else if (this.triggerType == TriggerFilterType.ALL) { #if UNITY_2022_3_OR_NEWER unityObjects = GameObject.FindObjectsByType(checkedClassType, FindObjectsSortMode.None); #else unityObjects = GameObject.FindObjectsOfType(checkedClassType); #endif } if (unityObjects[0] != null) { Type[] paramTypes = GetParameterTypes(); MethodInfo method = checkedClassType.GetMethod(this.TriggeredMethodName, paramTypes); object[] parameters = DetermineParameterValues(nodeHandler, nodeOutputValues, paramTypes); NodeConnection valueOutput = Outputs[0]; object returnValue = null; foreach (UnityEngine.Object unityObj in unityObjects) { returnValue = method.Invoke(unityObj, parameters); nodeOutputValues.TryAdd(valueOutput.ID, returnValue); } return returnValue; } return null; } } /// /// Determines the values for each parameter to pass to the method to be called. /// /// The node handler currently processing dialogue. /// A mapping of node IDs and connection IDs to the corresponding output values attributed to those IDs. /// The GameObject which is currently running the dialogue. /// Returns an array of values to use for each parameter. private object[] DetermineParameterValues(NodeHandler nodeHandler, Dictionary nodeOutputValues, Type[] paramTypes) { object[] parameters = null; if (this.ParameterValues != null) { if (paramTypes.Length > 0) { parameters = new object[paramTypes.Length]; for (int i = 0; i < paramTypes.Length; i++) { object paramValue = null; if (Inputs[i+1].AttachedIDs.Count > 0) { int incomingId = Inputs[i + 1].AttachedIDs[0]; paramValue = ConvertToType(nodeOutputValues[incomingId], paramTypes[i]); } else { string paramValueString = nodeHandler.ReplaceVariablesInString(this.ParameterValues[i]); paramValue = ConvertToType(paramValueString, paramTypes[i]); } parameters[i] = paramValue; } } } return parameters; } /// /// Returns the actual Type for the simplified type string provided. /// /// A simplified type name. /// The Type attributed to the simple type name specified. public static Type GetTypeForString(string simpleTypeName) { if (simpleTypeName.Equals("string")) { return typeof(string); } else if (simpleTypeName.Equals("byte")) { return typeof(byte); } else if (simpleTypeName.Equals("short")) { return typeof(short); } else if (simpleTypeName.Equals("int")) { return typeof(int); } else if (simpleTypeName.Equals("long")) { return typeof(long); } else if (simpleTypeName.Equals("float")) { return typeof(float); } else if (simpleTypeName.Equals("double")) { return typeof(double); } else if (simpleTypeName.Equals("decimal")) { return typeof(decimal); } else if (simpleTypeName.Equals("bool")) { return typeof(bool); } else if (simpleTypeName.Equals("object")) { return typeof(object); } return typeof(object); } /// /// Attempts to convert the object value provided to the specified Type. /// /// The object to convert. /// The Type to convert the provided object to. /// The converted value. public static object ConvertToType(object obj, Type type) { if (obj.GetType() == type) { return obj; } if (type == typeof(float)) { return Convert.ToSingle(obj); } else if (type == typeof(double)) { return Convert.ToDouble(obj); } else if (type == typeof(decimal)) { return Convert.ToDecimal(obj); } else if (type == typeof(byte)) { return Convert.ToByte(obj); } else if (type == typeof(short)) { return Convert.ToInt16(obj); } else if (type == typeof(int)) { return Convert.ToInt32(obj); } else if (type == typeof(long)) { return Convert.ToInt64(obj); } else if (type == typeof(bool)) { return Convert.ToBoolean(obj); } else if (type == typeof(string)) { string value = ""; if (obj != null) { value = obj.ToString(); } return value; } return null; } /// /// Returns an array of Types to use for the parameters of the method to be called. /// /// An array of the parameter Types to pass into the called method. private Type[] GetParameterTypes() { if (parameterTypes.Length > 0) { Type[] types = new Type[parameterTypes.Length]; for (int i = 0; i < parameterTypes.Length; i++) { types[i] = GetTypeForString(parameterTypes[i]); } return types; } return new Type[0]; } /// public NodeConnection GetFlowInput() { return FindFlowInputs()[0]; } /// public NodeConnection GetFlowOutput() { return FindFlowOutputs()[0]; } /// public bool DetermineAndStoreValue(NodeHandler nodeHandler, Dictionary nodeValues, GameObject convoOwner = null) { TriggerScript(nodeHandler, nodeValues, convoOwner); return true; } /// public bool HasDependencies() { return FindDependencyOutputIDs().Count > 0; } /// public List GetDependencyOutputIDs() { return FindDependencyOutputIDs(); } } /// /// An enumeration defining types of method calls to use on trigger script nodes. /// public enum TriggerFilterType { SELF, ALL, FIRST, TAG, NAME, STATIC } }