using System.IO; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using UnityEngine.Scripting; using Passer.Humanoid; namespace Passer { /// /// The Possession of a Humanoid Visitor /// /// /// \image html PawnPossessionsInspector.png /// /// * Default possessions, see PawnPossessions::defaultPossessions /// * Clear at start, see PawnPossessions::clearAtStart /// \version 4.0 and higher public class VisitorPossessions : MonoBehaviour { [System.Serializable] public class Possession { public string name; public Possessable.Type type; public bool persistent; public bool removable = true; public string siteLocation; public string assetPath; [System.NonSerialized] public Possessable scenePossession; } #region Locals protected class CachedPossessionBundle { public string siteLocation; public AssetBundle assetBundle; public static CachedPossessionBundle Find(string possessionLocation) { foreach (CachedPossessionBundle bundle in bundleCache) { if (bundle.siteLocation == possessionLocation && bundle.assetBundle != null) return bundle; } return null; } } [System.Serializable] protected class CachedPossession { public CachedPossessionBundle possessionBundle; public string assetPath; public GameObject possession; public bool preserved = false; // when the possession was a scenePossession which is preseved public static void Update(CachedPossessionBundle cachedBundle, string possessablePath, GameObject prefab) { CachedPossession foundPossession = cache.Find(entry => entry.assetPath == possessablePath ); if (foundPossession == null) { CachedPossession cachedPossession = new CachedPossession() { possessionBundle = cachedBundle, assetPath = possessablePath, possession = prefab, }; cache.Add(cachedPossession); } else { foundPossession.possession = prefab; } } } protected static List bundleCache = new List(); protected static List cache = new List(); #endregion public class Possessions { public List list = new List(); } //// May include an amount later //public static Possessions possessions; public List possessions; public List myPossessions { get { return possessions; } } /// /// The possessions which are available from the start /// public Possessable[] defaultPossessions; /// /// Clear the possessions when the application is started /// /// Default possessions will not be cleared. /// This works only on real Humanoids, not on HumanoidInterfaces/Sites public bool clearOnAwake; private string filePath { get { string filePath = Path.Combine(Application.persistentDataPath, "MyPossessions.json"); return filePath; } } #region Init protected virtual void Awake() { // Only do this for real humanoids HumanoidControl humanoid = GetComponentInParent(); if (humanoid != null) { if (!clearOnAwake) { //Debug.Log("Retrieve possessions from " + filePath); if (File.Exists(filePath)) { string json = File.ReadAllText(filePath); Possessions possessionList = JsonUtility.FromJson(json); possessions = possessionList.list; //JsonUtility.FromJson(json); } else { Debug.Log("Cleared Possessions"); possessions = new List();//new Possessions(); } } else { Debug.Log("Cleared Possessions"); possessions = new List(); // new Possessions(); } if (humanoid.avatarRig != null) { Possessable possessableAvatar = humanoid.avatarRig.GetComponent(); if (possessableAvatar != null) AddPossessions(new Possessable[] { possessableAvatar }, true); } } // Default Possessions cannot be deleted AddPossessions(defaultPossessions, false); } #endregion #region Stop protected virtual void OnDestroy() { //Debug.Log("Store possessions to " + filePath); string json = JsonUtility.ToJson(possessions); File.WriteAllText(filePath, json); //Debug.Log("Possesions stored"); } public static void DestroyScenePossessions() { //possessions.list.RemoveAll(poss => poss.persistent == false); } #endregion /// /// Try to add the gameObject to this humanoidpossessions. /// This only succeeds whent the hameObject has a Possessable component. /// /// The GameObject to add public void TryAddGameObject(GameObject gameObject) { Possessable possession = gameObject.GetComponentInChildren(); if (possession == null) return; Add(possession); } public void AddPossessions(Possessable[] scenePossessions, bool removable = true) { if (possessions == null || scenePossessions == null) return; foreach (Possessable possession in scenePossessions) { if (possession == null) continue; Possession persistentPossession = Add(possession, true); //persistentPossession.scenePossession = PreservePossession(possession); persistentPossession.persistent = possession.crossSite; persistentPossession.removable = removable; } } private static Possessable PreservePossession(Possessable possession) { // Keep the possession as an disabled object for later reference // Note: this does not work for networked setups!!! GameObject preservedPossession = Instantiate(possession.gameObject); preservedPossession.SetActive(false); Object.DontDestroyOnLoad(preservedPossession); return preservedPossession.GetComponent(); } /// /// Add the possessable object to the humanoidPossessions /// /// The possessable object to add /// The persistent possession public Possession Add(Possessable possessable, bool preserved = false) { if (possessable == null) return null; if (possessable.isUnique) { Possession foundPossession = possessions.Find( persistentPossession => persistentPossession.assetPath == possessable.assetPath ); if (foundPossession != null) return foundPossession; } Possession newPossession = new Possession() { name = possessable.name, siteLocation = possessable.siteLocation, assetPath = possessable.assetPath, type = possessable.possessionType, }; if (preserved) newPossession.scenePossession = PreservePossession(possessable); possessions.Add(newPossession); CachedPossessionBundle possessionBundle = CachedPossessionBundle.Find(possessable.siteLocation); CachedPossession cachedPossession = new CachedPossession() { assetPath = possessable.assetPath, possessionBundle = possessionBundle, possession = preserved ? newPossession.scenePossession.gameObject : possessable.gameObject, preserved = preserved, }; cache.Add(cachedPossession); Debug.Log("Possession cache: "); foreach (CachedPossession poss in cache) Debug.Log(" * " + poss.assetPath + " || " + poss.possessionBundle + " || " + poss.possession + " || " + poss.preserved); Debug.Log("cache: "); foreach (CachedPossessionBundle pos in bundleCache) { Debug.Log(" - " + pos.siteLocation + " || " + pos.assetBundle); } return newPossession; } public void DeletePossession(Possession possession) { Debug.Log("deleting " + possession); Possession foundPossession = possessions.Find( persistentPossession => persistentPossession.assetPath == possession.assetPath ); if (foundPossession == null) return; possessions.Remove(foundPossession); Debug.Log("deleted"); } private static AssetBundle lastAssetBundle; public static IEnumerator RetrievePossessionAsync(Possession possession, System.Action callback) { Debug.Log("Possession cache: "); foreach (CachedPossession poss in cache) Debug.Log(" * " + poss.assetPath + " || " + poss.possessionBundle + " || " + poss.possession + " || " + poss.preserved); Debug.Log("cache: "); foreach (CachedPossessionBundle pos in bundleCache) { Debug.Log(" - " + pos.siteLocation + " || " + pos.assetBundle); } if (possession.siteLocation == "") { Debug.Log("Get scene possession"); GameObject prefab = possession.scenePossession.gameObject; prefab.SetActive(true); callback(prefab); prefab.SetActive(false); yield return null; } else yield return RetrievePossessableAsync(possession.siteLocation, possession.assetPath, callback); } public static IEnumerator RetrievePossessableAsync(string possessableLocation, string possessablePath, System.Action callback) { GameObject prefab; if (possessableLocation == "") { CachedPossession foundPossession = cache.Find(entry => entry.assetPath == possessablePath); if (foundPossession == null) { Debug.Log("Cannot retrieve Possessable: location is not set"); callback(null); } else { //Debug.Log("Load from cache: " + possessablePath); prefab = foundPossession.possession; if (prefab == null) { Debug.LogError("Could not load " + possessablePath); callback(null); } else callback(prefab); } } else { CachedPossession foundPossession = cache.Find(entry => entry.assetPath == possessablePath); if (foundPossession != null && foundPossession.possession != null) { //Debug.Log("Load from cache: " + foundPossession.possession); prefab = foundPossession.possession; if (foundPossession.preserved) { //Debug.Log("preserved possession"); prefab.SetActive(true); callback(prefab); prefab.SetActive(false); } else callback(prefab); yield break; } CachedPossessionBundle foundPossessionBundle = bundleCache.Find(entry => entry.siteLocation == possessableLocation); if (foundPossessionBundle == null) { #if UNITY_ANDROID string url = "https://" + possessableLocation + ".android.site"; #elif UNITY_WEBGL string url = "https://" + possessableLocation + ".webgl.site"; #else string url = "https://" + possessableLocation + ".windows.site"; #endif //Debug.Log("Loading possession: " + url); UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url); yield return request.SendWebRequest(); AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request); if (assetBundle == null) { Debug.LogError("Could not load " + url); yield break; } //Debug.Log("Load: " + possessablePath); prefab = LoadPossessableFromAssetBundle(assetBundle, possessablePath); if (prefab == null) { Debug.LogError("Could not load " + possessablePath); yield break; } CachedPossessionBundle cachedBundle = new CachedPossessionBundle() { siteLocation = possessableLocation, assetBundle = assetBundle, }; bundleCache.Add(cachedBundle); CachedPossession.Update(cachedBundle, possessablePath, prefab); } else { Debug.Log("Load: " + possessablePath); prefab = LoadPossessableFromAssetBundle(foundPossessionBundle.assetBundle, possessablePath); if (prefab == null) { Debug.LogError("Could not load " + possessablePath); yield break; } CachedPossession.Update(foundPossessionBundle, possessablePath, prefab); } callback(prefab); } } private static GameObject LoadPossessableFromAssetBundle(AssetBundle assetBundle, string possessablePath) { string possessableName = possessablePath; int lastSlashIx = possessablePath.LastIndexOf('/'); if (lastSlashIx >= 0) possessableName = possessablePath.Substring(lastSlashIx + 1); possessableName = possessableName.ToLower(); GameObject prefab = assetBundle.LoadAsset(possessableName); return prefab; } public static void UnloadPossession() { if (lastAssetBundle != null) lastAssetBundle.Unload(true); } private int currentAvatarIndex = 0; public void UseNextAvatar() { List avatars = possessions.FindAll(possession => possession.type == Possessable.Type.Avatar); if (avatars.Count == 0) return; currentAvatarIndex = mod(currentAvatarIndex + 1, avatars.Count); UseAvatar(currentAvatarIndex); } public void UseAvatar(int avatarIndex) { List avatars = possessions.FindAll(possession => possession.type == Possessable.Type.Avatar); if (avatarIndex < 0 || avatarIndex > avatars.Count) return; HumanoidControl humanoid = FindObjectOfType(); if (humanoid == null) return; if (avatars[avatarIndex] != null) { if (avatars[avatarIndex].scenePossession != null) humanoid.ChangeAvatar(avatars[avatarIndex].scenePossession.gameObject); else StartCoroutine(RetrieveAvatarAsync(avatars[avatarIndex])); } } private static IEnumerator RetrieveAvatarAsync(Possession possession) { HumanoidControl humanoid = FindObjectOfType(); if (humanoid == null) yield break; string url = "https://" + possession.siteLocation + ".windows" + ".site"; Debug.Log("Loading possession: " + url); UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url); yield return request.SendWebRequest(); AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request); if (assetBundle == null) { Debug.LogError("Could not load " + url); yield break; } GameObject avatarPrefab = assetBundle.LoadAsset(possession.assetPath); if (avatarPrefab != null) humanoid.ChangeAvatar(avatarPrefab); } public static int mod(int k, int n) { k %= n; return (k < 0) ? k + n : k; } } }