// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine.SceneManagement; using System.Linq; using System.Text; namespace PixelCrushers.DialogueSystem { /// /// A static class of general purpose functions used by the Dialogue System. /// public static class Tools { public static void DeprecationWarning(MonoBehaviour mb, string extraInfo = null) { #if !SUPPRESS_DEPRECATION_WARNINGS if (mb == null) return; Debug.LogWarning("Dialogue System: " + mb.GetType().Name + " is deprecated and will be removed in the next version. " + extraInfo + "\nTo supress this message, add the scripting define symbol SUPPRESS_DEPRECATION_WARNINGS", mb); #endif } /// /// Determines if a GameObject reference is a non-instantiated prefab or a scene object. /// If `go` is `null`, active in the scene, or its parent is active in the scene, it's /// considered a scene object. Otherwise this method searches all scene objects for /// matches. If it doesn't find any matches, this is a prefab. /// /// true if a prefab; otherwise, false. /// GameObject. public static bool IsPrefab(GameObject go) { if (go == null) return false; if (go.activeInHierarchy) return false; if ((go.transform.parent != null) && go.transform.parent.gameObject.activeSelf) return false; var list = GameObjectUtility.FindObjectsByType(); for (int i = 0; i < list.Length; i++) { if (list[i] == go) return false; } return true; } /// /// Utility function to convert a hex string to byte value. /// /// /// The byte value of the hex string. /// /// /// The hex string (e.g., "f0"). /// public static byte HexToByte(string hex) { return byte.Parse(hex, System.Globalization.NumberStyles.HexNumber); } /// /// Determines whether an object is a numeric type. /// /// /// true if the object is a numeric type; otherwise, false. /// /// /// The object to check. /// public static bool IsNumber(object o) { return (o is int) || (o is float) || (o is double); } /// /// Converts a string to an int. /// /// /// The int, or 0 if the string can't be parsed to an int. /// /// /// The string. /// public static int StringToInt(string s) { return SafeConvert.ToInt(s); } /// /// Converts a string to a float, culture invariant (i.e., uses '.' for decimal point). /// /// /// The float, or 0 if the string can't be parsed to a float. /// /// /// The string. /// public static float StringToFloat(string s) { return SafeConvert.ToFloat(s); } /// /// Converts a string to a bool. /// /// /// The bool, or false if the string can't be parsed to a bool. /// /// /// The string. /// public static bool StringToBool(string s) { return (string.Compare(s, "True", System.StringComparison.OrdinalIgnoreCase) == 0); } /// /// Determines if a string is null, empty or whitespace. /// /// true if the string null, empty or whitespace; otherwise, false. /// The string to check. public static bool IsStringNullOrEmptyOrWhitespace(string s) { return string.IsNullOrEmpty(s) || string.IsNullOrEmpty(s.Trim()); } /// /// Returns the remainder of a string after all forward slashes, or /// the enter string if it doesn't contain any forward slashes. /// public static string GetAllAfterSlashes(string s) { if (string.IsNullOrEmpty(s) || !s.Contains("/")) return s; var pos = s.LastIndexOf("/") + 1; return (0 < pos && pos < s.Length) ? s.Substring(pos) : s; } /// /// Gets the name of the object, or null if the object is null. /// /// /// The object name. /// /// /// The object. /// public static string GetObjectName(UnityEngine.Object o) { return (o != null) ? o.name : "null"; } /// /// Gets the name of a component's GameObject. /// /// The game object name. /// A component public static string GetGameObjectName(Component c) { return (c == null) ? string.Empty : c.name; } /// /// Gets the full name of a GameObject, following the hierarchy down from the root. /// /// The full name. /// A GameObject. public static string GetFullName(GameObject go) { string fullName = string.Empty; if (go != null) { fullName = go.name; Transform t = go.transform.parent; while (t != null) { fullName = t.name + '.' + fullName; t = t.parent; } } return fullName; } /// /// Returns the first non-null argument. This function replaces C#'s null-coalescing /// operator (??), which doesn't work with component properties because, under the hood, /// they're always non-null. /// /// /// List of elements to select from. /// public static Transform Select(params Transform[] args) { for (int i = 0; i < args.Length; i++) { if (args[i] != null) { return args[i]; } } return null; } /// /// Returns the first non-null argument. This function replaces C#'s null-coalescing /// operator (??), which doesn't work with component properties because, under the hood, /// they're always non-null. /// /// /// List of elements to select from. /// public static MonoBehaviour Select(params MonoBehaviour[] args) { for (int i = 0; i < args.Length; i++) { if (args[i] != null) { return args[i]; } } return null; } /// /// Sends a message to all GameObjects in the scene. /// /// Message. public static void SendMessageToEveryone(string message) { GameObject[] gameObjects = GameObjectUtility.FindObjectsByType() as GameObject[]; for (int i = 0; i < gameObjects.Length; i++) { var go = gameObjects[i]; go.SendMessage(message, SendMessageOptions.DontRequireReceiver); } } /// /// Sends a message to all GameObjects in the scene. /// /// Message. /// Argument. public static void SendMessageToEveryone(string message, string arg) { GameObject[] gameObjects = GameObjectUtility.FindObjectsByType() as GameObject[]; for (int i = 0; i < gameObjects.Length; i++) { var go = gameObjects[i]; go.SendMessage(message, arg, SendMessageOptions.DontRequireReceiver); } } /// /// Sends a message to all GameObjects in the scene in batches. /// /// Message. /// Number of GameObjects to handle each frame. public static IEnumerator SendMessageToEveryoneAsync(string message, int gameObjectsPerFrame) { GameObject[] gameObjects = GameObjectUtility.FindObjectsByType() as GameObject[]; int count = 0; for (int i = 0; i < gameObjects.Length; i++) { var go = gameObjects[i]; go.SendMessage(message, SendMessageOptions.DontRequireReceiver); count++; if (count >= gameObjectsPerFrame) { count = 0; yield return null; } } } /// /// Sets the component's game object active or inactive. /// /// Component. /// The value to set. public static void SetGameObjectActive(Component component, bool value) { if (component != null) component.gameObject.SetActive(value); } /// /// Sets a game object active or inactive. /// /// GameObject. /// The value to set. public static void SetGameObjectActive(GameObject gameObject, bool value) { if (gameObject != null) gameObject.SetActive(value); } /// /// Checks if a float value is approximately zero (accounting for rounding error). /// /// /// true if the value is approximately zero. /// /// /// The float to check. /// public static bool ApproximatelyZero(float x) { return (x < 0.0001f); } /// /// Converts a web color string to a Color. /// /// /// The color. /// /// /// A web RGB-format color code of the format "\#rrggbb", where rr, gg, and bb are /// hexadecimal values (e.g., \#ff0000 for red). /// public static Color WebColor(string colorCode) { byte r = (colorCode.Length > 2) ? Tools.HexToByte(colorCode.Substring(1, 2)) : (byte)0; byte g = (colorCode.Length > 4) ? Tools.HexToByte(colorCode.Substring(3, 2)) : (byte)0; byte b = (colorCode.Length > 6) ? Tools.HexToByte(colorCode.Substring(5, 2)) : (byte)0; return new Color32(r, g, b, 255); } /// /// Converts a color of to a web color string. /// /// /// The web RGB-format color code of the format "\#rrggbb". /// /// /// Color. /// public static string ToWebColor(Color color) { return string.Format("#{0:x2}{1:x2}{2:x2}{3:x2}", (int)(255 * color.r), (int)(255 * color.g), (int)(255 * color.b), (int)(255 * color.a)); } public static string StripRichTextCodes(string s) { if (!s.Contains("<")) return s; return Regex.Replace(s, @"||||

