3062 lines
100 KiB
C#
3062 lines
100 KiB
C#
|
|
using UnityEngine;
|
|
using System.IO;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UMA.CharacterSystem;
|
|
|
|
#if UMA_ADDRESSABLES
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
using AsyncOp = UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<System.Collections.Generic.IList<UnityEngine.Object>>;
|
|
using UnityEngine.ResourceManagement.ResourceLocations;
|
|
|
|
#endif
|
|
using PackSlot = UMA.UMAPackedRecipeBase.PackedSlotDataV3;
|
|
using SlotRecipes = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<UMA.UMATextRecipe>>;
|
|
using RaceRecipes = System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<UMA.UMATextRecipe>>>;
|
|
using System.Linq;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEditor.SceneManagement;
|
|
#endif
|
|
|
|
using UnityEngine.SceneManagement;
|
|
using System.Text;
|
|
|
|
|
|
namespace UMA
|
|
{
|
|
[PreferBinarySerialization]
|
|
public class UMAAssetIndexer : ScriptableObject, ISerializationCallbackReceiver
|
|
{
|
|
public static float DefaultLife = 5.0f;
|
|
|
|
public UMALabelsEvent BeforeProcessingLabels = new UMALabelsEvent();
|
|
|
|
[Serializable]
|
|
public class TypeFolders
|
|
{
|
|
public string typeName;
|
|
public string[] Folders;
|
|
}
|
|
|
|
public List<TypeFolders> typeFolders = new List<TypeFolders>();
|
|
|
|
public Dictionary<string, List<string>> TypeFolderSearch = new Dictionary<string, List<string>>();
|
|
|
|
|
|
#if UMA_ADDRESSABLES
|
|
private class CachedOp
|
|
{
|
|
public AsyncOp Operation;
|
|
public float OperationTime;
|
|
public float Life; // life in seconds
|
|
public string Info;
|
|
|
|
public CachedOp(AsyncOp op, string info, float OpLife = 0.0f)
|
|
{
|
|
if (OpLife == 0.0f)
|
|
{
|
|
OpLife = DefaultLife;
|
|
}
|
|
|
|
Operation = op;
|
|
OperationTime = Time.time;
|
|
Life = OpLife;
|
|
Info = info;
|
|
}
|
|
|
|
public bool Expired
|
|
{
|
|
get
|
|
{
|
|
if (Time.time - OperationTime > Life)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if UMA_ADDRESSABLES
|
|
public Dictionary<string, bool> Preloads = new Dictionary<string, bool>();
|
|
private List<CachedOp> LoadedItems = new List<CachedOp>();
|
|
#endif
|
|
|
|
RaceRecipes raceRecipes = new RaceRecipes();
|
|
|
|
#region constants and static strings
|
|
public static string SortOrder = "Name";
|
|
public static string[] SortOrders = { "Name", "AssetName" };
|
|
public static Dictionary<string, System.Type> TypeFromString = new Dictionary<string, System.Type>();
|
|
public static Dictionary<string, AssetItem> GuidTypes = new Dictionary<string, AssetItem>();
|
|
public static Dictionary<string, string> LowerCaseLookup = new Dictionary<string, string>();
|
|
#endregion
|
|
#region Fields
|
|
protected Dictionary<System.Type, System.Type> TypeToLookup = new Dictionary<System.Type, System.Type>()
|
|
{
|
|
{ (typeof(SlotDataAsset)),(typeof(SlotDataAsset)) },
|
|
{ (typeof(OverlayDataAsset)),(typeof(OverlayDataAsset)) },
|
|
{ (typeof(RaceData)),(typeof(RaceData)) },
|
|
{ (typeof(UMATextRecipe)),(typeof(UMATextRecipe)) },
|
|
{ (typeof(UMAWardrobeRecipe)),(typeof(UMAWardrobeRecipe)) },
|
|
{ (typeof(UMAWardrobeCollection)),(typeof(UMAWardrobeCollection)) },
|
|
{ (typeof(RuntimeAnimatorController)),(typeof(RuntimeAnimatorController)) },
|
|
{ (typeof(AnimatorOverrideController)),(typeof(RuntimeAnimatorController)) },
|
|
#if UNITY_EDITOR
|
|
{ (typeof(AnimatorController)),(typeof(RuntimeAnimatorController)) },
|
|
#endif
|
|
{ typeof(TextAsset), typeof(TextAsset) },
|
|
{ typeof(DynamicUMADnaAsset), typeof(DynamicUMADnaAsset) },
|
|
{ typeof(UMAMaterial), typeof(UMAMaterial) },
|
|
{ typeof(UMAColorScheme), typeof(UMAColorScheme) }
|
|
};
|
|
|
|
|
|
// The names of the fully qualified types.
|
|
public List<string> IndexedTypeNames = new List<string>();
|
|
// These list is used so Unity will serialize the data
|
|
public List<AssetItem> SerializedItems = new List<AssetItem>();
|
|
// This is really where we keep the data.
|
|
private Dictionary<System.Type, Dictionary<string, AssetItem>> TypeLookup = new Dictionary<System.Type, Dictionary<string, AssetItem>>();
|
|
// This list tracks the types for use in iterating through the dictionaries
|
|
private System.Type[] Types =
|
|
{
|
|
(typeof(SlotDataAsset)),
|
|
(typeof(OverlayDataAsset)),
|
|
(typeof(RaceData)),
|
|
(typeof(UMATextRecipe)),
|
|
(typeof(UMAWardrobeRecipe)),
|
|
(typeof(UMAWardrobeCollection)),
|
|
(typeof(RuntimeAnimatorController)),
|
|
(typeof(AnimatorOverrideController)),
|
|
#if UNITY_EDITOR
|
|
(typeof(AnimatorController)),
|
|
#endif
|
|
(typeof(DynamicUMADnaAsset)),
|
|
(typeof(TextAsset)),
|
|
(typeof(UMAMaterial)),
|
|
(typeof(UMAColorScheme))
|
|
};
|
|
|
|
|
|
#endregion
|
|
#region Static Fields
|
|
static UMAAssetIndexer theIndexer = null;
|
|
|
|
|
|
#endregion
|
|
|
|
public static System.Diagnostics.Stopwatch StartTimer()
|
|
{
|
|
#if TIMEINDEXER
|
|
if(Debug.isDebugBuild)
|
|
Debug.Log("Timer started at " + Time.realtimeSinceStartup + " Sec");
|
|
System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
|
|
st.Start();
|
|
|
|
return st;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
public static void StopTimer(System.Diagnostics.Stopwatch st, string Status)
|
|
{
|
|
#if TIMEINDEXER
|
|
st.Stop();
|
|
if(Debug.isDebugBuild)
|
|
Debug.Log(Status + " Completed " + st.ElapsedMilliseconds + "ms");
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
public static UMAAssetIndexer Instance
|
|
{
|
|
get
|
|
{
|
|
if (theIndexer == null)
|
|
{
|
|
//var st = StartTimer();
|
|
theIndexer = Resources.Load("AssetIndexer") as UMAAssetIndexer;
|
|
if (theIndexer == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
theIndexer.UpdateSerializedDictionaryItems();
|
|
theIndexer.RebuildRaceRecipes();
|
|
#if UNITY_EDITOR
|
|
EditorSceneManager.sceneSaving += EditorSceneManager_sceneSaving;
|
|
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
|
|
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged; ;
|
|
#endif
|
|
//StopTimer(st,"Asset index load");
|
|
}
|
|
return theIndexer;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private static void EditorApplication_playModeStateChanged(PlayModeStateChange obj)
|
|
{
|
|
if (!EditorApplication.isPlayingOrWillChangePlaymode &&
|
|
!EditorApplication.isPlaying)
|
|
{
|
|
RebuildUMAS(SceneManager.GetActiveScene());
|
|
}
|
|
UMAMeshData.CleanupGlobalBuffers();
|
|
}
|
|
|
|
|
|
[UnityEditor.Callbacks.DidReloadScripts]
|
|
private static void OnScriptsReloaded()
|
|
{
|
|
if (!EditorApplication.isPlayingOrWillChangePlaymode)
|
|
{
|
|
UMAMeshData.CleanupGlobalBuffers();
|
|
}
|
|
}
|
|
public const string ConfigToggle_LeanMeanSceneFiles = "UMA_CLEANUP_GENERATED_DATA_ON_SAVE";
|
|
|
|
public static bool LeanMeanSceneFiles()
|
|
{
|
|
return EditorPrefs.GetBool(ConfigToggle_LeanMeanSceneFiles, true);
|
|
}
|
|
|
|
private static void EditorSceneManager_sceneSaved(UnityEngine.SceneManagement.Scene scene)
|
|
{
|
|
if (!LeanMeanSceneFiles())
|
|
{
|
|
return;
|
|
}
|
|
|
|
RebuildUMAS(scene);
|
|
}
|
|
|
|
private static void EditorSceneManager_sceneSaving(UnityEngine.SceneManagement.Scene scene, string path)
|
|
{
|
|
if (!LeanMeanSceneFiles())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CleanupUMAS(scene);
|
|
}
|
|
|
|
public static void RebuildUMAS(Scene scene)
|
|
{
|
|
GameObject[] sceneObjs = scene.GetRootGameObjects();
|
|
for (int i = 0; i < sceneObjs.Length; i++)
|
|
{
|
|
GameObject go = sceneObjs[i];
|
|
DynamicCharacterAvatar[] dcas = go.GetComponentsInChildren<DynamicCharacterAvatar>(false);
|
|
if (dcas.Length > 0)
|
|
{
|
|
for (int i1 = 0; i1 < dcas.Length; i1++)
|
|
{
|
|
DynamicCharacterAvatar dca = dcas[i1];
|
|
if (dca.editorTimeGeneration)
|
|
{
|
|
dca.GenerateSingleUMA();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void CleanupUMAS(Scene scene)
|
|
{
|
|
// Cleanup any editor generated UMAS
|
|
GameObject[] sceneObjs = scene.GetRootGameObjects();
|
|
for (int i = 0; i < sceneObjs.Length; i++)
|
|
{
|
|
GameObject go = sceneObjs[i];
|
|
DynamicCharacterAvatar[] dcas = go.GetComponentsInChildren<DynamicCharacterAvatar>(false);
|
|
if (dcas.Length > 0)
|
|
{
|
|
for (int i1 = 0; i1 < dcas.Length; i1++)
|
|
{
|
|
DynamicCharacterAvatar dca = dcas[i1];
|
|
// Free all the generated data so we don't junk up the scene file.
|
|
// it will be regenerated later.
|
|
dca.CleanupGeneratedData();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct IndexBackup
|
|
{
|
|
public DateTime BackupTime;
|
|
public AssetItem[] Items;
|
|
}
|
|
|
|
public string Backup()
|
|
{
|
|
try
|
|
{
|
|
RepairAndCleanup();
|
|
|
|
IndexBackup backup = new IndexBackup();
|
|
backup.BackupTime = DateTime.Now;
|
|
backup.Items = UpdateSerializedList().ToArray();
|
|
|
|
return JsonUtility.ToJson(backup);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public bool Restore(string s, bool quiet = false)
|
|
{
|
|
try
|
|
{
|
|
IndexBackup restore = JsonUtility.FromJson<IndexBackup>(s);
|
|
SerializedItems.Clear();
|
|
SerializedItems.AddRange(restore.Items);
|
|
if (!quiet)
|
|
{
|
|
EditorUtility.DisplayProgressBar("Restore", "Restoring index", 0.33f);
|
|
}
|
|
|
|
UpdateSerializedDictionaryItems();
|
|
if (!quiet)
|
|
{
|
|
EditorUtility.DisplayProgressBar("Restore", "Restoring index", 0.66f);
|
|
}
|
|
|
|
RepairAndCleanup();
|
|
if (!quiet)
|
|
{
|
|
EditorUtility.DisplayProgressBar("Restore", "Restoring index", 1.0f);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public void AddSearchFolder(string type, string FolderName)
|
|
{
|
|
if (!TypeFolderSearch.ContainsKey(type))
|
|
{
|
|
TypeFolderSearch.Add(type, new List<string>());
|
|
}
|
|
TypeFolderSearch[type].Add(FolderName);
|
|
}
|
|
|
|
public void RemoveSearchFolder(string type, string FolderName)
|
|
{
|
|
if (TypeFolderSearch.ContainsKey(type))
|
|
{
|
|
TypeFolderSearch[type].Remove(FolderName);
|
|
}
|
|
}
|
|
|
|
public Type GetRuntimeType(Type type)
|
|
{
|
|
return TypeToLookup[type];
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// This returns TRUE (isValid) if any type has valid entries
|
|
/// This returns FALSE if all types have no entries, or there are no types.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool IsValid()
|
|
{
|
|
foreach(var t in TypeToLookup.Keys)
|
|
{
|
|
var typeDic = GetAssetDictionary(t);
|
|
if (typeDic.Keys.Count > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if UMA_ADDRESSABLES
|
|
private HashSet<CachedOp> Cleanup = new HashSet<CachedOp>();
|
|
public void CheckCache()
|
|
{
|
|
Cleanup.Clear();
|
|
|
|
for(int i=0;i<LoadedItems.Count;i++)
|
|
{
|
|
CachedOp c = LoadedItems[i];
|
|
if (c.Expired)
|
|
{
|
|
Addressables.Release(c.Operation);
|
|
Cleanup.Add(c);
|
|
}
|
|
}
|
|
if (Cleanup.Count > 0)
|
|
{
|
|
LoadedItems.RemoveAll(x => Cleanup.Contains(x));
|
|
}
|
|
}
|
|
#endif
|
|
#if UNITY_EDITOR
|
|
public void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
|
{
|
|
bool changed = false;
|
|
|
|
// Build a dictionary of the items by path.
|
|
Dictionary<string, AssetItem> ItemsByPath = new Dictionary<string, AssetItem>();
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
if (ItemsByPath.ContainsKey(ai._Path))
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.Log("Duplicate path for item: " + ai._Path);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
ItemsByPath.Add(ai._Path, ai);
|
|
}
|
|
|
|
// see if they moved it in the editor.
|
|
for (int i = 0; i < movedAssets.Length; i++)
|
|
{
|
|
string NewPath = movedAssets[i];
|
|
string OldPath = movedFromAssetPaths[i];
|
|
|
|
// Check to see if this is an indexed asset.
|
|
if (ItemsByPath.ContainsKey(OldPath))
|
|
{
|
|
changed = true;
|
|
ItemsByPath[OldPath]._Path = NewPath;
|
|
}
|
|
}
|
|
|
|
// Rebuild the tables
|
|
SerializedItems.Clear();
|
|
foreach (AssetItem ai in ItemsByPath.Values)
|
|
{
|
|
// We null things out when we want to delete them. This prevents it from going back into
|
|
// the dictionary when rebuilt.
|
|
if (ai == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SerializedItems.Add(ai);
|
|
}
|
|
|
|
UpdateSerializedDictionaryItems();
|
|
if (changed)
|
|
{
|
|
ForceSave();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force the Index to save and reload
|
|
/// </summary>
|
|
public void ForceSave()
|
|
{
|
|
var st = StartTimer();
|
|
EditorUtility.SetDirty(this);
|
|
AssetDatabase.SaveAssets();
|
|
StopTimer(st, "ForceSave");
|
|
}
|
|
#endif
|
|
|
|
|
|
#region Manage Types
|
|
/// <summary>
|
|
/// Returns a list of all types that we know about.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public System.Type[] GetTypes()
|
|
{
|
|
return Types;
|
|
}
|
|
|
|
public System.Type GetIndexedType(System.Type type)
|
|
{
|
|
if (TypeToLookup.ContainsKey(type))
|
|
{
|
|
return TypeToLookup[type];
|
|
}
|
|
return type;
|
|
}
|
|
|
|
public Dictionary<System.Type, System.Type>.ValueCollection GetIndexedTypeValues()
|
|
{
|
|
return TypeToLookup.Values;
|
|
}
|
|
|
|
public bool IsIndexedType(System.Type type)
|
|
{
|
|
|
|
foreach (System.Type check in TypeToLookup.Keys)
|
|
{
|
|
if (check == type)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsAdditionalIndexedType(string QualifiedName)
|
|
{
|
|
for (int i = 0; i < IndexedTypeNames.Count; i++)
|
|
{
|
|
string s = IndexedTypeNames[i];
|
|
if (s == QualifiedName)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Add a type to the types tracked
|
|
/// </summary>
|
|
/// <param name="sType"></param>
|
|
public void AddType(System.Type sType)
|
|
{
|
|
string QualifiedName = sType.AssemblyQualifiedName;
|
|
if (IsAdditionalIndexedType(QualifiedName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<System.Type> newTypes = new List<System.Type>();
|
|
newTypes.AddRange(Types);
|
|
newTypes.Add(sType);
|
|
Types = newTypes.ToArray();
|
|
TypeToLookup.Add(sType, sType);
|
|
IndexedTypeNames.Add(sType.AssemblyQualifiedName);
|
|
BuildStringTypes();
|
|
}
|
|
|
|
public void RemoveType(System.Type sType)
|
|
{
|
|
string QualifiedName = sType.AssemblyQualifiedName;
|
|
if (!IsAdditionalIndexedType(QualifiedName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TypeToLookup.Remove(sType);
|
|
|
|
List<System.Type> newTypes = new List<System.Type>();
|
|
newTypes.AddRange(Types);
|
|
newTypes.Remove(sType);
|
|
Types = newTypes.ToArray();
|
|
TypeLookup.Remove(sType);
|
|
IndexedTypeNames.Remove(sType.AssemblyQualifiedName);
|
|
BuildStringTypes();
|
|
}
|
|
#endregion
|
|
|
|
#region Access the index
|
|
public AssetItem GetRecipeItem(UMAPackedRecipeBase recipe)
|
|
{
|
|
if (recipe is UMAWardrobeCollection)
|
|
{
|
|
return GetAssetItem<UMAWardrobeCollection>(recipe.name);
|
|
}
|
|
|
|
if (recipe is UMAWardrobeRecipe)
|
|
{
|
|
return GetAssetItem<UMAWardrobeRecipe>(recipe.name);
|
|
}
|
|
|
|
if (recipe is UMATextRecipe)
|
|
{
|
|
return GetAssetItem<UMATextRecipe>(recipe.name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public UMAData.UMARecipe GetRecipe(UMATextRecipe recipe, UMAContextBase context)
|
|
{
|
|
UMAPackedRecipeBase.UMAPackRecipe PackRecipe = recipe.PackedLoad(context);
|
|
try
|
|
{
|
|
UMAData.UMARecipe TempRecipe = UMATextRecipe.UnpackRecipe(PackRecipe, context);
|
|
return TempRecipe;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError("Error unpacking recipe: " + recipe.name + ". " + ex.Message);
|
|
}
|
|
return new UMAData.UMARecipe();
|
|
}
|
|
|
|
public bool HasAsset<T>(string Name)
|
|
{
|
|
System.Type ot = typeof(T);
|
|
System.Type theType = TypeToLookup[ot];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
return TypeDic.ContainsKey(Name);
|
|
}
|
|
|
|
public bool HasAsset<T>(int NameHash)
|
|
{
|
|
System.Type ot = typeof(T);
|
|
System.Type theType = TypeToLookup[ot];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
// This honestly hurt my heart typing this.
|
|
// Todo: replace this loop with a dictionary.
|
|
foreach (string s in TypeDic.Keys)
|
|
{
|
|
if (UMAUtils.StringToHash(s) == NameHash)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the asset specified, if it exists.
|
|
/// if it can't be found by name, then we do a scan of the assets to see if
|
|
/// we can find the name directly on the object, and return that.
|
|
/// We then rebuild the index to make sure it's up to date.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="Name"></param>
|
|
/// <returns></returns>
|
|
public AssetItem GetAssetItem<T>(string Name)
|
|
{
|
|
if (string.IsNullOrEmpty(Name))
|
|
{
|
|
return null;
|
|
}
|
|
#if UMA_INDEX_LC
|
|
Name = Name.ToLower();
|
|
#endif
|
|
System.Type ot = typeof(T);
|
|
|
|
if (!TypeToLookup.ContainsKey(ot))
|
|
{
|
|
Debug.LogError($"Unknown type: {ot.ToString()} for item {Name}");
|
|
}
|
|
System.Type theType = TypeToLookup[ot];
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
if (!TypeDic.ContainsKey(Name))
|
|
{
|
|
string lname = Name.ToLowerInvariant() + "." + ot.ToString();
|
|
if (LowerCaseLookup.ContainsKey(lname))
|
|
{
|
|
Name = LowerCaseLookup[lname];
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Unknown item [{lname}] in Lowercase Lookup");
|
|
}
|
|
}
|
|
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
if (TypeDic[Name] == null)
|
|
{
|
|
Debug.LogError($"Asset with Name {Name} is NULL for type {ot.ToString()}");
|
|
}
|
|
}
|
|
return TypeDic[Name];
|
|
}
|
|
else
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.LogWarning($"Unknown item [{Name}] for type {ot.ToString()}. TypeDic contains {TypeDic.Count} items");
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the asset specified, if it exists.
|
|
/// if it can't be found by name, then we do a scan of the assets to see if
|
|
/// we can find the name directly on the object, and return that.
|
|
/// We then rebuild the index to make sure it's up to date.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="Name"></param>
|
|
/// <returns></returns>
|
|
public AssetItem GetAssetItemForObject(UnityEngine.Object o)
|
|
{
|
|
System.Type ot = o.GetType();
|
|
System.Type theType = TypeToLookup[ot];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
string Name = AssetItem.GetEvilName(o);
|
|
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
return TypeDic[Name];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If we know the type, we can get the dictionary directly.
|
|
/// </summary>
|
|
/// <param name="ot"></param>
|
|
/// <param name="Name"></param>
|
|
/// <returns></returns>
|
|
public AssetItem GetAssetItem(System.Type ot, string Name)
|
|
{
|
|
System.Type theType = TypeToLookup[ot];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
return TypeDic[Name];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
|
|
public List<AssetItem> GetAssetItems(string recipe, bool LookForLODs = false)
|
|
{
|
|
AssetItem ai = GetAssetItem<UMAWardrobeRecipe>(recipe);
|
|
if (ai != null)
|
|
{
|
|
return GetAssetItems(ai.Item as UMAWardrobeRecipe, LookForLODs);
|
|
}
|
|
return new List<AssetItem>();
|
|
}
|
|
|
|
public List<AssetItem> GetAssetItems(UMAPackedRecipeBase recipe, bool LookForLODs = false)
|
|
{
|
|
if (recipe is UMAWardrobeCollection)
|
|
{
|
|
return new List<AssetItem>();
|
|
}
|
|
UMAPackedRecipeBase.UMAPackRecipe PackRecipe = recipe.PackedLoad(UMAContextBase.Instance);
|
|
|
|
var Slots = PackRecipe.slotsV3;
|
|
|
|
if (Slots == null)
|
|
{
|
|
return GetAssetItemsV2(PackRecipe, LookForLODs);
|
|
}
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(typeof(SlotDataAsset));
|
|
List<AssetItem> returnval = new List<AssetItem>();
|
|
|
|
for (int i1 = 0; i1 < Slots.Length; i1++)
|
|
{
|
|
PackSlot slot = Slots[i1];
|
|
// We are getting extra blank slots. That's weird.
|
|
if (slot == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(slot.id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AssetItem s = GetAssetItem<SlotDataAsset>(slot.id);
|
|
if (s != null)
|
|
{
|
|
returnval.Add(s);
|
|
string LodIndicator = slot.id.Trim() + "_LOD";
|
|
if (slot.id.Contains("_LOD"))
|
|
{
|
|
// LOD is directly in the base recipe.
|
|
LodIndicator = slot.id.Substring(0, slot.id.Length - 1);
|
|
}
|
|
|
|
if (slot.overlays != null)
|
|
{
|
|
for (int i = 0; i < slot.overlays.Length; i++)
|
|
{
|
|
UMAPackedRecipeBase.PackedOverlayDataV3 overlay = slot.overlays[i];
|
|
if (overlay == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AssetItem o = GetAssetItem<OverlayDataAsset>(overlay.id);
|
|
if (o != null)
|
|
{
|
|
returnval.Add(o);
|
|
}
|
|
}
|
|
}
|
|
if (LookForLODs)
|
|
{
|
|
foreach (string slod in TypeDic.Keys)
|
|
{
|
|
if (String.IsNullOrEmpty(slod))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (slod.StartsWith(LodIndicator))
|
|
{
|
|
AssetItem lodSlot = GetAssetItem<SlotDataAsset>(slod);
|
|
returnval.Add(lodSlot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return returnval;
|
|
}
|
|
|
|
private List<AssetItem> GetAssetItemsV2(UMAPackedRecipeBase.UMAPackRecipe PackRecipe, bool LookForLods)
|
|
{
|
|
List<AssetItem> returnval = new List<AssetItem>();
|
|
|
|
var Slots = PackRecipe.slotsV2;
|
|
|
|
if (Slots == null)
|
|
{
|
|
return returnval;
|
|
}
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(typeof(SlotDataAsset));
|
|
|
|
for (int i1 = 0; i1 < Slots.Length; i1++)
|
|
{
|
|
UMAPackedRecipeBase.PackedSlotDataV2 slot = Slots[i1];
|
|
if (slot == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(slot.id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string LodIndicator = slot.id.Trim() + "_LOD";
|
|
AssetItem s = GetAssetItem<SlotDataAsset>(slot.id);
|
|
if (s != null)
|
|
{
|
|
returnval.Add(s);
|
|
var overlays = slot.overlays;
|
|
for (int i = 0; i < overlays.Length; i++)
|
|
{
|
|
UMAPackedRecipeBase.PackedOverlayDataV2 overlay = overlays[i];
|
|
AssetItem o = GetAssetItem<OverlayDataAsset>(overlay.id);
|
|
if (o != null)
|
|
{
|
|
returnval.Add(o);
|
|
}
|
|
}
|
|
}
|
|
if (LookForLods)
|
|
{
|
|
foreach (string slod in TypeDic.Keys)
|
|
{
|
|
if (slod.StartsWith(LodIndicator))
|
|
{
|
|
AssetItem lodSlot = GetAssetItem<SlotDataAsset>(slod);
|
|
returnval.Add(lodSlot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return returnval;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the asset hash and name for the given object
|
|
/// </summary>
|
|
private void GetEvilAssetNameAndHash(System.Type type, UnityEngine.Object o, ref string assetName, int assetHash)
|
|
{
|
|
if (o is SlotDataAsset)
|
|
{
|
|
SlotDataAsset sd = o as SlotDataAsset;
|
|
assetName = sd.slotName;
|
|
assetHash = sd.nameHash;
|
|
}
|
|
else if (o is OverlayDataAsset)
|
|
{
|
|
OverlayDataAsset od = o as OverlayDataAsset;
|
|
assetName = od.overlayName;
|
|
assetHash = od.nameHash;
|
|
}
|
|
else if (o is RaceData)
|
|
{
|
|
RaceData rd = o as RaceData;
|
|
assetName = rd.raceName;
|
|
assetHash = UMAUtils.StringToHash(assetName);
|
|
}
|
|
else
|
|
{
|
|
assetName = o.name;
|
|
assetHash = UMAUtils.StringToHash(assetName);
|
|
}
|
|
#if UMA_INDEX_LC
|
|
assetName = assetName.ToLower();
|
|
assetHash = UMAUtils.StringToHash(assetName);
|
|
#endif
|
|
}
|
|
|
|
public List<AssetItem> GetAssetItems<T>()
|
|
{
|
|
List<AssetItem> Items = new List<AssetItem>();
|
|
System.Type ot = typeof(T);
|
|
System.Type theType = TypeToLookup[ot];
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
Items.AddRange(TypeDic.Values);
|
|
|
|
return Items;
|
|
}
|
|
public List<AssetItem> GetAssetItems(Type t)
|
|
{
|
|
List<AssetItem> Items = new List<AssetItem>();
|
|
System.Type theType = TypeToLookup[t];
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
Items.AddRange(TypeDic.Values);
|
|
|
|
return Items;
|
|
}
|
|
|
|
public List<T> GetAllAssets<T>(string[] foldersToSearch = null) where T : UnityEngine.Object
|
|
{
|
|
var st = StartTimer();
|
|
|
|
var ret = new List<T>();
|
|
System.Type ot = typeof(T);
|
|
System.Type theType = TypeToLookup[ot];
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
foreach (KeyValuePair<string, AssetItem> kp in TypeDic)
|
|
{
|
|
if (AssetFolderCheck(kp.Value, foldersToSearch))
|
|
{
|
|
if (kp.Value.Item != null)
|
|
{
|
|
ret.Add((kp.Value.Item as T));
|
|
}
|
|
}
|
|
}
|
|
StopTimer(st, "GetAllAssets type=" + typeof(T).Name);
|
|
return ret;
|
|
}
|
|
|
|
// Only do a full check of the index one time after domain reload
|
|
|
|
private static bool WasChecked = false;
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// returns true if it rebuilt the index.
|
|
/// returns false if it did NOT rebuild the index.
|
|
/// </summary>
|
|
public bool CheckIndex()
|
|
{
|
|
// Unfortunately that asmdef is not available here
|
|
string autoconfig = "UMA_INDEX_AUTOREPAIR";
|
|
if (EditorPrefs.GetBool(autoconfig, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (WasChecked)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
WasChecked = true;
|
|
|
|
if (!IsValid())
|
|
{
|
|
HealIndex();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if UNITY_EDITOR
|
|
Dictionary<System.Type, HashSet<int>> repairsAttempted = new Dictionary<System.Type, HashSet<int>>();
|
|
|
|
public bool AlreadyAttempted<T>(int nameHash)
|
|
{
|
|
if (repairsAttempted.ContainsKey(typeof(T)) == false)
|
|
{
|
|
repairsAttempted.Add(typeof(T), new HashSet<int>());
|
|
}
|
|
|
|
HashSet<int> processedTable = repairsAttempted[typeof(T)];
|
|
if (!processedTable.Contains(nameHash))
|
|
{
|
|
processedTable.Add(nameHash);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
public T GetAsset<T>(int nameHash, string[] foldersToSearch = null, bool recursionGuard = false) where T : UnityEngine.Object
|
|
{
|
|
#if UNITY_EDITOR
|
|
bool indexUpdated = CheckIndex();
|
|
#endif
|
|
System.Type ot = typeof(T);
|
|
Dictionary<string, AssetItem> TypeDic = (Dictionary<string, AssetItem>)TypeLookup[ot];
|
|
string assetName = "";
|
|
int assetHash = -1;
|
|
foreach (KeyValuePair<string, AssetItem> kp in TypeDic)
|
|
{
|
|
assetName = "";
|
|
assetHash = -1;
|
|
GetEvilAssetNameAndHash(typeof(T), kp.Value.Item, ref assetName, assetHash);
|
|
if (assetHash == nameHash)
|
|
{
|
|
if (AssetFolderCheck(kp.Value, foldersToSearch))
|
|
{
|
|
return (kp.Value.Item as T);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
#if UNITY_EDITOR
|
|
|
|
// If this is NOT the second time through the retrieval
|
|
// AND it is not in play mode
|
|
// AND we have not already rebuilt the library because it was corrupt or lost,
|
|
// THEN we rebuild the type library for this specific type and try again.
|
|
if (!recursionGuard && !indexUpdated && !Application.isPlaying)
|
|
{
|
|
// If we've never done this before for this item, try again.
|
|
if (!AlreadyAttempted<T>(nameHash))
|
|
{
|
|
RefreshType(ot);
|
|
return GetAsset<T>(nameHash, foldersToSearch, true);
|
|
}
|
|
}
|
|
#endif
|
|
return null;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// Refresh a specific type by searching the folders
|
|
/// </summary>
|
|
/// <param name="ot"></param>
|
|
private void RefreshType(Type ot)
|
|
{
|
|
string typeString = ot.Name;
|
|
|
|
List<string> FolderFilter = null;
|
|
if (TypeFolderSearch.ContainsKey(typeString))
|
|
{
|
|
FolderFilter = TypeFolderSearch[typeString];
|
|
}
|
|
AddType(typeString, ot, FolderFilter);
|
|
ForceSave();
|
|
}
|
|
#endif
|
|
|
|
public T GetAsset<T>(string name, string[] foldersToSearch, bool recursionGuard = false) where T : UnityEngine.Object
|
|
{
|
|
#if UNITY_EDITOR
|
|
bool indexUpdated = CheckIndex();
|
|
#endif
|
|
var thisAssetItem = GetAssetItem<T>(name);
|
|
if (thisAssetItem != null)
|
|
{
|
|
if (AssetFolderCheck(thisAssetItem, foldersToSearch))
|
|
{
|
|
return (thisAssetItem.Item as T);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR
|
|
|
|
// If this is NOT the second time through the retrieval
|
|
// AND it is not in play mode
|
|
// AND we have not already rebuilt the library because it was corrupt or lost,
|
|
// THEN we rebuild the type library for this specific type and try again.
|
|
if (!recursionGuard && !indexUpdated && !Application.isPlaying)
|
|
{
|
|
// If we've never done this before for this item, try again.
|
|
int nameHash = UMAUtils.StringToHash(name);
|
|
if (!AlreadyAttempted<T>(nameHash))
|
|
{
|
|
|
|
RefreshType(typeof(T));
|
|
return GetAsset<T>(name, foldersToSearch, true);
|
|
}
|
|
}
|
|
#endif
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public T GetAsset<T>(string name, bool recursionGuard = false) where T : UnityEngine.Object
|
|
{
|
|
#if UNITY_EDITOR
|
|
bool indexUpdated = CheckIndex();
|
|
#endif
|
|
var thisAssetItem = GetAssetItem<T>(name);
|
|
if (thisAssetItem != null)
|
|
{
|
|
return (thisAssetItem.Item as T);
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR
|
|
// If this is NOT the second time through the retrieval
|
|
// AND it is not in play mode
|
|
// AND we have not already rebuilt the library because it was corrupt or lost,
|
|
// THEN we rebuild the type library for this specific type and try again.
|
|
if (!recursionGuard && !indexUpdated && !Application.isPlaying)
|
|
{
|
|
// If we've never done this before for this item, try again.
|
|
int nameHash = UMAUtils.StringToHash(name);
|
|
if (!AlreadyAttempted<T>(nameHash))
|
|
{
|
|
RefreshType(typeof(T));
|
|
return GetAsset<T>(name, true);
|
|
}
|
|
}
|
|
#endif
|
|
return null;
|
|
}
|
|
}
|
|
public List<UMARecipeBase> GetRecipesForRaceSlot(string race, string slot)
|
|
{
|
|
// This will get the aggregate for all compatible races with no duplicates.
|
|
List<string> recipes = GetRecipeNamesForRaceSlot(race, slot);
|
|
|
|
// Build a list of recipes to return.
|
|
List<UMARecipeBase> results = new List<UMARecipeBase>();
|
|
|
|
for (int i = 0; i < recipes.Count; i++)
|
|
{
|
|
string recipeName = recipes[i];
|
|
UMAWardrobeRecipe uwr = GetAsset<UMAWardrobeRecipe>(recipeName);
|
|
if (uwr != null)
|
|
{
|
|
results.Add(uwr);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
|
|
private void internalGetRecipes(string race, ref Dictionary<string, HashSet<UMATextRecipe>> results)
|
|
{
|
|
if (raceRecipes.ContainsKey(race))
|
|
{
|
|
SlotRecipes sr = raceRecipes[race];
|
|
|
|
foreach (KeyValuePair<string, List<UMATextRecipe>> kp in sr)
|
|
{
|
|
if (!results.ContainsKey(kp.Key))
|
|
{
|
|
results.Add(kp.Key, new HashSet<UMATextRecipe>());
|
|
}
|
|
results[kp.Key].UnionWith(kp.Value);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
public Dictionary<string, List<UMATextRecipe>> GetRecipes(string race)
|
|
{
|
|
Dictionary<string, HashSet<UMATextRecipe>> aggregate = new Dictionary<string, HashSet<UMATextRecipe>>();
|
|
|
|
internalGetRecipes(race, ref aggregate);
|
|
|
|
RaceData rc = GetAsset<RaceData>(race);
|
|
if (rc != null)
|
|
{
|
|
List<string> list = rc.GetCrossCompatibleRaces();
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
string CompatRace = list[i];
|
|
internalGetRecipes(CompatRace, ref aggregate);
|
|
}
|
|
}
|
|
|
|
SlotRecipes results = new SlotRecipes();
|
|
foreach (KeyValuePair<string, HashSet<UMATextRecipe>> kp in aggregate)
|
|
{
|
|
results.Add(kp.Key, kp.Value.ToList());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private HashSet<string> internalGetRecipeNamesForRaceSlot(string race, string slot)
|
|
{
|
|
HashSet<string> results = new HashSet<string>();
|
|
|
|
if (raceRecipes.ContainsKey(race))
|
|
{
|
|
SlotRecipes sr = raceRecipes[race];
|
|
if (sr.ContainsKey(slot))
|
|
{
|
|
for (int i = 0; i < sr[slot].Count; i++)
|
|
{
|
|
UMAWardrobeRecipe uwr = (UMAWardrobeRecipe)sr[slot][i];
|
|
results.Add(uwr.name);
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<string> GetRecipeNamesForRaceSlot(string race, string slot)
|
|
{
|
|
// Start with recipes that are directly marked for this race.
|
|
HashSet<string> results = internalGetRecipeNamesForRaceSlot(race, slot);
|
|
|
|
RaceData rc = GetAsset<RaceData>(race);
|
|
if (rc != null)
|
|
{
|
|
List<string> list = rc.GetCrossCompatibleRaces();
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
string CompatRace = list[i];
|
|
results.UnionWith(internalGetRecipeNamesForRaceSlot(CompatRace, slot));
|
|
}
|
|
}
|
|
|
|
return results.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load all items from the asset bundle into the index.
|
|
/// </summary>
|
|
/// <param name="ab"></param>
|
|
public void AddFromAssetBundle(AssetBundle ab)
|
|
{
|
|
for (int i = 0; i < Types.Length; i++)
|
|
{
|
|
Type t = Types[i];
|
|
var objs = ab.LoadAllAssets(t);
|
|
|
|
for (int i1 = 0; i1 < objs.Length; i1++)
|
|
{
|
|
UnityEngine.Object o = objs[i1];
|
|
ProcessNewItem(o, false, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load all items from the asset bundle into the index.
|
|
/// </summary>
|
|
/// <param name="ab"></param>
|
|
public void UnloadBundle(AssetBundle ab)
|
|
{
|
|
for (int i = 0; i < Types.Length; i++)
|
|
{
|
|
Type t = Types[i];
|
|
var objs = ab.LoadAllAssets(t);
|
|
|
|
for (int i1 = 0; i1 < objs.Length; i1++)
|
|
{
|
|
UnityEngine.Object o = objs[i1];
|
|
RemoveItem(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the given asset path resides in one of the given folder paths. Returns true if foldersToSearch is null or empty and no check is required
|
|
/// </summary>
|
|
private bool AssetFolderCheck(AssetItem itemToCheck, string[] foldersToSearch = null)
|
|
{
|
|
if (foldersToSearch == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (foldersToSearch.Length == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < foldersToSearch.Length; i++)
|
|
{
|
|
if (itemToCheck._Path.IndexOf(foldersToSearch[i]) > -1)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Addressables
|
|
|
|
#if UNITY_EDITOR
|
|
GameObject EditorUMAContextBase;
|
|
#endif
|
|
public UMAContextBase GetContext()
|
|
{
|
|
UMAContextBase instance = UMAContextBase.Instance;
|
|
if (instance != null)
|
|
{
|
|
return instance;
|
|
}
|
|
#if UNITY_EDITOR
|
|
//EditorUMAContextBase = UMAContextBase.CreateEditorContext();
|
|
return UMAContextBase.Instance;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
public void DestroyEditorUMAContextBase()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (EditorUMAContextBase != null)
|
|
{
|
|
foreach (Transform child in EditorUMAContextBase.transform)
|
|
{
|
|
DestroyImmediate(child.gameObject);
|
|
}
|
|
DestroyImmediate(EditorUMAContextBase);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if UMA_ADDRESSABLES
|
|
public string GetLabel(UMARecipeBase recipe)
|
|
{
|
|
return recipe.AssignedLabel;
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> PreloadWardrobe(DynamicCharacterAvatar avatar, bool keepLoaded = false)
|
|
{
|
|
List<string> keys = new List<string>();
|
|
RaceData race = GetAsset<RaceData>(avatar.activeRace.name);
|
|
|
|
// preload any assigned recipes.
|
|
foreach (var wr in avatar.WardrobeRecipes.Values)
|
|
{
|
|
//Debug.Log("Adding Wardrobe recipe: " + wr.name);
|
|
if (wr != null)
|
|
{
|
|
keys.Add(GetLabel(wr));
|
|
}
|
|
}
|
|
|
|
// preload any additive recipes.
|
|
foreach (var addList in avatar.AdditiveRecipes.Values)
|
|
{
|
|
if (addList != null)
|
|
{
|
|
foreach (var wr in addList)
|
|
{
|
|
if (wr != null)
|
|
{
|
|
keys.Add(GetLabel(wr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// preload utility recipes
|
|
foreach (var tr in avatar.umaAdditionalRecipes)
|
|
{
|
|
if (tr != null)
|
|
{
|
|
keys.Add(GetLabel(tr));
|
|
}
|
|
}
|
|
|
|
return LoadLabelList(keys, keepLoaded);
|
|
}
|
|
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> Preload(DynamicCharacterAvatar avatar, bool keepLoaded = false)
|
|
{
|
|
List<string> keys = new List<string>();
|
|
RaceData race = GetAsset<RaceData>(avatar.activeRace.name);
|
|
|
|
// preload the race
|
|
if (race != null)
|
|
{
|
|
if (race.baseRaceRecipe != null)
|
|
{
|
|
keys.Add(GetLabel(race.baseRaceRecipe));
|
|
}
|
|
}
|
|
|
|
|
|
// preload any assigned recipes.
|
|
foreach (var wr in avatar.WardrobeRecipes.Values)
|
|
{
|
|
if (wr != null)
|
|
{
|
|
keys.Add(GetLabel(wr));
|
|
}
|
|
}
|
|
|
|
foreach(var addList in avatar.AdditiveRecipes.Values)
|
|
{
|
|
if (addList != null)
|
|
{
|
|
foreach(var wr in addList)
|
|
{
|
|
if (wr != null)
|
|
{
|
|
keys.Add(GetLabel(wr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (avatar.umaAdditionalRecipes != null)
|
|
{
|
|
foreach (var tr in avatar.umaAdditionalRecipes)
|
|
{
|
|
if (tr != null)
|
|
{
|
|
keys.Add(GetLabel(tr));
|
|
}
|
|
}
|
|
}
|
|
var op = LoadLabelList(keys, keepLoaded);
|
|
return op;
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> Preload(RaceData theRace, bool keepLoaded = false)
|
|
{
|
|
return LoadLabel(GetLabel(theRace.baseRaceRecipe), keepLoaded);
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> Preload(List<RaceData> theRaces, bool keepLoaded = false)
|
|
{
|
|
List<string> keys = new List<string>();
|
|
foreach(RaceData rc in theRaces)
|
|
{
|
|
string key = GetLabel(rc.baseRaceRecipe);
|
|
|
|
if (keys.Contains(key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
keys.Add(key);
|
|
}
|
|
return LoadLabelList(keys, keepLoaded);
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> LoadLabel(string label, bool keepLoaded = false)
|
|
{
|
|
List<string> keys = new List<string>();
|
|
keys.Add(label);
|
|
return LoadLabelList(keys, keepLoaded);
|
|
}
|
|
|
|
|
|
public static string KeysToString(string msg, List<string> keys)
|
|
{
|
|
StringBuilder sb = new StringBuilder(msg);
|
|
sb.Append(String.Join("; ", keys));
|
|
return sb.ToString();
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> Preload(UMATextRecipe theRecipe, bool keepLoaded = false)
|
|
{
|
|
#if SUPER_LOGGING
|
|
Debug.Log("Preloading: " + theRecipe.name);
|
|
#endif
|
|
List<string> keys = new List<string>();
|
|
keys.Add(GetLabel(theRecipe));
|
|
return LoadLabelList(keys, keepLoaded);
|
|
}
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> Preload(List<UMATextRecipe> theRecipes, bool keepLoaded = false)
|
|
{
|
|
UMAContextBase context = UMAContextBase.Instance;
|
|
if (!context)
|
|
{
|
|
Debug.LogError("No context to preload!");
|
|
AsyncOperationHandle<IList<UnityEngine.Object>> ao = new AsyncOperationHandle<IList<UnityEngine.Object>>();
|
|
return ao;
|
|
}
|
|
|
|
List<string> Keys = new List<string>();
|
|
|
|
foreach (UMATextRecipe utr in theRecipes)
|
|
{
|
|
Keys.Add(GetLabel(utr));
|
|
}
|
|
|
|
return LoadLabelList(Keys,keepLoaded);
|
|
}
|
|
#if UNITY_EDITOR
|
|
async void ValidateSingleKey(string s)
|
|
{
|
|
var result = await Addressables.LoadResourceLocationsAsync(s).Task;
|
|
}
|
|
#endif
|
|
|
|
public AsyncOperationHandle<IList<UnityEngine.Object>> LoadLabelList(List<string> Keys, bool keepLoaded)
|
|
{
|
|
|
|
BeforeProcessingLabels.Invoke(Keys);
|
|
|
|
foreach (string label in Keys)
|
|
{
|
|
if (!Preloads.ContainsKey(label))
|
|
{
|
|
Preloads[label] = keepLoaded;
|
|
}
|
|
else
|
|
{
|
|
if (keepLoaded) // only overwrite if keepLoaded = true. All "keepLoaded" take precedence.
|
|
{
|
|
Preloads[label] = keepLoaded;
|
|
}
|
|
}
|
|
}
|
|
|
|
var op = Addressables.LoadAssetsAsync<UnityEngine.Object>(Keys, result =>
|
|
{
|
|
// The last items is now passed here AFTER the completed event, breaking everything.
|
|
// change to event model here.
|
|
}, Addressables.MergeMode.Union, true);
|
|
if (op.Status == AsyncOperationStatus.Failed)
|
|
{
|
|
if (op.OperationException is InvalidKeyException exk)
|
|
{
|
|
string badMessage = "Resources for the following recipes cannot be loaded from the Addressables System: ";
|
|
if (exk.Key is List<string> badKeys && badKeys.Count > 0)
|
|
{
|
|
throw new UMAInvalidKeyException(badMessage+KeysToString(badMessage,badKeys), badKeys);
|
|
}
|
|
else
|
|
{
|
|
badMessage = "Resources for the following recipes cannot be loaded from the Addressables System: "+exk.Key.ToString()+" - " + KeysToString("Resource Keys = ",Keys);
|
|
throw new UMAInvalidKeyException(badMessage, exk.Key as List<string>);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (op.OperationException != null)
|
|
{
|
|
throw new Exception("An exception of type: " + op.OperationException.GetType().ToString() + " was thrown while loading recipes from the Addressables system. Message is: " + op.OperationException.Message);
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Addressables call failed but an exception was not specified.");
|
|
}
|
|
}
|
|
}
|
|
op.Completed += ProcessItems;
|
|
if (!keepLoaded)
|
|
{
|
|
string info = "";
|
|
foreach (string s in Keys)
|
|
{
|
|
info += s + "; ";
|
|
}
|
|
|
|
LoadedItems.Add(new CachedOp(op, info));
|
|
}
|
|
return op;
|
|
}
|
|
|
|
// It appears that Addressables can now call this function on an invalid result.
|
|
// We need to ensure that the operation succeeded, and that the result value is not null
|
|
private void ProcessItems(AsyncOp Op)
|
|
{
|
|
if (Op.IsDone && Op.Status == AsyncOperationStatus.Succeeded)
|
|
{
|
|
if (Op.Result != null)
|
|
{
|
|
foreach (var o in Op.Result)
|
|
{
|
|
ProcessNewItem(o, true, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
private void RemoveItem(UnityEngine.Object ob)
|
|
{
|
|
if (!IsIndexedType(ob.GetType()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
System.Type ot = ob.GetType();
|
|
System.Type theType = TypeToLookup[ot];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
AssetItem ai = null;
|
|
string Name = AssetItem.GetEvilName(ob);
|
|
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
ai = TypeDic[Name];
|
|
TypeDic.Remove(Name);
|
|
}
|
|
if (GuidTypes.ContainsKey(Name))
|
|
{
|
|
GuidTypes.Remove(Name);
|
|
}
|
|
#if UNITY_EDITOR
|
|
if (ai != null)
|
|
{
|
|
SerializedItems.Remove(ai);
|
|
}
|
|
ForceSave();
|
|
RebuildIndex();
|
|
#endif
|
|
}
|
|
|
|
public void ProcessNewItem(UnityEngine.Object result, bool isAddressable, bool keepLoaded)
|
|
{
|
|
if (!IsIndexedType(result.GetType())) // JRRM
|
|
{
|
|
return;
|
|
}
|
|
|
|
AssetItem resultItem = GetAssetItemForObject(result);
|
|
if (resultItem == null)
|
|
{
|
|
resultItem = new AssetItem(result.GetType(), result);
|
|
resultItem.IsAddressable = isAddressable;
|
|
resultItem.IsAlwaysLoaded = keepLoaded;
|
|
AddAssetItem(resultItem);
|
|
|
|
resultItem._SerializedItem = result;
|
|
resultItem.AddReference();
|
|
}
|
|
else
|
|
{
|
|
if (keepLoaded)
|
|
{
|
|
resultItem.IsAlwaysLoaded = keepLoaded;
|
|
}
|
|
|
|
resultItem._SerializedItem = result;
|
|
resultItem.AddReference();
|
|
}
|
|
|
|
if (result is UMAMaterial um)
|
|
{
|
|
if (um.material.shader == null)
|
|
{
|
|
// if the shader has been stripped, then we need to reset it.
|
|
um.material.shader = Shader.Find(um.ShaderName);
|
|
}
|
|
}
|
|
if (result is UMAWardrobeRecipe)
|
|
{
|
|
AddRaceRecipe(result as UMAWardrobeRecipe);
|
|
}
|
|
else if (result is SlotDataAsset)
|
|
{
|
|
SlotDataAsset sd = result as SlotDataAsset;
|
|
if (sd.material == null)
|
|
{
|
|
if (!string.IsNullOrEmpty(sd.materialName))
|
|
{
|
|
sd.material = Instance.GetAsset<UMAMaterial>(sd.materialName);
|
|
}
|
|
}
|
|
}
|
|
else if (result is OverlayDataAsset)
|
|
{
|
|
OverlayDataAsset od = result as OverlayDataAsset;
|
|
if (od.material == null)
|
|
{
|
|
if (!string.IsNullOrEmpty(od.materialName))
|
|
{
|
|
od.material = Instance.GetAsset<UMAMaterial>(od.materialName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void PostBuildMaterialFixup()
|
|
{
|
|
#if UNITY_EDITOR
|
|
var slots = GetAllAssets<SlotDataAsset>();
|
|
var overlays = GetAllAssets<OverlayDataAsset>();
|
|
var umaMaterials = GetAllAssets<UMAMaterial>();
|
|
|
|
// if we stripped the shaders from the materials, we need to look them up
|
|
// and reassign them here.
|
|
for (int i = 0; i < umaMaterials.Count; i++)
|
|
{
|
|
UMAMaterial um = umaMaterials[i];
|
|
if (um.material == null)
|
|
{
|
|
if (!string.IsNullOrEmpty(um.MaterialName))
|
|
{
|
|
var guids = AssetDatabase.FindAssets("t:Material " + um.MaterialName);
|
|
if (guids != null && guids.Length > 0)
|
|
{
|
|
string assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
|
|
um.material = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
|
|
EditorUtility.SetDirty(um);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (um.material != null && um.material.shader == null)
|
|
{
|
|
um.material.shader = Shader.Find(um.ShaderName);
|
|
if (um.material.shader == null)
|
|
{
|
|
Debug.LogError("Unable to find shader " + um.ShaderName + " on UMAMaterial " + um.name);
|
|
}
|
|
else
|
|
{
|
|
// Shader was found. We need to resave the material with the correct shader
|
|
EditorUtility.SetDirty(um);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < slots.Count; i++)
|
|
{
|
|
SlotDataAsset sd = slots[i];
|
|
if (sd.material == null)
|
|
{
|
|
if (!string.IsNullOrEmpty(sd.materialName))
|
|
{
|
|
sd.material = Instance.GetAsset<UMAMaterial>(sd.materialName);
|
|
|
|
if (sd.material == null)
|
|
{
|
|
Debug.LogWarning("Unable to find material '" + sd.materialName + "' for slot: " + sd.name);
|
|
}
|
|
EditorUtility.SetDirty(sd);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Material name is null on slot: " + sd.name);
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < overlays.Count; i++)
|
|
{
|
|
OverlayDataAsset od = overlays[i];
|
|
if (od.material == null)
|
|
{
|
|
if (!string.IsNullOrEmpty(od.materialName))
|
|
{
|
|
od.material = Instance.GetAsset<UMAMaterial>(od.materialName);
|
|
if (od.material == null)
|
|
{
|
|
Debug.LogWarning("Unable to find material '" + od.materialName + "' for overlay: " + od.name);
|
|
}
|
|
EditorUtility.SetDirty(od);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Material name is null on overlay: " + od.name);
|
|
}
|
|
}
|
|
}
|
|
ForceSave();
|
|
#endif
|
|
}
|
|
#if UMA_ADDRESSABLES
|
|
public void Unload(AsyncOperationHandle<IList<UnityEngine.Object>> AssetOperation)
|
|
{
|
|
#if SUPER_LOGGING
|
|
Debug.Log("Unloading AsyncOperationHandle<> in Indexer.Unload()");
|
|
#endif
|
|
foreach(UnityEngine.Object obj in AssetOperation.Result)
|
|
{
|
|
ReleaseReference(obj);
|
|
}
|
|
Addressables.Release(AssetOperation);
|
|
LoadedItems.RemoveAll(x => x.Operation.Equals(AssetOperation));
|
|
}
|
|
|
|
public void UnloadAll(bool forceResourceUnload)
|
|
{
|
|
|
|
foreach (CachedOp op in LoadedItems)
|
|
{
|
|
Addressables.Release(op.Operation);
|
|
}
|
|
Dictionary<string, AssetItem> SlotDic = GetAssetDictionary(typeof(SlotDataAsset));
|
|
Dictionary<string, AssetItem> OverlayDic = GetAssetDictionary(typeof(OverlayDataAsset));
|
|
|
|
foreach (AssetItem ai in SlotDic.Values)
|
|
{
|
|
if ((ai._SerializedItem != null && ai.IsAddressable && ai.IsAlwaysLoaded == false) || ai.Ignore)
|
|
{
|
|
ai.ReleaseItem();
|
|
ai.ReferenceCount = 0;
|
|
}
|
|
}
|
|
|
|
// Preloads is tracking if a loaded item is "keep" or not.
|
|
// After freeing everything, we really only need to know about the "keeps".
|
|
// This is necessary, because it's possible to request to "keep" something in one call
|
|
// and NOT keep it in another call. In this case, the previous "Keep" needs to be kept, so
|
|
// we can honor the keep.
|
|
//
|
|
// cheesiest cheap way to clear the Preloads
|
|
Dictionary<string, bool> newPreloads = new Dictionary<string, bool>();
|
|
foreach(KeyValuePair<string,bool> kvp in Preloads)
|
|
{
|
|
if (kvp.Value == true)
|
|
{
|
|
newPreloads.Add(kvp.Key, kvp.Value);
|
|
}
|
|
}
|
|
Preloads = newPreloads;
|
|
|
|
foreach (AssetItem ai in OverlayDic.Values)
|
|
{
|
|
if ((ai._SerializedItem != null && ai.IsAddressable && ai.IsAlwaysLoaded == false) || ai.Ignore)
|
|
{
|
|
ai.ReleaseItem();
|
|
ai.ReferenceCount = 0;
|
|
}
|
|
}
|
|
LoadedItems.Clear();
|
|
if (forceResourceUnload)
|
|
{
|
|
Resources.UnloadUnusedAssets();
|
|
}
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
#region Add Remove Assets
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
public void AddIfIndexed(UnityEngine.Object o)
|
|
{
|
|
System.Type type = o.GetType();
|
|
if (IsIndexedType(type))
|
|
{
|
|
EvilAddAsset(type, o);
|
|
}
|
|
}
|
|
|
|
public void RemoveIfIndexed(UnityEngine.Object o)
|
|
{
|
|
RemoveAsset(o.GetType(), AssetItem.GetEvilName(o));
|
|
}
|
|
|
|
public void RecursiveScanFoldersForAssets(string path)
|
|
{
|
|
var assetFiles = System.IO.Directory.GetFiles(path);
|
|
|
|
for (int i = 0; i < assetFiles.Length; i++)
|
|
{
|
|
string assetFile = assetFiles[i];
|
|
string Extension = System.IO.Path.GetExtension(assetFile).ToLower();
|
|
if (Extension == ".asset" || Extension == ".controller" || Extension == ".txt")
|
|
{
|
|
UnityEngine.Object o = AssetDatabase.LoadMainAssetAtPath(assetFile);
|
|
|
|
if (o)
|
|
{
|
|
AddIfIndexed(o);
|
|
}
|
|
}
|
|
}
|
|
string[] array = System.IO.Directory.GetDirectories(path);
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
string subFolder = array[i];
|
|
RecursiveScanFoldersForAssets(subFolder.Replace('\\', '/'));
|
|
}
|
|
}
|
|
|
|
public void RecursiveScanFoldersForRemovingAssets(string path)
|
|
{
|
|
var assetFiles = System.IO.Directory.GetFiles(path);
|
|
|
|
for (int i = 0; i < assetFiles.Length; i++)
|
|
{
|
|
string assetFile = assetFiles[i];
|
|
string Extension = System.IO.Path.GetExtension(assetFile).ToLower();
|
|
if (Extension == ".asset" || Extension == ".controller" || Extension == ".txt")
|
|
{
|
|
UnityEngine.Object o = AssetDatabase.LoadMainAssetAtPath(assetFile);
|
|
|
|
if (o)
|
|
{
|
|
RemoveIfIndexed(o);
|
|
}
|
|
}
|
|
}
|
|
string[] array = System.IO.Directory.GetDirectories(path);
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
string subFolder = array[i];
|
|
RecursiveScanFoldersForRemovingAssets(subFolder.Replace('\\', '/'));
|
|
}
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Adds an asset to the index. Does NOT save the asset! you must do that separately.
|
|
/// </summary>
|
|
/// <param name="type">System Type of the object to add.</param>
|
|
/// <param name="name">Name for the object.</param>
|
|
/// <param name="path">Path to the object.</param>
|
|
/// <param name="o">The Object to add.</param>
|
|
/// <param name="skipBundleCheck">Option to skip checking Asset Bundles.</param>
|
|
public void AddAsset(System.Type type, string name, string path, UnityEngine.Object o)
|
|
{
|
|
if (o == null)
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.Log("Skipping null item");
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (type == null)
|
|
{
|
|
type = o.GetType();
|
|
}
|
|
|
|
AssetItem ai = new AssetItem(type, name, path, o);
|
|
AddAssetItem(ai);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an asset to the index. If the name already exists, it is not added. (Should we do this, or replace it?)
|
|
/// </summary>
|
|
/// <param name="ai"></param>
|
|
/// <param name="SkipBundleCheck"></param>
|
|
/// <returns>Whether the asset was added or not.</returns>
|
|
private bool AddAssetItem(AssetItem ai)
|
|
{
|
|
try
|
|
{
|
|
if (!TypeToLookup.ContainsKey(ai._Type))
|
|
{
|
|
Debug.LogError("Unable to get Lookup Type for Type: " + ai._Type.ToString() + " for Object " + ai._Name);
|
|
return false;
|
|
}
|
|
|
|
System.Type theType = TypeToLookup[ai._Type];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
if (TypeDic == null)
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.Log("Unable to add asset item!. Unable to get Type Dictionary of type " + theType.ToString() + "For object " + ai._Name);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Get out if we already have it.
|
|
if (TypeDic.ContainsKey(ai._Name))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ai._Name.ToLower().Contains((ai._Type.Name + "placeholder").ToLower()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ai._Type == typeof(UMAWardrobeRecipe))
|
|
{
|
|
AddToRaceLookup(ai._SerializedItem as UMAWardrobeRecipe);
|
|
}
|
|
|
|
string Key = ai._Name.ToLowerInvariant() + "." + ai._Type.ToString();
|
|
|
|
if (!LowerCaseLookup.ContainsKey(Key))
|
|
{
|
|
LowerCaseLookup.Add(Key, ai._Name);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (string.IsNullOrWhiteSpace(ai._Name))
|
|
{
|
|
throw new Exception("Invalid name on Asset type " + ai._Type.ToString() + " - asset is: " + ai.Item.name);
|
|
}
|
|
if (ai.IsAddressable || ai.Ignore)
|
|
{
|
|
ai._SerializedItem = null;
|
|
}
|
|
#if UMA_ADDRESSABLES
|
|
AddressableInfo ainfo = AddressableUtility.GetAddressableInfo(ai._Path);
|
|
if (ainfo != null)
|
|
{
|
|
ai.IsAddressable = true;
|
|
ai.AddressableAddress = ainfo.AddressableAddress;
|
|
ai.AddressableGroup = ainfo.AddressableGroup;
|
|
ai.AddressableLabels = ainfo.AddressableLabels;
|
|
}
|
|
#endif
|
|
if (!string.IsNullOrEmpty(ai._Guid))
|
|
{
|
|
if (!GuidTypes.ContainsKey(ai._Guid))
|
|
{
|
|
GuidTypes.Add(ai._Guid, ai);
|
|
}
|
|
}
|
|
#endif
|
|
if (ai._SerializedItem != null)
|
|
{
|
|
if (ai._SerializedItem is IUMAIndexOptions)
|
|
{
|
|
var iso = ai._SerializedItem as IUMAIndexOptions;
|
|
if (iso.ForceKeep)
|
|
{
|
|
ai.IsAlwaysLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!TypeDic.ContainsKey(ai._Name))
|
|
{
|
|
TypeDic.Add(ai._Name, ai);
|
|
}
|
|
else
|
|
{
|
|
// New: update existing items. This will allow for mods.
|
|
TypeDic[ai._Name] = ai;
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
UnityEngine.Debug.LogWarning("Exception in UMAAssetIndexer.AddAssetItem: " + ex.StackTrace);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// If we added a new AssetItem that is a Wardrobe Recipe, then it needs to be added to the tables.
|
|
/// </summary>
|
|
/// <param name="uwr"></param>
|
|
private void AddToRaceLookup(UMAWardrobeRecipe uwr)
|
|
{
|
|
if (uwr == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < uwr.compatibleRaces.Count; i++)
|
|
{
|
|
string raceName = uwr.compatibleRaces[i];
|
|
if (!raceRecipes.ContainsKey(raceName))
|
|
{
|
|
raceRecipes.Add(raceName, new SlotRecipes());
|
|
}
|
|
SlotRecipes sl = raceRecipes[raceName];
|
|
if (!sl.ContainsKey(uwr.wardrobeSlot))
|
|
{
|
|
sl.Add(uwr.wardrobeSlot, new List<UMATextRecipe>());
|
|
}
|
|
List<UMATextRecipe> recipes = sl[uwr.wardrobeSlot];
|
|
if (recipes.Contains(uwr)) // I'm hoping this function isn't called much outside of updates, editor.
|
|
{
|
|
continue;
|
|
}
|
|
|
|
recipes.Add(uwr);
|
|
}
|
|
}
|
|
|
|
public void ClearItem(UnityEngine.Object obj)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// releases an asset an asset reference
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="Name"></param>
|
|
public void ReleaseReference(UnityEngine.Object obj)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string Name = AssetItem.GetEvilName(obj);
|
|
|
|
// Leave if this is an unreferenced type - for example, a texture (etc).
|
|
// This can happen because these are referenced by the Overlay.
|
|
if (!TypeToLookup.ContainsKey(obj.GetType()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
System.Type theType = TypeToLookup[obj.GetType()];
|
|
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
AssetItem ai = TypeDic[Name];
|
|
ai.FreeReference();
|
|
}
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
public AssetItem FromGuid(string GUID)
|
|
{
|
|
if (GuidTypes.ContainsKey(GUID))
|
|
{
|
|
return GuidTypes[GUID];
|
|
}
|
|
return null;
|
|
}
|
|
/// <summary>
|
|
/// This is the evil version of AddAsset. This version cares not for the good of the project, nor
|
|
/// does it care about readability, expandibility, and indeed, hates goodness with every beat of it's
|
|
/// tiny evil shrivelled heart.
|
|
/// I started going down the good path - I created an interface to get the name info, added it to all the
|
|
/// classes. Then we ran into RuntimeAnimatorController. I would have had to wrap it. And Visual Studio kept
|
|
/// complaining about the interface, even though Unity thought it was OK.
|
|
///
|
|
/// So in the end, good was defeated. And would never raise it's sword in the pursuit of chivalry again.
|
|
///
|
|
/// And EvilAddAsset doesn't save either. You have to do that manually.
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="o"></param>
|
|
/// <returns>Whether the Asset was added or not.</returns>
|
|
public bool EvilAddAsset(System.Type type, UnityEngine.Object o)
|
|
{
|
|
AssetItem ai = null;
|
|
ai = new AssetItem(TypeToLookup[type], o);
|
|
ai._Path = AssetDatabase.GetAssetPath(o.GetInstanceID());
|
|
return AddAssetItem(ai);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Removes an asset from the index
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="Name"></param>
|
|
public void RemoveAsset(System.Type type, string Name, bool refresh = true)
|
|
{
|
|
System.Type theType = TypeToLookup[type];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
AssetItem ai = TypeDic[Name];
|
|
TypeDic.Remove(Name);
|
|
if (GuidTypes.ContainsKey(ai._Guid))
|
|
{
|
|
GuidTypes.Remove(ai._Guid);
|
|
}
|
|
|
|
SerializedItems.Remove(ai);
|
|
if (theType == typeof(UMAWardrobeRecipe))
|
|
{
|
|
// remove it from the race lookup.
|
|
foreach (SlotRecipes sl in raceRecipes.Values)
|
|
{
|
|
foreach (List<UMATextRecipe> recipes in sl.Values)
|
|
{
|
|
recipes.Remove(ai.Item as UMATextRecipe);
|
|
}
|
|
}
|
|
}
|
|
if (refresh)
|
|
{
|
|
ForceSave();
|
|
RebuildIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Permanently delete the item from the filesystem.
|
|
public void DeleteAsset(System.Type type, string Name)
|
|
{
|
|
System.Type theType = TypeToLookup[type];
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(theType);
|
|
if (TypeDic.ContainsKey(Name))
|
|
{
|
|
AssetItem ai = TypeDic[Name];
|
|
TypeDic.Remove(Name);
|
|
if (GuidTypes.ContainsKey(ai._Guid))
|
|
{
|
|
GuidTypes.Remove(ai._Guid);
|
|
}
|
|
if (theType == typeof(UMAWardrobeRecipe))
|
|
{
|
|
// remove it from the race lookup.
|
|
foreach (SlotRecipes sl in raceRecipes.Values)
|
|
{
|
|
foreach (List<UMATextRecipe> recipes in sl.Values)
|
|
{
|
|
recipes.Remove(ai.Item as UMATextRecipe);
|
|
}
|
|
}
|
|
}
|
|
File.Delete(ai._Path);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#endregion
|
|
|
|
#region Maintenance
|
|
|
|
#if UNITY_EDITOR
|
|
public void ClearAddressableFlags()
|
|
{
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai.IsAddressable = false;
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
ForceSave();
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Updates the dictionaries from this list.
|
|
/// Used when restoring items after modification, or after deserialization.
|
|
/// </summary>
|
|
public void UpdateSerializedDictionaryItems()
|
|
{
|
|
DebugSerialization("Updating serialized Dictionary Items");
|
|
// Rebuuild all the lookup tables
|
|
// Lookup by guid
|
|
GuidTypes = new Dictionary<string, AssetItem>();
|
|
// Lookup by type, object name
|
|
for (int i = 0; i < Types.Length; i++)
|
|
{
|
|
Type type = Types[i];
|
|
CreateLookupDictionary(type);
|
|
}
|
|
// Lookup actual name from lowercase name.
|
|
LowerCaseLookup = new Dictionary<string, string>();
|
|
DebugSerialization($"Adding Items from SerializedItems - size is {SerializedItems.Count}");
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
// We null things out when we want to delete them. This prevents it from going back into
|
|
// the dictionary when rebuilt.
|
|
if (ai == null)
|
|
{
|
|
DebugSerialization("Skipping null item in SerializedItems");
|
|
continue;
|
|
}
|
|
DebugSerialization($"Adding item {ai._Name}");
|
|
AddAssetItem(ai);
|
|
}
|
|
DebugSerialization("All items added");
|
|
}
|
|
|
|
class recipeEqualityComparer : IEqualityComparer<UMAWardrobeRecipe>
|
|
{
|
|
public bool Equals(UMAWardrobeRecipe b1, UMAWardrobeRecipe b2)
|
|
{
|
|
if (b2 == null && b1 == null)
|
|
{
|
|
return true;
|
|
}
|
|
else if (b1 == null || b2 == null)
|
|
{
|
|
return false;
|
|
}
|
|
else if (b1.name == b2.name)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int GetHashCode(UMAWardrobeRecipe bx)
|
|
{
|
|
return bx.GetHashCode();
|
|
}
|
|
}
|
|
|
|
private recipeEqualityComparer req;
|
|
|
|
private void AddRaceRecipe(UMAWardrobeRecipe uwr)
|
|
{
|
|
if (!uwr)
|
|
{
|
|
return;
|
|
}
|
|
// if (req == null)
|
|
// req = new recipeEqualityComparer();
|
|
|
|
for (int i = 0; i < uwr.compatibleRaces.Count; i++)
|
|
{
|
|
string racename = uwr.compatibleRaces[i];
|
|
if (!raceRecipes.ContainsKey(racename))
|
|
{
|
|
raceRecipes.Add(racename, new SlotRecipes());
|
|
}
|
|
SlotRecipes sl = raceRecipes[racename];
|
|
if (!sl.ContainsKey(uwr.wardrobeSlot))
|
|
{
|
|
sl.Add(uwr.wardrobeSlot, new List<UMATextRecipe>());
|
|
}
|
|
if (!sl[uwr.wardrobeSlot].Contains(uwr))//, req))
|
|
{
|
|
sl[uwr.wardrobeSlot].Add(uwr);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RebuildRaceRecipes()
|
|
{
|
|
//Dictionary<string, RaceData> RaceLookup = new Dictionary<string, RaceData>();
|
|
|
|
List<RaceData> races = GetAllAssets<RaceData>();
|
|
|
|
/// Build Race Recipes and RaceLookup
|
|
raceRecipes.Clear();
|
|
|
|
/// Add all the directly assigned items.
|
|
var wardrobe = GetAllAssets<UMAWardrobeRecipe>();
|
|
|
|
for (int i = 0; i < wardrobe.Count; i++)
|
|
{
|
|
UMAWardrobeRecipe uwr = wardrobe[i];
|
|
AddRaceRecipe(uwr);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a lookup dictionary for a list. Used when reloading after deserialization
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
private void CreateLookupDictionary(System.Type type)
|
|
{
|
|
DebugSerialization($"Creating lookup dictionary for type: {type.ToString()}");
|
|
Dictionary<string, AssetItem> dic = new Dictionary<string, AssetItem>();
|
|
if (TypeLookup.ContainsKey(type))
|
|
{
|
|
DebugSerialization($"Dictionary already exists for type: {type.ToString()}");
|
|
TypeLookup[type] = dic;
|
|
}
|
|
else
|
|
{
|
|
DebugSerialization($"Dictionary did not exist for type: {type.ToString()}");
|
|
TypeLookup.Add(type, dic);
|
|
}
|
|
}
|
|
|
|
private void DebugSerialization(string s)
|
|
{
|
|
#if DEBUG_SERIALIZATION
|
|
Debug.Log("[Serializing] "+s);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the list so all items can be processed at once, or for
|
|
/// serialization.
|
|
/// </summary>
|
|
public List<AssetItem> UpdateSerializedList()
|
|
{
|
|
DebugSerialization("Clearing Serialized Items");
|
|
SerializedItems.Clear();
|
|
DebugSerialization("Adding items to serialized list");
|
|
foreach (System.Type type in TypeToLookup.Keys)
|
|
{
|
|
if (type == TypeToLookup[type])
|
|
{
|
|
DebugSerialization($"Adding type to serialized list {type.ToString()}");
|
|
Dictionary<string, AssetItem> TypeDic = GetAssetDictionary(type);
|
|
if (TypeDic != null)
|
|
{
|
|
foreach (AssetItem ai in TypeDic.Values)
|
|
{
|
|
if (ai.IsAddressable || ai.Ignore)
|
|
{
|
|
ai._SerializedItem = null;
|
|
}
|
|
SerializedItems.Add(ai);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugSerialization($"Type dictionary for type is NULL");
|
|
}
|
|
}
|
|
}
|
|
return SerializedItems;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a list of types and a string to look them up.
|
|
/// </summary>
|
|
public void BuildStringTypes()
|
|
{
|
|
TypeFromString.Clear();
|
|
for (int i = 0; i < Types.Length; i++)
|
|
{
|
|
Type st = Types[i];
|
|
TypeFromString.Add(st.Name, st);
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private List<AssetItem> Keeps = new List<AssetItem>();
|
|
|
|
public void SaveKeeps()
|
|
{
|
|
Keeps.Clear();
|
|
UpdateSerializedList();
|
|
|
|
foreach (AssetItem ai in SerializedItems)
|
|
{
|
|
if (ai.IsAlwaysLoaded)
|
|
{
|
|
Keeps.Add(ai);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RestoreKeeps()
|
|
{
|
|
foreach (AssetItem ai in Keeps)
|
|
{
|
|
AssetItem assetItem = GetAssetItem(ai._Type, ai._Name);
|
|
if (assetItem != null)
|
|
{
|
|
assetItem.IsAlwaysLoaded = true;
|
|
}
|
|
}
|
|
Keeps.Clear();
|
|
}
|
|
|
|
public void AddEverything(bool includeText)
|
|
{
|
|
Clear(false);
|
|
|
|
List<string> types = new List<string>();
|
|
types.AddRange(TypeFromString.Keys);
|
|
|
|
for (int i = 0; i < types.Count; i++)
|
|
{
|
|
string s = types[i];
|
|
System.Type CurrentType = TypeFromString[s];
|
|
if (!includeText)
|
|
{
|
|
if (IsText(CurrentType))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
List<string> FolderFilter = null;
|
|
if (TypeFolderSearch.ContainsKey(s))
|
|
{
|
|
FolderFilter = TypeFolderSearch[s];
|
|
}
|
|
|
|
// AnimatorController and AnimatorOverrideController are processed as "RuntimeAnimatorController"
|
|
if (s != "AnimatorController" && s != "AnimatorOverrideController")
|
|
{
|
|
AddType(s, CurrentType, FolderFilter);
|
|
}
|
|
}
|
|
ForceSave();
|
|
}
|
|
|
|
private void AddType(string s, Type CurrentType, List<string> FolderFilter)
|
|
{
|
|
string[] guids = AssetDatabase.FindAssets("t:" + s);
|
|
for (int i = 0; i < guids.Length; i++)
|
|
{
|
|
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
|
|
|
|
// IF we have filters
|
|
if (FolderFilter != null && FolderFilter.Count > 0)
|
|
{
|
|
// IF the assetpath contains any of the filters, then it passed.
|
|
// we will add it.
|
|
// otherwise, go on to the next asset
|
|
bool filterPassed = false;
|
|
string fixedPath = assetPath.Replace("\\", "/").ToLowerInvariant();
|
|
|
|
|
|
for (int i1 = 0; i1 < FolderFilter.Count; i1++)
|
|
{
|
|
string fldr = FolderFilter[i1];
|
|
string fixedfldr = fldr.Replace("\\", "/").ToLowerInvariant();
|
|
if (fixedPath.Contains(fixedfldr))
|
|
{
|
|
filterPassed = true;
|
|
}
|
|
}
|
|
if (!filterPassed)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
string fileName = Path.GetFileName(assetPath);
|
|
EditorUtility.DisplayProgressBar("Adding Items to Global Library.", fileName, ((float)i / (float)guids.Length));
|
|
|
|
if (assetPath.ToLower().Contains(".shader"))
|
|
{
|
|
continue;
|
|
}
|
|
UnityEngine.Object o = AssetDatabase.LoadAssetAtPath(assetPath, CurrentType);
|
|
if (o != null)
|
|
{
|
|
if (SkipDuplicateType(o, CurrentType))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AssetItem ai = new AssetItem(CurrentType, o);
|
|
AddAssetItem(ai);
|
|
}
|
|
else
|
|
{
|
|
if (assetPath == null)
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.LogWarning("Cannot instantiate item " + guids[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.LogWarning("Cannot instantiate item " + assetPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
|
|
private static bool IsText(Type CurrentType)
|
|
{
|
|
return CurrentType == typeof(TextAsset);
|
|
}
|
|
|
|
private bool SkipDuplicateType(UnityEngine.Object o, Type currentType)
|
|
{
|
|
if (o.GetType() == typeof(UMAWardrobeRecipe) && currentType == typeof(UMATextRecipe))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (o.GetType() == typeof(UMAWardrobeCollection) && currentType == typeof(UMATextRecipe))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (o.GetType() == typeof(UMAWardrobeCollection) && currentType == typeof(UMAWardrobeRecipe))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the index
|
|
/// </summary>
|
|
public void Clear(bool forceSave = true)
|
|
{
|
|
// Rebuild the tables
|
|
GuidTypes.Clear();
|
|
ClearReferences();
|
|
SerializedItems.Clear();
|
|
UpdateSerializedDictionaryItems();
|
|
if (forceSave)
|
|
{
|
|
ForceSave();
|
|
}
|
|
}
|
|
|
|
|
|
public bool IsRemoveableItem(AssetItem ai)
|
|
{
|
|
if (ai._SerializedItem != null)
|
|
{
|
|
if (ai._SerializedItem.GetType() == typeof(SlotDataAsset))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (ai._SerializedItem.GetType() == typeof(OverlayDataAsset))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Adds references to all items by accessing the item property.
|
|
/// This forces Unity to load the item and return a reference to it.
|
|
/// When building, Unity needs the references to the items because we
|
|
/// cannot demand load them without the AssetDatabase.
|
|
/// </summary>
|
|
public void AddReferences()
|
|
{
|
|
// Rebuild the tables
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
if (ai.IsAddressable || ai.Ignore)
|
|
{
|
|
ai.FreeReference();
|
|
}
|
|
else
|
|
{
|
|
ai.CacheSerializedItem();
|
|
}
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
ForceSave();
|
|
}
|
|
|
|
public void UpdateReferences()
|
|
{
|
|
// Rebuild the tables
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
if (ai.IsAddressable || ai.Ignore)
|
|
{
|
|
ai.FreeReference();
|
|
}
|
|
else
|
|
{
|
|
ai.CacheSerializedItem();
|
|
}
|
|
}
|
|
ForceSave();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This releases items by dereferencing them so they can be
|
|
/// picked up by garbage collection.
|
|
/// This also makes working with the index much faster.
|
|
/// </summary>
|
|
public void ClearReferences()
|
|
{
|
|
// Rebuild the tables
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai.FreeReference();
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
ForceSave();
|
|
Resources.UnloadUnusedAssets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This releases items by dereferencing them so they can be
|
|
/// picked up by garbage collection.
|
|
/// This also makes working with the index much faster.
|
|
/// </summary>
|
|
public void RemoveReferences()
|
|
{
|
|
// Rebuild the tables
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai.FreeReference();
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
ForceSave();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Repairs the index. Removes anything that it cannot find.
|
|
/// </summary>
|
|
public void RepairAndCleanup()
|
|
{
|
|
// Rebuild the tables
|
|
UpdateSerializedList();
|
|
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai.IsAddressable = false;
|
|
ai.AddressableLabels = "";
|
|
ai.AddressableGroup = "";
|
|
ai.AddressableAddress = "";
|
|
#if UNITY_EDITOR
|
|
#if UMA_ADDRESSABLES
|
|
AddressableInfo ainfo = AddressableUtility.GetAddressableInfo(ai._Path);
|
|
if (ainfo != null)
|
|
{
|
|
ai.AddressableAddress = ainfo.AddressableAddress;
|
|
ai.IsAddressable = true;
|
|
ai.AddressableGroup = ainfo.AddressableGroup;
|
|
ai._SerializedItem = null;
|
|
ai.AddressableLabels = ainfo.AddressableLabels;
|
|
}
|
|
else
|
|
#endif
|
|
#endif
|
|
|
|
if (!ai.IsAssetBundle)
|
|
{
|
|
// If we already have a reference to the item, let's verify that everything is correct on it.
|
|
UnityEngine.Object obj = ai.Item;
|
|
if (obj != null)
|
|
{
|
|
ai._Name = ai.EvilName;
|
|
ai._Path = AssetDatabase.GetAssetPath(obj.GetInstanceID());
|
|
ai._Guid = AssetDatabase.AssetPathToGUID(ai._Path);
|
|
}
|
|
else
|
|
{
|
|
// Clear out the item reference so we will attempt to fix it if it's broken.
|
|
ai._SerializedItem = null;
|
|
// This will attempt to load the item, using the path, guid or name (in that order).
|
|
// This is in case we didn't have a reference to the item, and it was moved
|
|
ai.CacheSerializedItem();
|
|
// If an item can't be found and we didn't ahve a reference to it, then we need to delete it.
|
|
if (ai._SerializedItem == null)
|
|
{
|
|
// Can't be found or loaded
|
|
// null it out, so it doesn't get added back.
|
|
SerializedItems[i] = null;
|
|
}
|
|
ai.FreeReference();
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateSerializedDictionaryItems();
|
|
RebuildRaceRecipes();
|
|
ForceSave();
|
|
}
|
|
|
|
#endif
|
|
/// <summary>
|
|
/// returns the entire lookup dictionary for a specific type.
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, AssetItem> GetAssetDictionary(System.Type type)
|
|
{
|
|
System.Type LookupType = TypeToLookup[type];
|
|
if (TypeLookup.ContainsKey(LookupType) == false)
|
|
{
|
|
TypeLookup[LookupType] = new Dictionary<string, AssetItem>();
|
|
}
|
|
return TypeLookup[LookupType];
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// Heals the index if possible, if not rebuilds
|
|
/// </summary>
|
|
public void HealIndex(bool AlwaysRebuild = false)
|
|
{
|
|
// do not heal in the editor if we are playing.
|
|
if (Application.isPlaying == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Debug.Log("Healing index...");
|
|
if (!AlwaysRebuild)
|
|
{
|
|
// See if we can shortcut
|
|
if (SerializedItems.Count > 0)
|
|
{
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai._Name = ai.EvilName;
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
return;
|
|
}
|
|
}
|
|
|
|
SaveKeeps();
|
|
Clear();
|
|
BuildStringTypes();
|
|
AddEverything(false);
|
|
RestoreKeeps();
|
|
Resources.UnloadUnusedAssets();
|
|
ForceSave();
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Rebuilds the name indexes by dumping everything back to the list, updating the name, and then rebuilding
|
|
/// the dictionaries.
|
|
/// </summary>
|
|
public void RebuildIndex()
|
|
{
|
|
UpdateSerializedList();
|
|
for (int i = 0; i < SerializedItems.Count; i++)
|
|
{
|
|
AssetItem ai = SerializedItems[i];
|
|
ai._Name = ai.EvilName;
|
|
}
|
|
UpdateSerializedDictionaryItems();
|
|
RebuildRaceRecipes();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Serialization
|
|
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
|
{
|
|
UpdateSerializedList();
|
|
|
|
// load typeFolders so it can be serialized.
|
|
typeFolders.Clear();
|
|
foreach (var kpv in TypeFolderSearch)
|
|
{
|
|
TypeFolders tpf = new TypeFolders();
|
|
tpf.typeName = kpv.Key;
|
|
tpf.Folders = kpv.Value.ToArray();
|
|
typeFolders.Add(tpf);
|
|
}
|
|
}
|
|
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
{
|
|
DebugSerialization("After Deserialize called");
|
|
var st = StartTimer();
|
|
#region typestuff
|
|
List<System.Type> newTypes = new List<System.Type>()
|
|
{
|
|
(typeof(SlotDataAsset)),
|
|
(typeof(OverlayDataAsset)),
|
|
(typeof(RaceData)),
|
|
(typeof(UMATextRecipe)),
|
|
(typeof(UMAWardrobeRecipe)),
|
|
(typeof(UMAWardrobeCollection)),
|
|
(typeof(RuntimeAnimatorController)),
|
|
(typeof(AnimatorOverrideController)),
|
|
#if UNITY_EDITOR
|
|
(typeof(AnimatorController)),
|
|
#endif
|
|
(typeof(DynamicUMADnaAsset)),
|
|
(typeof(TextAsset)),
|
|
(typeof(UMAMaterial)),
|
|
typeof(UMAColorScheme)
|
|
};
|
|
|
|
TypeToLookup = new Dictionary<System.Type, System.Type>()
|
|
{
|
|
{ (typeof(SlotDataAsset)),(typeof(SlotDataAsset)) },
|
|
{ (typeof(OverlayDataAsset)),(typeof(OverlayDataAsset)) },
|
|
{ (typeof(RaceData)),(typeof(RaceData)) },
|
|
{ (typeof(UMATextRecipe)),(typeof(UMATextRecipe)) },
|
|
{ (typeof(UMAWardrobeRecipe)),(typeof(UMAWardrobeRecipe)) },
|
|
{ (typeof(UMAWardrobeCollection)),(typeof(UMAWardrobeCollection)) },
|
|
{ (typeof(RuntimeAnimatorController)),(typeof(RuntimeAnimatorController)) },
|
|
{ (typeof(AnimatorOverrideController)),(typeof(RuntimeAnimatorController)) },
|
|
#if UNITY_EDITOR
|
|
{ (typeof(AnimatorController)),(typeof(RuntimeAnimatorController)) },
|
|
#endif
|
|
{ typeof(TextAsset), typeof(TextAsset) },
|
|
{ (typeof(DynamicUMADnaAsset)), (typeof(DynamicUMADnaAsset)) },
|
|
{ (typeof(UMAMaterial)),(typeof(UMAMaterial)) },
|
|
{ typeof(UMAColorScheme), typeof(UMAColorScheme) }
|
|
};
|
|
|
|
List<string> invalidTypeNames = new List<string>();
|
|
// Add the additional Types.
|
|
for (int i = 0; i < IndexedTypeNames.Count; i++)
|
|
{
|
|
string s = IndexedTypeNames[i];
|
|
if (s == "")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
System.Type sType = System.Type.GetType(s);
|
|
if (sType == null)
|
|
{
|
|
invalidTypeNames.Add(s);
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
Debug.LogWarning("Could not find type for " + s);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
newTypes.Add(sType);
|
|
if (!TypeToLookup.ContainsKey(sType))
|
|
{
|
|
TypeToLookup.Add(sType, sType);
|
|
}
|
|
}
|
|
|
|
Types = newTypes.ToArray();
|
|
|
|
if (invalidTypeNames.Count > 0)
|
|
{
|
|
for (int i = 0; i < invalidTypeNames.Count; i++)
|
|
{
|
|
string ivs = invalidTypeNames[i];
|
|
IndexedTypeNames.Remove(ivs);
|
|
}
|
|
}
|
|
BuildStringTypes();
|
|
|
|
// Load TypeFolderSearch
|
|
TypeFolderSearch.Clear();
|
|
for (int i = 0; i < typeFolders.Count; i++)
|
|
{
|
|
TypeFolders tpf = typeFolders[i];
|
|
TypeFolderSearch.Add(tpf.typeName, new List<string>(tpf.Folders));
|
|
}
|
|
#endregion
|
|
|
|
// if the serialized dictionary is empty
|
|
// and the serialized items are NOT null, then do this
|
|
if (SerializedItems != null && SerializedItems.Count > 0)
|
|
{
|
|
if (TypeLookup == null)
|
|
{
|
|
TypeLookup = new Dictionary<Type, Dictionary<string, AssetItem>>();
|
|
}
|
|
}
|
|
StopTimer(st, "After Serialize");
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public void PrepareBuild()
|
|
{
|
|
SaveKeeps();
|
|
Clear();
|
|
BuildStringTypes();
|
|
AddEverything(false);
|
|
RestoreKeeps();
|
|
ClearMHASlotReferences();
|
|
AddReferences();
|
|
#if UMA_ADDRESSABLES
|
|
// TODO: Build addressable bundles here.
|
|
// For now, we will leave that in the build script.
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// This should be called by your build script
|
|
/// </summary>
|
|
public void ClearMHASlotReferences()
|
|
{
|
|
string[] mhaGUIDS = AssetDatabase.FindAssets("t:MeshHideAsset");
|
|
for (int i = 0; i < mhaGUIDS.Length; i++)
|
|
{
|
|
string guid = mhaGUIDS[i];
|
|
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
string fileName = Path.GetFileName(assetPath);
|
|
MeshHideAsset mha = AssetDatabase.LoadAssetAtPath<MeshHideAsset>(assetPath);
|
|
// mha.FreeReference();
|
|
EditorUtility.SetDirty(mha);
|
|
#if UNITY_2021_1_OR_NEWER
|
|
AssetDatabase.SaveAssetIfDirty(mha);
|
|
#endif
|
|
}
|
|
#if !UNITY_2021_1_OR_NEWER
|
|
AssetDatabase.SaveAssets();
|
|
#endif
|
|
}
|
|
#endif
|
|
#endregion
|
|
}
|
|
|
|
#if UMA_ADDRESSABLES
|
|
|
|
/// <summary>
|
|
/// This exception exists as a separate exception so we can track keys.
|
|
/// </summary>
|
|
public class UMAInvalidKeyException : Exception
|
|
{
|
|
public string Labels { get; private set; }
|
|
public UMAInvalidKeyException()
|
|
{
|
|
Labels = "No Key Specified";
|
|
}
|
|
|
|
public UMAInvalidKeyException(string msg) : base(msg)
|
|
{
|
|
Labels = "No Key Specified";
|
|
}
|
|
public UMAInvalidKeyException(string msg, Exception inner) : base(msg,inner)
|
|
{
|
|
Labels = "No Key Specified";
|
|
}
|
|
|
|
public UMAInvalidKeyException(string msg, List<string> Keys) : base(msg)
|
|
{
|
|
Labels = UMAAssetIndexer.KeysToString(msg,Keys);
|
|
}
|
|
};
|
|
#endif
|
|
}
|