// 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>||]+>|" +
@"]+>||]+>||]+>|" +
@"|");
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*) {(?