|

|<\\/p>||", string.Empty); } public static Regex TextMeshProTagsRegex = new Regex(@"<[Bb]>||<[Ii]>|||||<#\w+>|" + @"||]+>|||||" + @"||||||" + @"||||||" + @"|||||||" + @"||||||<[Ss]>||<[Uu]>||" + @"||||

|

|<\\/p>||]+>|]+>|" + @"]+>||]+>||]+>|" + @"|"); public static string StripTextMeshProTags(string s) { if (!s.Contains('<')) return s; return TextMeshProTagsRegex.Replace(s, string.Empty); } public static string StripRPGMakerCodes(string s) { if (!s.Contains('\\')) return s; return Regex.Replace(s, @"\\\.|\\,|\\\>|\\\<|\\\^", string.Empty); } /// /// Determines whether an animation clip is in the animation list. /// /// /// true if the clip is in the animation list. /// /// /// The legacy Animation component. /// /// /// The clip name. /// public static bool IsClipInAnimations(Animation animation, string clipName) { if (animation != null) { foreach (AnimationState state in animation) { if (string.Equals(state.name, clipName)) return true; } } return false; } /// /// Finds an in-scene GameObject even if it's inactive. /// /// Name of the GameObject. /// The GameObject, or null if not found. public static GameObject GameObjectHardFind(string goName) { return GameObjectUtility.GameObjectHardFind(goName); } /// /// Finds an in-scene GameObject matching a name and tag even if it's inactive. /// /// Name of the GameObject. /// Tag. /// The GameObject, or null if not found. public static GameObject GameObjectHardFind(string goName, string tag) { return GameObjectUtility.GameObjectHardFind(goName, tag); } /// /// Finds all GameObjects with a specified tag, even inactive GameObjects. /// /// Tag. /// Array of GameObjects with a tag. public static GameObject[] FindGameObjectsWithTagHard(string tag) { var list = new List(); var rootGameObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { GameObjectSearchForTags(rootGameObjects[i].transform, tag, list); } return list.ToArray(); } private static void GameObjectSearchForTags(Transform t, string tag, List list) { if (t == null) return; if (string.Equals(t.tag, tag)) list.Add(t.gameObject); foreach (Transform child in t) { GameObjectSearchForTags(child, tag, list); } } /// /// Like GetComponentInChildren(), but also searches parents. /// /// The component, or null if not found. /// Game object to search. /// The component type. public static T GetComponentAnywhere(GameObject gameObject) where T : Component { if (!gameObject) return null; T component = gameObject.GetComponentInChildren(); if (component) return component; Transform ancestor = gameObject.transform.parent; while (!component && ancestor) { component = ancestor.GetComponentInChildren(); ancestor = ancestor.parent; } return component; } /// /// Gets the height of the game object based on its collider. This only works if the /// game object has a CharacterController, CapsuleCollider, BoxCollider, or SphereCollider. /// /// The game object height if it has a recognized type of collider; otherwise 0. /// Game object. public static float GetGameObjectHeight(GameObject gameObject) { CharacterController controller = gameObject.GetComponent(); if (controller != null) { return controller.height; } else { CapsuleCollider capsuleCollider = gameObject.GetComponent(); if (capsuleCollider != null) { return capsuleCollider.height; } else { BoxCollider boxCollider = gameObject.GetComponent(); if (boxCollider != null) { return boxCollider.center.y + boxCollider.size.y; } else { SphereCollider sphereCollider = gameObject.GetComponent(); if (sphereCollider != null) { return sphereCollider.center.y + sphereCollider.radius; } } } } return 0; } /// /// Sets a component's enabled state to a specified state. /// /// Component to set. /// State to set the component to (true, false, or flip). public static void SetComponentEnabled(Component component, Toggle state) { bool newValue; if (component == null) return; if (component is Renderer) { Renderer targetRenderer = component as Renderer; newValue = ToggleUtility.GetNewValue(targetRenderer.enabled, state); targetRenderer.enabled = newValue; } else if (component is Collider) { Collider targetCollider = component as Collider; newValue = ToggleUtility.GetNewValue(targetCollider.enabled, state); targetCollider.enabled = newValue; } else if (component is Animation) { Animation animationComponent = component as Animation; newValue = ToggleUtility.GetNewValue(animationComponent.enabled, state); animationComponent.enabled = newValue; } else if (component is Animator) { Animator animator = component as Animator; newValue = ToggleUtility.GetNewValue(animator.enabled, state); animator.enabled = newValue; } else if (component is AudioSource) { AudioSource audioSource = component as AudioSource; newValue = ToggleUtility.GetNewValue(audioSource.enabled, state); audioSource.enabled = newValue; } else if (component is Behaviour) { Behaviour behaviour = component as Behaviour; newValue = ToggleUtility.GetNewValue(behaviour.enabled, state); behaviour.enabled = newValue; } else { if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Don't know how to enable/disable {1}.{2}", new System.Object[] { DialogueDebug.Prefix, component.name, component.GetType().Name })); return; } if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: {1}.{2}.enabled = {3}", new System.Object[] { DialogueDebug.Prefix, component.name, component.GetType().Name, newValue })); } public static bool IsCursorActive() { return IsCursorVisible() && !IsCursorLocked(); } public static void SetCursorActive(bool value) { ShowCursor(value); LockCursor(!value); } #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 public static bool IsCursorVisible() { return Screen.showCursor; } public static bool IsCursorLocked() { return Screen.lockCursor; } public static void ShowCursor(bool value) { Screen.showCursor = value; } public static void LockCursor(bool value) { Screen.lockCursor = value; } #else public static bool IsCursorVisible() { return CursorControl.isCursorVisible; } public static bool IsCursorLocked() { return CursorControl.isCursorLocked; } public static void ShowCursor(bool value) { CursorControl.ShowCursor(value); } public static void LockCursor(bool value) { CursorControl.LockCursor(value); } #endif #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 public static void LoadLevel(int index) { if (DialogueDebug.LogInfo) Debug.Log("Dialogue System: Loading level #" + index); Application.LoadLevel(index); } public static void LoadLevel(string name) { if (DialogueDebug.LogInfo) Debug.Log("Dialogue System: Loading level " + name); Application.LoadLevel(name); } public static AsyncOperation LoadLevelAsync(string name) { if (DialogueDebug.LogInfo) Debug.Log("Dialogue System: Asynchronously loading level " + name); return Application.LoadLevelAsync(name); } public static AsyncOperation LoadLevelAsync(int index) { if (DialogueDebug.LogInfo) Debug.Log("Dialogue System: Asynchronously loading level " + index); return Application.LoadLevelAsync(index); } public static string loadedLevelName { get { return Application.loadedLevelName; } } #else public static void LoadLevel(int index) { if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Loading level #" + index); SceneManager.LoadScene(index); } public static void LoadLevel(string name) { if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Loading level " + name); SceneManager.LoadScene(name); } public static AsyncOperation LoadLevelAsync(string name) { if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Asynchronously loading level " + name); return SceneManager.LoadSceneAsync(name); } public static AsyncOperation LoadLevelAsync(int index) { if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Asynchronously loading level " + index); return SceneManager.LoadSceneAsync(index); } public static string loadedLevelName { get { return SceneManager.GetActiveScene().name; } } #endif #region Replace HTML private static string[] htmlTags = new string[] { "", "", "", "", "", "

", "", "", "

", "", "" }; /// /// Removes HTML tags from a string. /// /// /// The string without HTML. /// /// /// The HTML-filled string. /// public static string RemoveHtml(string s) { // [TODO] Replace with something like: http://www.codeproject.com/Articles/298519/Fast-Token-Replacement-in-Csharp if (!string.IsNullOrEmpty(s)) { s = ReplaceMarkup(s); foreach (string htmlTag in htmlTags) { s = s.Replace(htmlTag, string.Empty); } if (s.Contains("&#")) s = ReplaceHtmlCharacterCodes(s); s = s.Replace(""", "\""); s = s.Replace("&", "&"); s = s.Replace("<", "<"); s = s.Replace(">", ">"); s = s.Replace(" ", " "); s = s.Trim(); } return s; } /// /// Selectively replaces HTML character codes (numeric character references) that articy uses. /// public static string ReplaceHtmlCharacterCodes(string s) { var text = s; Regex regex = new Regex(@"&#[0-9]+;"); text = regex.Replace(text, delegate (Match match) { string codeString = match.Value.Substring(2, match.Value.Length - 3); int numericCode; if (!int.TryParse(codeString, out numericCode)) return match.Value; return char.ConvertFromUtf32(numericCode).ToString(); }); return text; } //================================================================== // Code contributed by Racoon7: const RegexOptions Options = RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase; static readonly Regex StylesRegex = new Regex(@"", Options); // Get the part of text dealing with styles static readonly Regex StyleRegex = new Regex(@"#(?s[1-9]\d*) {(?